feat(costs): add billing, quota, and budget control plane
This commit is contained in:
@@ -1,6 +1,19 @@
|
||||
import type { ProviderQuotaResult } from "@paperclipai/shared";
|
||||
import { listServerAdapters } from "../adapters/registry.js";
|
||||
|
||||
const QUOTA_PROVIDER_TIMEOUT_MS = 20_000;
|
||||
|
||||
function providerSlugForAdapterType(type: string): string {
|
||||
switch (type) {
|
||||
case "claude_local":
|
||||
return "anthropic";
|
||||
case "codex_local":
|
||||
return "openai";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks each registered adapter for its provider quota windows and aggregates the results.
|
||||
* Adapters that don't implement getQuotaWindows() are silently skipped.
|
||||
@@ -11,19 +24,41 @@ export async function fetchAllQuotaWindows(): Promise<ProviderQuotaResult[]> {
|
||||
const adapters = listServerAdapters().filter((a) => a.getQuotaWindows != null);
|
||||
|
||||
const settled = await Promise.allSettled(
|
||||
adapters.map((adapter) => adapter.getQuotaWindows!()),
|
||||
adapters.map((adapter) => withQuotaTimeout(adapter.type, adapter.getQuotaWindows!())),
|
||||
);
|
||||
|
||||
return settled.map((result, i) => {
|
||||
if (result.status === "fulfilled") return result.value;
|
||||
// Determine provider slug from the fulfilled value if available, otherwise fall back
|
||||
// to the adapter type so the error is still attributable to the right provider.
|
||||
const adapterType = adapters[i]!.type;
|
||||
return {
|
||||
provider: adapterType,
|
||||
provider: providerSlugForAdapterType(adapterType),
|
||||
ok: false,
|
||||
error: String(result.reason),
|
||||
windows: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function withQuotaTimeout(
|
||||
adapterType: string,
|
||||
task: Promise<ProviderQuotaResult>,
|
||||
): Promise<ProviderQuotaResult> {
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
try {
|
||||
return await Promise.race([
|
||||
task,
|
||||
new Promise<ProviderQuotaResult>((resolve) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
resolve({
|
||||
provider: providerSlugForAdapterType(adapterType),
|
||||
ok: false,
|
||||
error: `quota polling timed out after ${Math.round(QUOTA_PROVIDER_TIMEOUT_MS / 1000)}s`,
|
||||
windows: [],
|
||||
});
|
||||
}, QUOTA_PROVIDER_TIMEOUT_MS);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user