Files
openchamber/packages/web/server/lib/quota/providers/index.js
T
Bohdan Triapitsyn b77a30cd88 feat(quota): read Claude plan limits from the Claude Code login
Claude quota only worked when the user had signed into Anthropic through
OpenCode. Credentials are now discovered from Claude Code itself first: the
macOS Keychain entry, then the Linux/WSL credentials file (honouring
CLAUDE_CONFIG_DIR), then OpenCode auth.json, then CLAUDE_CODE_OAUTH_TOKEN.

All sources stay read-only and the OAuth token is never refreshed: Anthropic
allows one live refresh token per client_id, so refreshing here would sign the
user out of Claude Code. Credentials are re-read per request instead, and an
expired token reports that Claude Code needs a sign-in rather than a bare 401.

Usage is now read from the limits[] array, so model-scoped weekly limits work
again after Anthropic stopped populating seven_day_sonnet/seven_day_opus, and
new limit kinds no longer need a code change. Adds extra-usage spend and the
plan name, and holds the last good values through Anthropic's 429s with a
cooldown and an account-keyed cache.
2026-08-14 20:49:59 +03:00

214 lines
6.5 KiB
JavaScript

/**
* Quota Providers Registry
*
* Implements quota fetching for various AI providers using a registry pattern.
* @module quota/providers
*/
import { buildResult } from '../utils/index.js';
import * as claude from './claude/index.js';
import * as codex from './codex.js';
import * as copilot from './copilot.js';
import * as crof from './crof.js';
import * as cursor from './cursor.js';
import * as deepseek from './deepseek.js';
import * as google from './google/index.js';
import * as kimi from './kimi.js';
import * as nanogpt from './nanogpt.js';
import * as openai from './openai.js';
import * as openrouter from './openrouter.js';
import * as zai from './zai.js';
import * as zhipuaiCodingPlan from './zhipuai-coding-plan.js';
import * as minimaxCodingPlan from './minimax-coding-plan.js';
import * as minimaxCnCodingPlan from './minimax-cn-coding-plan.js';
import * as neuralwatt from './neuralwatt.js';
import * as ollamaCloud from './ollama-cloud.js';
import * as wafer from './wafer.js';
import * as opencodeGo from './opencode-go.js';
import * as xai from './xai.js';
const registry = {
claude: {
providerId: claude.providerId,
providerName: claude.providerName,
isConfigured: claude.isConfigured,
fetchQuota: claude.fetchQuota
},
codex: {
providerId: codex.providerId,
providerName: codex.providerName,
isConfigured: codex.isConfigured,
fetchQuota: codex.fetchQuota
},
crof: {
providerId: crof.providerId,
providerName: crof.providerName,
isConfigured: crof.isConfigured,
fetchQuota: crof.fetchQuota
},
cursor: {
providerId: cursor.providerId,
providerName: cursor.providerName,
isConfigured: cursor.isConfigured,
fetchQuota: cursor.fetchQuota
},
deepseek: {
providerId: deepseek.providerId,
providerName: deepseek.providerName,
isConfigured: deepseek.isConfigured,
fetchQuota: deepseek.fetchQuota
},
google: {
providerId: google.providerId,
providerName: google.providerName,
isConfigured: google.isConfigured,
fetchQuota: google.fetchGoogleQuota
},
'zai-coding-plan': {
providerId: zai.providerId,
providerName: zai.providerName,
isConfigured: zai.isConfigured,
fetchQuota: zai.fetchQuota
},
'zhipuai-coding-plan': {
providerId: zhipuaiCodingPlan.providerId,
providerName: zhipuaiCodingPlan.providerName,
isConfigured: zhipuaiCodingPlan.isConfigured,
fetchQuota: zhipuaiCodingPlan.fetchQuota
},
'kimi-for-coding': {
providerId: kimi.providerId,
providerName: kimi.providerName,
isConfigured: kimi.isConfigured,
fetchQuota: kimi.fetchQuota
},
openrouter: {
providerId: openrouter.providerId,
providerName: openrouter.providerName,
isConfigured: openrouter.isConfigured,
fetchQuota: openrouter.fetchQuota
},
'nano-gpt': {
providerId: nanogpt.providerId,
providerName: nanogpt.providerName,
isConfigured: nanogpt.isConfigured,
fetchQuota: nanogpt.fetchQuota
},
'github-copilot': {
providerId: copilot.providerId,
providerName: copilot.providerName,
isConfigured: copilot.isConfigured,
fetchQuota: copilot.fetchQuota
},
'github-copilot-addon': {
providerId: copilot.providerIdAddon,
providerName: copilot.providerNameAddon,
isConfigured: copilot.isConfigured,
fetchQuota: copilot.fetchQuotaAddon
},
'minimax-coding-plan': {
providerId: minimaxCodingPlan.providerId,
providerName: minimaxCodingPlan.providerName,
isConfigured: minimaxCodingPlan.isConfigured,
fetchQuota: minimaxCodingPlan.fetchQuota
},
'minimax-cn-coding-plan': {
providerId: minimaxCnCodingPlan.providerId,
providerName: minimaxCnCodingPlan.providerName,
isConfigured: minimaxCnCodingPlan.isConfigured,
fetchQuota: minimaxCnCodingPlan.fetchQuota
},
'ollama-cloud': {
providerId: ollamaCloud.providerId,
providerName: ollamaCloud.providerName,
isConfigured: ollamaCloud.isConfigured,
fetchQuota: ollamaCloud.fetchQuota
},
wafer: {
providerId: wafer.providerId,
providerName: wafer.providerName,
isConfigured: wafer.isConfigured,
fetchQuota: wafer.fetchQuota
},
'opencode-go': {
providerId: opencodeGo.providerId,
providerName: opencodeGo.providerName,
isConfigured: opencodeGo.isConfigured,
fetchQuota: opencodeGo.fetchQuota
},
neuralwatt: {
providerId: neuralwatt.providerId,
providerName: neuralwatt.providerName,
isConfigured: neuralwatt.isConfigured,
fetchQuota: neuralwatt.fetchQuota
},
xai: {
providerId: xai.providerId,
providerName: xai.providerName,
isConfigured: xai.isConfigured,
fetchQuota: xai.fetchQuota
}
};
export const listConfiguredQuotaProviders = () => {
const configured = [];
for (const [id, provider] of Object.entries(registry)) {
try {
if (provider.isConfigured()) {
configured.push(id);
}
} catch {
// Ignore provider-specific config errors in list API.
}
}
return configured;
};
export const fetchQuotaForProvider = async (providerId) => {
const provider = registry[providerId];
if (!provider) {
return buildResult({
providerId,
providerName: providerId,
ok: false,
configured: false,
error: 'Unsupported provider'
});
}
try {
return await provider.fetchQuota();
} catch (error) {
return buildResult({
providerId: provider.providerId,
providerName: provider.providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};
export const fetchClaudeQuota = claude.fetchQuota;
export const fetchOpenaiQuota = openai.fetchQuota;
export const fetchGoogleQuota = google.fetchGoogleQuota;
export const fetchCodexQuota = codex.fetchQuota;
export const fetchCursorQuota = cursor.fetchQuota;
export const fetchDeepseekQuota = deepseek.fetchQuota;
export const fetchCopilotQuota = copilot.fetchQuota;
export const fetchCopilotAddonQuota = copilot.fetchQuotaAddon;
export const fetchKimiQuota = kimi.fetchQuota;
export const fetchOpenRouterQuota = openrouter.fetchQuota;
export const fetchZaiQuota = zai.fetchQuota;
const fetchZhipuaiCodingPlanQuota = zhipuaiCodingPlan.fetchQuota;
export const fetchNanoGptQuota = nanogpt.fetchQuota;
export const fetchMinimaxCodingPlanQuota = minimaxCodingPlan.fetchQuota;
export const fetchMinimaxCnCodingPlanQuota = minimaxCnCodingPlan.fetchQuota;
export const fetchOllamaCloudQuota = ollamaCloud.fetchQuota;
export const fetchWaferQuota = wafer.fetchQuota;
export const fetchZhipuaiQuota = zhipuaiCodingPlan.fetchQuota;