Files
OmniRoute/open-sse/services/usage.ts
Dohyun Jung a2c5d8a2f5 feat(quota): use official OpenCode Go usage API (#12124)
Migra o quota fetcher do OpenCode Go para a API oficial de uso, com refactor substancial que remove ~1850 linhas de código legado e atualiza a suíte de testes existente inteira para o novo contrato. Validado no worktree combinado (typecheck limpo, testes focados verdes). Obrigado!
2026-08-30 11:11:46 -03:00

248 lines
9.6 KiB
TypeScript

/**
* Usage Fetcher - Get usage data from provider APIs
*
* This module is the dispatcher (orchestration) layer: it maps a provider name
* to the per-provider usage fetcher leaf under `./usage/<provider>.ts` and
* shapes the connection into the args each leaf expects. The provider-specific
* fetcher/parser logic itself lives in those leaves so this file stays flat.
* External consumers import `getUsageForProvider` / `USAGE_FETCHER_PROVIDERS`
* (and the re-exported helpers) from here — the leaf split is an internal
* implementation detail.
*/
import {
extractCodeAssistOnboardTierId,
extractCodeAssistSubscriptionTier,
} from "./codeAssistSubscription.ts";
import { toDisplayLabel } from "./usage/scalars.ts";
import { parseResetTime, createQuotaFromUsage } from "./usage/quota.ts";
import {
getMiniMaxUsage,
getMiniMaxPlanLabel,
getMiniMaxSessionTotal,
inferMiniMaxPlanLabelFromTotals,
getMiniMaxQuotaResetAt,
isMiniMaxTextQuotaModel,
getMiniMaxWeeklyTotal,
createMiniMaxQuotaFromCount,
createMiniMaxQuotaFromPercent,
getMiniMaxRemainingPercent,
getMiniMaxAuthErrorMessage,
getMiniMaxErrorSummary,
} from "./usage/minimax.ts";
import { getGlmUsage } from "./usage/glm.ts";
// Re-exported para o teste glm-coding-plan-monthly (importa de services/usage).
export { glmMonthlyRemainingPercentage } from "./usage/glm.ts";
import {
getAntigravityUsage,
getAntigravityPlanLabel,
mapCodeAssistSubscriptionToPlanLabel,
mapCodeAssistTierIdToLabel,
mapSubscriptionTierStringToPlanLabel,
} from "./usage/antigravity.ts";
import { getCursorUsage } from "./usage/cursor.ts";
import { getKimiUsage } from "./usage/kimi.ts";
import { getCodexUsage } from "./usage/codex.ts";
import { getClaudeUsage, getClaudePlanLabel } from "./usage/claude.ts";
import { getKiroUsage, buildKiroUsageResult, discoverKiroProfileArn } from "./usage/kiro.ts";
// Re-exported para os testes kiro-* (importam de services/usage).
export { buildKiroUsageResult, discoverKiroProfileArn } from "./usage/kiro.ts";
import { getAdobeFireflyUsage } from "./usage/adobeFirefly.ts";
import { getOpenrouterUsage } from "./usage/openrouter.ts";
import { getOllamaCloudUsage } from "./opencodeOllamaUsage.ts";
import { getCodeBuddyCnUsage } from "./usage/codebuddy-cn.ts";
import { getPromptQlUsage } from "./usage/promptql.ts";
import { getHyperAgentUsage } from "./usage/hyperagent.ts";
import { getGitHubUsage, formatGitHubQuotaSnapshot, inferGitHubPlanName } from "./usage/github.ts";
import { getCrofUsage } from "./usage/crof.ts";
import { getNanoGptUsage } from "./usage/nanogpt.ts";
import { getQoderUsage, parseQoderUserStatusUsage } from "./usage/qoder.ts";
// Re-exported para o teste qoder-usage-quota (importa parseQoderUserStatusUsage de services/usage).
export { parseQoderUserStatusUsage } from "./usage/qoder.ts";
import { getOpencodeUsage } from "./usage/opencode.ts";
import { getDeepseekUsage } from "./usage/deepseek.ts";
import { getBailianCodingPlanUsage } from "./usage/bailian.ts";
import { getVertexUsage } from "./usage/vertex.ts";
import { getXiaomiMimoUsage } from "./usage/xiaomi-mimo.ts";
import { getXaiUsage } from "./usage/xai.ts";
import { getXaiOauthUsage } from "./usage/xaiOauth.ts";
import { getGrokCliUsage } from "./usage/grokCli.ts";
import { getFirecrawlUsage } from "./usage/firecrawl.ts";
import { getVolcenginePlanUsage } from "./usage/volcenginePlan.ts";
import { getCommandCodeUsage } from "./usage/command-code.ts";
import { getQwenTokenPlanUsage } from "./usage/qwen-token-plan.ts";
import { getConolUsage } from "./conolUsage.ts";
import { getAgentrouterUsage } from "./usage/agentrouter.ts";
type JsonRecord = Record<string, unknown>;
type UsageProviderConnection = JsonRecord & {
id?: string;
provider?: string;
accessToken?: string;
apiKey?: string;
providerSpecificData?: JsonRecord;
projectId?: string;
email?: string;
};
/**
* Single source of truth for which providers have a `getUsageForProvider`
* implementation — consumers like `genericQuotaFetcher.ts` reference it so the
* registration list can't drift from the switch statement below.
*
* If you add a new provider to the switch, add it to the list too. The list now lives in
* `./usage/fetcherProviders.ts` (a zero-dependency leaf) so that consumers which only need
* to know *whether* a fetcher exists — the provider-plugin manifest — can read it without
* importing this dispatcher. Re-exported here so this stays the public import path.
*/
export { USAGE_FETCHER_PROVIDERS } from "./usage/fetcherProviders.ts";
export type { UsageFetcherProvider } from "./usage/fetcherProviders.ts";
/**
* Get usage data for a provider connection
* @param {Object} connection - Provider connection with accessToken
* @returns {Promise<unknown>} Usage data with quotas
*/
export async function getUsageForProvider(
connection: UsageProviderConnection,
options: { forceRefresh?: boolean } = {}
) {
const { id, provider, accessToken, apiKey, providerSpecificData, projectId, email } = connection;
switch (provider) {
case "github":
return await getGitHubUsage(accessToken, providerSpecificData);
case "antigravity":
case "agy":
return await getAntigravityUsage(
provider,
accessToken,
providerSpecificData,
projectId,
id,
options
);
case "claude":
return await getClaudeUsage(accessToken);
case "codex":
return await getCodexUsage(accessToken, providerSpecificData);
case "cursor":
return await getCursorUsage(accessToken || "", providerSpecificData);
case "kiro":
case "amazon-q":
return await getKiroUsage(accessToken, providerSpecificData);
case "vertex":
case "vertex-partner":
return await getVertexUsage(id || "", provider);
case "kimi-coding":
case "kimi-coding-apikey":
return await getKimiUsage(accessToken, apiKey, providerSpecificData);
case "qoder":
// Qoder PATs live in `apiKey` (decrypted) or `providerSpecificData.qoderPat`,
// never in `accessToken`.
return await getQoderUsage(apiKey, providerSpecificData);
case "glm":
case "glm-cn":
case "zai":
case "glmt":
return await getGlmUsage(apiKey || "", {
...(providerSpecificData || {}),
...(provider === "glm-cn" ? { apiRegion: "china" } : {}),
});
case "opencode-go":
return await getOpencodeUsage(id || "", apiKey || "");
case "ollama-cloud":
return await getOllamaCloudUsage(providerSpecificData);
case "minimax":
case "minimax-cn":
return await getMiniMaxUsage(apiKey || "", provider);
case "crof":
return await getCrofUsage(apiKey || "");
case "bailian-coding-plan":
return await getBailianCodingPlanUsage(id || "", apiKey || "", providerSpecificData);
case "qwen-cloud-token-plan":
return await getQwenTokenPlanUsage(id || "", apiKey || "", providerSpecificData);
case "nanogpt":
return await getNanoGptUsage(apiKey || "");
case "deepseek":
return await getDeepseekUsage(id || "", apiKey || "");
case "openrouter":
return await getOpenrouterUsage(id || "", apiKey || "", providerSpecificData);
case "opencode":
case "opencode-zen":
return await getOpencodeUsage(id || "", apiKey || "");
case "xiaomi-mimo":
return await getXiaomiMimoUsage(id || "");
case "xai":
return await getXaiUsage(id || "");
case "xai-oauth":
case "xao":
return await getXaiOauthUsage(id || "", accessToken, connection);
case "grok-cli":
return await getGrokCliUsage(accessToken);
case "codebuddy-cn":
return await getCodeBuddyCnUsage(accessToken, apiKey, providerSpecificData);
case "promptql":
case "pql":
// DDN lux JWTs carry projectId only in JWT aud; connection.projectId may be set by sync.
return await getPromptQlUsage(apiKey || accessToken, providerSpecificData, projectId);
case "adobe-firefly":
case "firefly":
// Cookie or IMS JWT in apiKey/accessToken → GET firefly.adobe.io/v1/credits/balance
return await getAdobeFireflyUsage(apiKey, accessToken, providerSpecificData);
case "hyperagent":
case "ha":
return await getHyperAgentUsage(apiKey || accessToken, providerSpecificData);
case "firecrawl":
return await getFirecrawlUsage(id || "", apiKey, connection);
case "volcengine-agent-plan":
case "volcengine-coding-plan":
return await getVolcenginePlanUsage(apiKey || "", provider, providerSpecificData);
case "command-code":
return await getCommandCodeUsage(apiKey || accessToken || "");
case "conol-web":
case "cnl":
return await getConolUsage(apiKey || accessToken, providerSpecificData);
case "agentrouter":
return await getAgentrouterUsage(id, connection);
default:
return { message: `Usage API not implemented for ${provider}` };
}
}
export const __testing = {
parseResetTime,
parseQoderUserStatusUsage,
formatGitHubQuotaSnapshot,
inferGitHubPlanName,
getAntigravityPlanLabel,
extractCodeAssistSubscriptionTier,
extractCodeAssistOnboardTierId,
getMiniMaxPlanLabel,
inferMiniMaxPlanLabelFromTotals,
getOpencodeUsage,
getClaudePlanLabel,
createQuotaFromUsage,
getMiniMaxQuotaResetAt,
isMiniMaxTextQuotaModel,
getMiniMaxSessionTotal,
getMiniMaxWeeklyTotal,
createMiniMaxQuotaFromCount,
createMiniMaxQuotaFromPercent,
getMiniMaxRemainingPercent,
getMiniMaxUsage,
getXiaomiMimoUsage,
getXaiUsage,
getXaiOauthUsage,
getFirecrawlUsage,
getCommandCodeUsage,
getVertexUsage,
getMiniMaxAuthErrorMessage,
getMiniMaxErrorSummary,
mapCodeAssistSubscriptionToPlanLabel,
mapCodeAssistTierIdToLabel,
mapSubscriptionTierStringToPlanLabel,
toDisplayLabel,
getKiroUsage,
};