Refactor application architecture and shared functionality

This commit is contained in:
Jakub Syty
2026-08-21 10:59:47 +02:00
398 changed files with 28819 additions and 4361 deletions
+17 -2
View File
@@ -8,6 +8,7 @@ This module fetches quota and usage signals for supported providers in the web s
- `packages/web/server/lib/quota/routes.js`: Express route registration for quota endpoints.
- `packages/web/server/lib/quota/providers/index.js`: provider registry, configured-provider list, and provider dispatcher.
- `packages/web/server/lib/quota/providers/google/`: Google-specific auth, API, and transform modules.
- `packages/web/server/lib/quota/providers/claude/`: Claude credential discovery, usage transforms, and rate-limit handling.
- `packages/web/server/lib/quota/utils/`: shared auth, transform, and formatting helpers.
## Supported provider IDs (dispatcher)
@@ -16,8 +17,9 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
| Provider ID | Display name | Module | Auth aliases/keys |
| --- | --- | --- | --- |
| `claude` | Claude | `providers/claude.js` | `anthropic`, `claude` |
| `claude` | Claude | `providers/claude/` | Claude Code Keychain entry, Claude Code credentials file, OpenCode `auth.json` (`anthropic`, `claude`), `CLAUDE_CODE_OAUTH_TOKEN` |
| `codex` | Codex | `providers/codex.js` | `openai`, `codex`, `chatgpt` |
| `command-code` | Command Code | `providers/command-code.js` | `command-code` OAuth/API credential in OpenCode `auth.json`, or `COMMAND_CODE_API_KEY` |
| `cursor` | Cursor | `providers/cursor.js` | Environment/token files, OpenChamber-managed credentials, or explicit one-time Cursor import |
| `crof` | CrofAI | `providers/crof.js` | `crof` (API key under `key` or `token`) |
| `deepseek` | DeepSeek | `providers/deepseek.js` | `deepseek` (API key under `key` or `token`) |
@@ -51,8 +53,21 @@ Provider modules must export `providerId`, `providerName`, `aliases`, `isConfigu
Ollama Cloud and Cursor credentials are explicitly managed through Settings. OpenCode Go usage uses `GET https://opencode.ai/zen/go/v1/usage` with the `opencode-go` API key from OpenCode `auth.json` as a bearer token. The server validates managed credentials before atomic `0600` writes and never returns secrets through its API. OpenChamber never scans browser cookie stores or automatically reads Cursor storage; Cursor import is an explicit one-time user action and never modifies Cursor's database.
Command Code usage resolves account scope through `GET /alpha/whoami`, then reads server-backed credit balances and five-hour/weekly limits from `GET /alpha/billing/credits?orgId=...`. Personal accounts return `org: null` and use `/alpha/billing/credits` without an `orgId`; organization accounts include their organization id. Web/Electron and VS Code read the standard `command-code` OpenCode auth entry (including OAuth `access`) or `COMMAND_CODE_API_KEY`; credentials remain in the owning runtime and are never returned to shared UI.
On the first OpenCode Go usage refresh after upgrading, OpenChamber deletes the obsolete `quota/opencode-go.json` credential file without reading its cookie value.
## Claude credential and limit semantics
Claude quota reports the subscription limits Claude Code itself is bound by, read from `GET https://api.anthropic.com/api/oauth/usage`.
- **Credential sources**, in priority order: the macOS Keychain entry `Claude Code-credentials`, then `${CLAUDE_CONFIG_DIR:-~/.claude}/.credentials.json` (the Linux/WSL location), then the OpenCode `auth.json` entry, then `CLAUDE_CODE_OAUTH_TOKEN`. The Keychain wins on macOS because the credentials file there is a leftover Claude Code no longer updates.
- **All sources are read-only.** OpenChamber never writes to Claude Code's credential store and never refreshes the OAuth token, because Anthropic does not support two live refresh tokens for one `client_id` — refreshing here would sign the user out of Claude Code. Credentials are read fresh per request so a Claude Code refresh is picked up immediately; an expired token yields an explicit "open Claude Code to sign in again" error rather than a bare 401.
- **Limits come from the `limits` array**, keyed by `kind`: `session` maps to the `5h` window, `weekly_all` to `7d`, and `weekly_scoped` to a per-model `7d` window named by `scope.model.display_name`. The legacy `five_hour`/`seven_day` fields are only a fallback; `seven_day_sonnet`/`seven_day_opus` are no longer populated by Anthropic. Unrecognized limit kinds and Anthropic's rotating internal code names (`nimbus_quill`, `tangelo`, ...) are ignored rather than guessed at.
- **Extra usage** is reported as the `extra_usage` window from `spend`, only while `spend.enabled` is true, with a money `valueLabel`.
- **Rate limiting**: Anthropic returns 429 aggressively. The last successful usage payload is cached in memory and reserved during a cooldown (`Retry-After`, else five minutes, capped at one hour). The cache is keyed by a hash of the access and refresh tokens, so switching accounts drops it instead of showing the previous account's numbers.
- **Runtime parity**: Web/Electron and VS Code preserve the last successful Claude values during the same bounded 429 cooldown. Quota dispatchers also coalesce concurrent refreshes for the same provider in each runtime, while requests for different providers remain parallel.
## Add a new provider (quick steps)
1. Choose module shape based on complexity:
- Simple providers: create `packages/web/server/lib/quota/providers/<provider>.js`.
@@ -95,4 +110,4 @@ completion quota are intentionally omitted. Keep
- Keep provider IDs stable; clients use them directly.
- Avoid adding alias-based dispatch in `fetchQuotaForProvider`; dispatch currently expects exact provider IDs.
- Keep Google behavior changes isolated and review `providers/google/*` together.
- Z.ai Coding Plan exposes separate 5-hour and weekly `TOKENS_LIMIT` entries plus a monthly `TIME_LIMIT` for MCP tools; web and VS Code must preserve all three windows.
- Z.ai Coding Plan exposes separate 5-hour and weekly token/credit limit entries plus a monthly `TIME_LIMIT` for MCP tools. The API renamed the limit type from `TOKENS_LIMIT` to `CREDIT_LIMIT` (same `unit`/`number` window semantics); `CREDIT_LIMIT` entries additionally carry `usage` (total), `currentValue` (consumed), and `remaining`, surfaced as a credit `valueLabel`, and the payload's `data.level` becomes `planLabel`. Web and VS Code must preserve these windows and stay in sync.
@@ -1,107 +0,0 @@
import { readAuthFile } from '../../opencode/auth.js';
import {
getAuthEntry,
normalizeAuthEntry,
buildResult,
toUsageWindow,
toNumber,
toTimestamp
} from '../utils/index.js';
export const providerId = 'claude';
export const providerName = 'Claude';
const aliases = ['anthropic', 'claude'];
export const isConfigured = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
return Boolean(entry?.access || entry?.token);
};
export const fetchQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const accessToken = entry?.access ?? entry?.token;
if (!accessToken) {
return buildResult({
providerId,
providerName,
ok: false,
configured: false,
error: 'Not configured'
});
}
try {
const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'anthropic-beta': 'oauth-2025-04-20'
}
});
if (!response.ok) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: `API error: ${response.status}`
});
}
const payload = await response.json();
const windows = {};
const fiveHour = payload?.five_hour ?? null;
const sevenDay = payload?.seven_day ?? null;
const sevenDaySonnet = payload?.seven_day_sonnet ?? null;
const sevenDayOpus = payload?.seven_day_opus ?? null;
if (fiveHour) {
windows['5h'] = toUsageWindow({
usedPercent: toNumber(fiveHour.utilization),
windowSeconds: null,
resetAt: toTimestamp(fiveHour.resets_at)
});
}
if (sevenDay) {
windows['7d'] = toUsageWindow({
usedPercent: toNumber(sevenDay.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDay.resets_at)
});
}
if (sevenDaySonnet) {
windows['7d-sonnet'] = toUsageWindow({
usedPercent: toNumber(sevenDaySonnet.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDaySonnet.resets_at)
});
}
if (sevenDayOpus) {
windows['7d-opus'] = toUsageWindow({
usedPercent: toNumber(sevenDayOpus.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDayOpus.resets_at)
});
}
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};
@@ -0,0 +1,113 @@
/**
* Claude credential discovery.
*
* Claude Code is the primary source: on macOS it keeps its OAuth tokens in the
* login Keychain, elsewhere in a credentials file. OpenCode's own `auth.json`
* entry is the fallback for users who signed into Anthropic through OpenCode
* instead of Claude Code.
*
* Every source is read-only. Claude rotates a Keychain/credentials entry from
* under us whenever Claude Code refreshes, so credentials are read fresh per
* request rather than cached; a stale cached token would outlive the record it
* came from.
*
* @module quota/providers/claude/auth
*/
import { execFileSync } from 'child_process';
import os from 'os';
import path from 'path';
import { readAuthFile } from '../../../opencode/auth.js';
import { asObject, asNonEmptyString, normalizeTimestamp, getAuthEntry, normalizeAuthEntry, readJsonFile } from '../../utils/index.js';
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
const OPENCODE_AUTH_ALIASES = ['anthropic', 'claude'];
/**
* @typedef {object} ClaudeCredential
* @property {string} accessToken
* @property {string|null} refreshToken
* @property {number|null} expiresAt Epoch milliseconds, when the source reports it.
* @property {string|null} planLabel Subscription tier reported by Claude Code, e.g. `max`.
* @property {'keychain'|'credentials-file'|'opencode-auth'|'env'} source
*/
const claudeConfigDirectory = () => {
const override = asNonEmptyString(process.env.CLAUDE_CONFIG_DIR);
return override ? path.resolve(override) : path.join(os.homedir(), '.claude');
};
/**
* Claude Code writes one JSON blob holding both its own OAuth tokens
* (`claudeAiOauth`) and unrelated MCP server tokens. Only the former is read.
*/
const parseClaudeCodeBlob = (blob, source) => {
const oauth = asObject(asObject(blob)?.claudeAiOauth);
const accessToken = asNonEmptyString(oauth?.accessToken);
if (!accessToken) return null;
return {
accessToken,
refreshToken: asNonEmptyString(oauth.refreshToken),
expiresAt: normalizeTimestamp(oauth.expiresAt),
planLabel: asNonEmptyString(oauth.subscriptionType),
source
};
};
const readKeychainCredential = () => {
if (process.platform !== 'darwin') return null;
let raw;
try {
raw = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {
encoding: 'utf8',
timeout: 10_000,
stdio: ['ignore', 'pipe', 'ignore']
});
} catch {
// No entry, or the user denied Keychain access. Both mean "try the next source".
return null;
}
try {
return parseClaudeCodeBlob(JSON.parse(raw.trim()), 'keychain');
} catch {
console.warn('Claude quota: Keychain credentials are not valid JSON');
return null;
}
};
const readCredentialsFile = () =>
parseClaudeCodeBlob(readJsonFile(path.join(claudeConfigDirectory(), '.credentials.json')), 'credentials-file');
const readOpenCodeCredential = () => {
const entry = normalizeAuthEntry(getAuthEntry(readAuthFile(), OPENCODE_AUTH_ALIASES));
const accessToken = asNonEmptyString(entry?.access) ?? asNonEmptyString(entry?.token);
if (!accessToken) return null;
return {
accessToken,
refreshToken: asNonEmptyString(entry.refresh),
expiresAt: normalizeTimestamp(entry.expires),
planLabel: null,
source: 'opencode-auth'
};
};
const readEnvCredential = () => {
const accessToken = asNonEmptyString(process.env.CLAUDE_CODE_OAUTH_TOKEN);
if (!accessToken) return null;
return { accessToken, refreshToken: null, expiresAt: null, planLabel: null, source: 'env' };
};
/**
* First credential a source can produce, in priority order.
*
* The Keychain wins over the credentials file because on macOS the file is a
* leftover that Claude Code no longer updates.
*
* @returns {ClaudeCredential|null}
*/
export const loadClaudeCredential = () =>
readKeychainCredential()
?? readCredentialsFile()
?? readOpenCodeCredential()
?? readEnvCredential();
@@ -0,0 +1,117 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const execFileSync = vi.fn();
const files = new Map();
const openCodeAuth = vi.fn(() => ({}));
vi.mock('child_process', () => ({ execFileSync: (...args) => execFileSync(...args) }));
vi.mock('fs', () => {
const fs = {
existsSync: (filePath) => files.has(filePath),
readFileSync: (filePath) => {
if (!files.has(filePath)) throw new Error('ENOENT');
return files.get(filePath);
},
};
return { ...fs, default: fs };
});
vi.mock('../../../opencode/auth.js', () => ({ readAuthFile: () => openCodeAuth() }));
import { loadClaudeCredential } from './auth.js';
const claudeCodeBlob = (accessToken) => JSON.stringify({
mcpOAuth: { 'linear|abc': { accessToken: 'unrelated-mcp-token' } },
claudeAiOauth: {
accessToken,
refreshToken: `${accessToken}-refresh`,
expiresAt: 1786735755912,
subscriptionType: 'max',
},
});
const withPlatform = (platform, run) => {
const original = Object.getOwnPropertyDescriptor(process, 'platform');
Object.defineProperty(process, 'platform', { value: platform, configurable: true });
try {
return run();
} finally {
Object.defineProperty(process, 'platform', original);
}
};
beforeEach(() => {
files.clear();
execFileSync.mockReset();
execFileSync.mockImplementation(() => { throw new Error('no keychain entry'); });
openCodeAuth.mockReturnValue({});
delete process.env.CLAUDE_CONFIG_DIR;
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
});
afterEach(() => {
delete process.env.CLAUDE_CONFIG_DIR;
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
});
describe('Claude credential discovery', () => {
it('prefers the macOS Keychain over a stale credentials file', () => {
execFileSync.mockReturnValue(claudeCodeBlob('keychain-token'));
files.set(`${process.env.HOME}/.claude/.credentials.json`, claudeCodeBlob('file-token'));
const credential = withPlatform('darwin', loadClaudeCredential);
expect(credential.accessToken).toBe('keychain-token');
expect(credential.refreshToken).toBe('keychain-token-refresh');
expect(credential.planLabel).toBe('max');
expect(credential.source).toBe('keychain');
});
it('reads the credentials file on Linux, where there is no Keychain', () => {
files.set(`${process.env.HOME}/.claude/.credentials.json`, claudeCodeBlob('file-token'));
const credential = withPlatform('linux', loadClaudeCredential);
expect(execFileSync).not.toHaveBeenCalled();
expect(credential.accessToken).toBe('file-token');
expect(credential.source).toBe('credentials-file');
});
it('honours CLAUDE_CONFIG_DIR when locating the credentials file', () => {
process.env.CLAUDE_CONFIG_DIR = '/tmp/claude-home';
files.set('/tmp/claude-home/.credentials.json', claudeCodeBlob('custom-dir-token'));
expect(withPlatform('linux', loadClaudeCredential).accessToken).toBe('custom-dir-token');
});
it('falls back to the OpenCode auth entry when Claude Code is not signed in', () => {
openCodeAuth.mockReturnValue({ anthropic: { access: 'opencode-token', refresh: 'opencode-refresh', expires: 1786735755912 } });
const credential = withPlatform('linux', loadClaudeCredential);
expect(credential.accessToken).toBe('opencode-token');
expect(credential.source).toBe('opencode-auth');
expect(credential.planLabel).toBeNull();
});
it('falls back to CLAUDE_CODE_OAUTH_TOKEN last, without a refresh token', () => {
process.env.CLAUDE_CODE_OAUTH_TOKEN = 'env-token';
const credential = withPlatform('linux', loadClaudeCredential);
expect(credential.accessToken).toBe('env-token');
expect(credential.refreshToken).toBeNull();
expect(credential.source).toBe('env');
});
it('ignores a Keychain blob that only holds unrelated MCP tokens', () => {
execFileSync.mockReturnValue(JSON.stringify({ mcpOAuth: { 'linear|abc': { accessToken: 'unrelated' } } }));
expect(withPlatform('darwin', loadClaudeCredential)).toBeNull();
});
it('returns null when every source is empty', () => {
expect(withPlatform('darwin', loadClaudeCredential)).toBeNull();
});
});
@@ -0,0 +1,151 @@
/**
* Claude subscription quota.
*
* Reports the plan limits Claude Code itself is bound by (rolling session
* window, weekly windows, model-scoped weekly windows, and paid extra usage)
* using the OAuth credential Claude Code already holds.
*
* @module quota/providers/claude
*/
import { createHash } from 'crypto';
import { buildResult } from '../../utils/index.js';
import { loadClaudeCredential } from './auth.js';
import { toClaudeUsage } from './transforms.js';
export const providerId = 'claude';
export const providerName = 'Claude';
export const aliases = ['anthropic', 'claude'];
const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
const OAUTH_BETA_HEADER = 'oauth-2025-04-20';
const DEFAULT_COOLDOWN_MS = 5 * 60 * 1000;
const MAX_COOLDOWN_MS = 60 * 60 * 1000;
/**
* Last good payload, kept only to survive Anthropic's aggressive rate limiting.
* Keyed by credential fingerprint so a second account never sees the first
* account's numbers.
*
* @type {{ fingerprint: string, usage: object, planLabel: string|null }|null}
*/
let cachedUsage = null;
let cooldownUntil = 0;
let pendingFetch = null;
const fingerprintOf = (credential) =>
createHash('sha256').update(`${credential.accessToken}\0${credential.refreshToken ?? ''}`).digest('hex');
const cooldownFromHeader = (response) => {
const raw = response.headers.get('retry-after');
const retryAfter = Number(raw);
if (Number.isFinite(retryAfter) && retryAfter > 0) {
return Math.min(retryAfter * 1000, MAX_COOLDOWN_MS);
}
const retryAt = raw ? Date.parse(raw) : Number.NaN;
if (Number.isFinite(retryAt) && retryAt > Date.now()) {
return Math.min(retryAt - Date.now(), MAX_COOLDOWN_MS);
}
return DEFAULT_COOLDOWN_MS;
};
const cachedResultFor = (fingerprint, planLabel) => {
if (!cachedUsage || cachedUsage.fingerprint !== fingerprint) return null;
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage: cachedUsage.usage,
planLabel: planLabel ?? cachedUsage.planLabel
});
};
const failure = (error, { configured = true } = {}) =>
buildResult({ providerId, providerName, ok: false, configured, error });
export const isConfigured = () => Boolean(loadClaudeCredential());
const fetchQuotaUncoalesced = async () => {
const credential = loadClaudeCredential();
if (!credential) {
return failure('Not configured', { configured: false });
}
const fingerprint = fingerprintOf(credential);
if (cachedUsage && cachedUsage.fingerprint !== fingerprint) {
cachedUsage = null;
cooldownUntil = 0;
}
if (Date.now() < cooldownUntil) {
return cachedResultFor(fingerprint, credential.planLabel)
?? failure('Rate limited. Retrying soon.');
}
let response;
try {
response = await fetch(USAGE_URL, {
method: 'GET',
headers: {
Authorization: `Bearer ${credential.accessToken}`,
'anthropic-beta': OAUTH_BETA_HEADER
}
});
} catch (error) {
return failure(error instanceof Error ? error.message : 'Request failed');
}
if (response.status === 429) {
cooldownUntil = Date.now() + cooldownFromHeader(response);
return cachedResultFor(fingerprint, credential.planLabel)
?? failure('Rate limited. Retrying soon.');
}
if (response.status === 401 || response.status === 403) {
return failure('Claude session expired. Open Claude Code to sign in again.');
}
if (!response.ok) {
return failure(`API error: ${response.status}`);
}
let payload;
try {
payload = await response.json();
} catch {
return failure('Unexpected response from Anthropic');
}
const { windows, models } = toClaudeUsage(payload);
const usage = Object.keys(models).length > 0 ? { windows, models } : { windows };
cachedUsage = { fingerprint, usage, planLabel: credential.planLabel };
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage,
planLabel: credential.planLabel
});
};
export const fetchQuota = () => {
if (pendingFetch) return pendingFetch;
const request = fetchQuotaUncoalesced();
const pending = request.finally(() => {
if (pendingFetch === pending) pendingFetch = null;
});
pendingFetch = pending;
return pendingFetch;
};
/** Test seam: clears the rate-limit cache between cases. */
export const resetClaudeQuotaCache = () => {
cachedUsage = null;
cooldownUntil = 0;
pendingFetch = null;
};
@@ -0,0 +1,147 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const credential = vi.fn();
vi.mock('./auth.js', () => ({
loadClaudeCredential: () => credential(),
}));
import { fetchQuota, isConfigured, resetClaudeQuotaCache } from './index.js';
const CREDENTIAL = {
accessToken: 'access-a',
refreshToken: 'refresh-a',
expiresAt: Date.now() + 3_600_000,
planLabel: 'max',
source: 'keychain',
};
const PAYLOAD = {
limits: [
{ kind: 'session', percent: 5, resets_at: '2026-08-14T19:10:00Z', scope: null },
{ kind: 'weekly_all', percent: 4, resets_at: '2026-08-20T15:00:00Z', scope: null },
],
};
const jsonResponse = (body) => ({
ok: true,
status: 200,
headers: new Headers(),
json: async () => body,
});
const errorResponse = (status, headers = {}) => ({
ok: false,
status,
headers: new Headers(headers),
json: async () => ({}),
});
beforeEach(() => {
resetClaudeQuotaCache();
credential.mockReturnValue(CREDENTIAL);
});
afterEach(() => {
vi.unstubAllGlobals();
});
describe('Claude quota provider', () => {
it('reports not configured when no credential source has a token', async () => {
credential.mockReturnValue(null);
expect(isConfigured()).toBe(false);
const result = await fetchQuota();
expect(result.configured).toBe(false);
expect(result.ok).toBe(false);
expect(result.usage).toBeNull();
});
it('returns windows and the plan label from the credential', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(jsonResponse(PAYLOAD)));
const result = await fetchQuota();
expect(result.ok).toBe(true);
expect(result.planLabel).toBe('max');
expect(result.usage.windows['5h'].usedPercent).toBe(5);
});
it('coalesces concurrent refreshes into one Anthropic request', async () => {
let resolveResponse;
const fetchMock = vi.fn().mockReturnValue(new Promise((resolve) => {
resolveResponse = resolve;
}));
vi.stubGlobal('fetch', fetchMock);
const first = fetchQuota();
const second = fetchQuota();
resolveResponse(jsonResponse(PAYLOAD));
const [firstResult, secondResult] = await Promise.all([first, second]);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(firstResult.ok).toBe(true);
expect(secondResult.ok).toBe(true);
});
it('keeps serving the last good values while Anthropic rate limits', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(jsonResponse(PAYLOAD))
.mockResolvedValueOnce(errorResponse(429, { 'retry-after': '120' }));
vi.stubGlobal('fetch', fetchMock);
await fetchQuota();
const rateLimited = await fetchQuota();
expect(rateLimited.ok).toBe(true);
expect(rateLimited.usage.windows['5h'].usedPercent).toBe(5);
});
it('does not call Anthropic again while the rate-limit cooldown is active', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(jsonResponse(PAYLOAD))
.mockResolvedValueOnce(errorResponse(429));
vi.stubGlobal('fetch', fetchMock);
await fetchQuota();
await fetchQuota();
await fetchQuota();
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it('never shows one account cached values after the credential changes', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(jsonResponse(PAYLOAD))
.mockResolvedValueOnce(errorResponse(429));
vi.stubGlobal('fetch', fetchMock);
await fetchQuota();
credential.mockReturnValue({ ...CREDENTIAL, accessToken: 'access-b', refreshToken: 'refresh-b', planLabel: null });
const afterSwitch = await fetchQuota();
expect(afterSwitch.ok).toBe(false);
expect(afterSwitch.usage).toBeNull();
});
it('explains an expired session instead of reporting a bare 401', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(errorResponse(401)));
const result = await fetchQuota();
expect(result.ok).toBe(false);
expect(result.configured).toBe(true);
expect(result.error).toContain('Claude Code');
});
it('surfaces a network failure instead of an empty successful result', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('socket hang up')));
const result = await fetchQuota();
expect(result.ok).toBe(false);
expect(result.usage).toBeNull();
expect(result.error).toBe('socket hang up');
});
});
@@ -0,0 +1,127 @@
/**
* Shapes Anthropic's OAuth usage payload into quota windows.
*
* The payload carries the same limit twice: a legacy set of named fields
* (`five_hour`, `seven_day`, ...) and a newer `limits` array. Only the array
* reports model-scoped limits, and Anthropic ships new limit kinds there under
* rotating internal code names, so limits are read from the array by `kind` and
* fall back to the legacy fields when the array is missing.
*
* @module quota/providers/claude/transforms
*/
import { asObject, asNonEmptyString, toNumber, toTimestamp, toUsageWindow, formatMoney } from '../../utils/index.js';
const SESSION_WINDOW = '5h';
const WEEKLY_WINDOW = '7d';
const EXTRA_USAGE_WINDOW = 'extra_usage';
// Consumers rank limits by how soon they run out, so each window carries its
// duration. Extra usage is a monthly spend cap, not a rolling window.
const SESSION_WINDOW_SECONDS = 5 * 60 * 60;
const WEEKLY_WINDOW_SECONDS = 7 * 24 * 60 * 60;
const asArray = (value) => (Array.isArray(value) ? value : []);
/** Money in Anthropic's minor-unit form, e.g. `{ amount_minor: 10000, exponent: 2 }`. */
const toAmount = (value) => {
const money = asObject(value);
const minor = toNumber(money?.amount_minor);
if (minor === null) return null;
const exponent = toNumber(money?.exponent) ?? 2;
return minor / 10 ** exponent;
};
const formatSpendLabel = (used, limit, currency) => {
const usedLabel = formatMoney(used);
if (usedLabel === null) return null;
const prefix = currency === 'USD' || !currency ? '$' : `${currency} `;
const limitLabel = formatMoney(limit);
return limitLabel === null ? `${prefix}${usedLabel}` : `${prefix}${usedLabel} / ${prefix}${limitLabel}`;
};
const addWindow = (target, key, { percent, resetAt, valueLabel, windowSeconds = null }) => {
if (percent === null && !valueLabel) return;
target[key] = toUsageWindow({ usedPercent: percent, windowSeconds, resetAt, valueLabel });
};
const applyLimitsArray = (limits, windows, models) => {
for (const entry of limits) {
const limit = asObject(entry);
if (!limit) continue;
const percent = toNumber(limit.percent);
const resetAt = toTimestamp(limit.resets_at);
const modelName = asNonEmptyString(asObject(asObject(limit.scope)?.model)?.display_name);
if (limit.kind === 'session') {
addWindow(windows, SESSION_WINDOW, { percent, resetAt, windowSeconds: SESSION_WINDOW_SECONDS });
continue;
}
if (limit.kind === 'weekly_all') {
addWindow(windows, WEEKLY_WINDOW, { percent, resetAt, windowSeconds: WEEKLY_WINDOW_SECONDS });
continue;
}
if (limit.kind === 'weekly_scoped' && modelName) {
const modelWindows = {};
addWindow(modelWindows, WEEKLY_WINDOW, { percent, resetAt, windowSeconds: WEEKLY_WINDOW_SECONDS });
if (Object.keys(modelWindows).length > 0) models[modelName] = { windows: modelWindows };
}
}
};
const applyLegacyFields = (payload, windows) => {
const fiveHour = asObject(payload.five_hour);
const sevenDay = asObject(payload.seven_day);
if (fiveHour) {
addWindow(windows, SESSION_WINDOW, {
percent: toNumber(fiveHour.utilization),
resetAt: toTimestamp(fiveHour.resets_at),
windowSeconds: SESSION_WINDOW_SECONDS
});
}
if (sevenDay) {
addWindow(windows, WEEKLY_WINDOW, {
percent: toNumber(sevenDay.utilization),
resetAt: toTimestamp(sevenDay.resets_at),
windowSeconds: WEEKLY_WINDOW_SECONDS
});
}
};
/**
* Extra usage is the paid overflow beyond plan limits. It is only meaningful
* once the account has it enabled; a disabled block would render as a permanent
* empty bar.
*/
const applyExtraUsage = (payload, windows) => {
const spend = asObject(payload.spend);
if (!spend || spend.enabled !== true) return;
const used = toAmount(spend.used);
const limit = toAmount(spend.limit);
addWindow(windows, EXTRA_USAGE_WINDOW, {
percent: toNumber(spend.percent),
resetAt: null,
valueLabel: formatSpendLabel(used, limit, asNonEmptyString(asObject(spend.used)?.currency))
});
};
/**
* @param {unknown} rawPayload Parsed JSON body of the OAuth usage endpoint.
* @returns {{ windows: Record<string, object>, models: Record<string, object> }}
*/
export const toClaudeUsage = (rawPayload) => {
const payload = asObject(rawPayload) ?? {};
const windows = {};
const models = {};
const limits = asArray(payload.limits);
if (limits.length > 0) {
applyLimitsArray(limits, windows, models);
} else {
applyLegacyFields(payload, windows);
}
applyExtraUsage(payload, windows);
return { windows, models };
};
@@ -0,0 +1,82 @@
import { describe, expect, it } from 'vitest';
import { toClaudeUsage } from './transforms.js';
// Trimmed capture of GET https://api.anthropic.com/api/oauth/usage for a Max account.
const LIVE_PAYLOAD = {
five_hour: { utilization: 5.0, resets_at: '2026-08-14T19:10:00.313090+00:00' },
seven_day: { utilization: 4.0, resets_at: '2026-08-20T15:00:00.313112+00:00' },
seven_day_opus: null,
seven_day_sonnet: null,
nimbus_quill: { utilization: 0.0, resets_at: null },
limits: [
{ kind: 'session', group: 'session', percent: 5, resets_at: '2026-08-14T19:10:00.313090+00:00', scope: null, is_active: true },
{ kind: 'weekly_all', group: 'weekly', percent: 4, resets_at: '2026-08-20T15:00:00.313112+00:00', scope: null, is_active: false },
{
kind: 'weekly_scoped',
group: 'weekly',
percent: 12,
resets_at: '2026-08-20T15:00:00.313301+00:00',
scope: { model: { id: null, display_name: 'Fable' }, surface: null },
is_active: false
}
],
spend: {
used: { amount_minor: 250, currency: 'USD', exponent: 2 },
limit: { amount_minor: 10000, currency: 'USD', exponent: 2 },
percent: 2.5,
enabled: true
}
};
describe('Claude usage transforms', () => {
it('maps the limits array to session, weekly, and model-scoped windows', () => {
const { windows, models } = toClaudeUsage(LIVE_PAYLOAD);
expect(windows['5h'].usedPercent).toBe(5);
expect(windows['5h'].resetAt).toBe(Date.parse('2026-08-14T19:10:00.313090+00:00'));
expect(windows['7d'].usedPercent).toBe(4);
expect(models.Fable.windows['7d'].usedPercent).toBe(12);
});
it('reports each window duration so callers can rank limits by urgency', () => {
const { windows, models } = toClaudeUsage(LIVE_PAYLOAD);
expect(windows['5h'].windowSeconds).toBe(5 * 60 * 60);
expect(windows['7d'].windowSeconds).toBe(7 * 24 * 60 * 60);
expect(models.Fable.windows['7d'].windowSeconds).toBe(7 * 24 * 60 * 60);
expect(windows.extra_usage.windowSeconds).toBeNull();
});
it('reports extra usage as a spend window with a money label', () => {
const { windows } = toClaudeUsage(LIVE_PAYLOAD);
expect(windows.extra_usage.usedPercent).toBe(2.5);
expect(windows.extra_usage.valueLabel).toBe('$2.50 / $100.00');
});
it('omits extra usage when the account has it disabled', () => {
const { windows } = toClaudeUsage({ ...LIVE_PAYLOAD, spend: { ...LIVE_PAYLOAD.spend, enabled: false } });
expect(windows.extra_usage).toBeUndefined();
});
it('falls back to the legacy named fields when no limits array is present', () => {
const { windows, models } = toClaudeUsage({ five_hour: LIVE_PAYLOAD.five_hour, seven_day: LIVE_PAYLOAD.seven_day });
expect(windows['5h'].usedPercent).toBe(5);
expect(windows['7d'].usedPercent).toBe(4);
expect(models).toEqual({});
});
it('ignores unknown limit kinds instead of inventing windows for them', () => {
const { windows } = toClaudeUsage({ limits: [{ kind: 'iguana_necktie', percent: 90, resets_at: null }] });
expect(windows).toEqual({});
});
it('returns empty usage for a malformed payload', () => {
expect(toClaudeUsage(null)).toEqual({ windows: {}, models: {} });
expect(toClaudeUsage({ limits: 'nope' })).toEqual({ windows: {}, models: {} });
});
});
@@ -0,0 +1,90 @@
import { readAuthFile } from '../../opencode/auth.js';
import { asObject, buildResult, getAuthEntry, normalizeAuthEntry, toNumber, toUsageWindow } from '../utils/index.js';
export const providerId = 'command-code';
export const providerName = 'Command Code';
export const aliases = ['command-code'];
const API_BASE_URL = 'https://api.commandcode.ai';
const getApiKey = (auth = readAuthFile()) => {
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const stored = entry?.key ?? entry?.access ?? entry?.token;
return (typeof stored === 'string' ? stored.trim() : '') || process.env.COMMAND_CODE_API_KEY?.trim() || null;
};
const requestJson = async (path, apiKey, fetchImpl) => {
const response = await fetchImpl(`${API_BASE_URL}${path}`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${apiKey}`,
'User-Agent': 'OpenChamber quota provider',
},
signal: AbortSignal.timeout(15_000),
});
if (response.status === 401 || response.status === 403) throw new Error('Command Code authentication failed');
if (!response.ok) throw new Error(`Command Code usage API returned HTTP ${response.status}`);
return response.json().catch(() => null);
};
const formatCredits = (value) => String(Math.round((value + Number.EPSILON) * 100) / 100);
const toBalanceWindow = (value) => toUsageWindow({
usedPercent: null,
windowSeconds: null,
resetAt: null,
valueLabel: formatCredits(value),
});
export const parseCommandCodeCredits = (payload) => {
const root = asObject(payload);
const credits = asObject(root?.credits);
const limits = asObject(root?.windowLimits);
const windows = {};
for (const [label, field] of [['monthly_credits', 'monthlyCredits'], ['purchased_credits', 'purchasedCredits'], ['free_credits', 'freeCredits']]) {
const value = toNumber(credits?.[field]);
if (value !== null) windows[label] = toBalanceWindow(value);
}
for (const [label, field, windowSeconds] of [['5h', 'fiveHour', 5 * 60 * 60], ['weekly', 'weekly', 7 * 24 * 60 * 60]]) {
const limit = asObject(limits?.[field]);
const used = toNumber(limit?.used);
const cap = toNumber(limit?.cap);
if (used === null || cap === null || cap <= 0) continue;
const resetAt = toNumber(limit?.resetAt);
windows[label] = toUsageWindow({
usedPercent: Math.min(100, Math.max(0, used / cap * 100)),
windowSeconds,
resetAt: resetAt === null ? null : resetAt < 1_000_000_000_000 ? resetAt * 1000 : resetAt,
valueLabel: `${formatCredits(used)} / ${formatCredits(cap)}`,
});
}
return windows;
};
export const fetchCommandCodeUsage = async (apiKey, fetchImpl = fetch) => {
const identity = asObject(await requestJson('/alpha/whoami', apiKey, fetchImpl));
const org = asObject(identity?.org);
const orgId = typeof org?.id === 'string' ? org.id.trim() : '';
const creditsPath = orgId
? `/alpha/billing/credits?orgId=${encodeURIComponent(orgId)}`
: '/alpha/billing/credits';
const credits = await requestJson(creditsPath, apiKey, fetchImpl);
const windows = parseCommandCodeCredits(credits);
if (Object.keys(windows).length === 0) throw new Error('Command Code usage data could not be parsed');
return windows;
};
export const isConfigured = () => Boolean(getApiKey());
export const fetchQuota = async (auth = readAuthFile()) => {
const apiKey = getApiKey(auth);
if (!apiKey) return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' });
try {
return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows: await fetchCommandCodeUsage(apiKey) } });
} catch (error) {
return buildResult({ providerId, providerName, ok: false, configured: true, error: error instanceof Error ? error.message : 'Request failed' });
}
};
@@ -0,0 +1,72 @@
import { describe, expect, it, vi } from 'vitest';
import { fetchCommandCodeUsage, fetchQuota, parseCommandCodeCredits } from './command-code.js';
const creditsPayload = {
credits: { monthlyCredits: 120, purchasedCredits: 30, freeCredits: 5 },
windowLimits: {
fiveHour: { used: 25, cap: 100, resetAt: 1_776_000_000 },
weekly: { used: 70, cap: 200, resetAt: 1_776_604_800 },
},
};
describe('Command Code quota provider', () => {
it('parses balances and rate-limit windows', () => {
const windows = parseCommandCodeCredits(creditsPayload);
expect(windows.monthly_credits).toMatchObject({ usedPercent: null, valueLabel: '120' });
expect(windows.purchased_credits).toMatchObject({ usedPercent: null, valueLabel: '30' });
expect(windows.free_credits).toMatchObject({ usedPercent: null, valueLabel: '5' });
expect(windows['5h']).toMatchObject({ usedPercent: 25, valueLabel: '25 / 100', resetAt: 1_776_000_000_000 });
expect(windows.weekly.usedPercent).toBe(35);
});
it('formats fractional credit values for display', () => {
const windows = parseCommandCodeCredits({
credits: { monthlyCredits: 69.7947070034 },
windowLimits: { fiveHour: { used: 0.2052929966, cap: 14 } },
});
expect(windows.monthly_credits.valueLabel).toBe('69.79');
expect(windows['5h'].valueLabel).toBe('0.21 / 14');
});
it('resolves the organization before fetching credits', async () => {
const requests = [];
const windows = await fetchCommandCodeUsage('secret', async (url, options) => {
requests.push({ url, options });
return new Response(JSON.stringify(url.endsWith('/alpha/whoami') ? { org: { id: 'org/a' } } : creditsPayload));
});
expect(requests.map(({ url }) => url)).toEqual([
'https://api.commandcode.ai/alpha/whoami',
'https://api.commandcode.ai/alpha/billing/credits?orgId=org%2Fa',
]);
expect(requests[0].options.headers.Authorization).toBe('Bearer secret');
expect(windows['5h'].usedPercent).toBe(25);
});
it('fetches account-scoped credits without orgId for personal accounts', async () => {
const urls = [];
await fetchCommandCodeUsage('secret', async (url) => {
urls.push(url);
return new Response(JSON.stringify(url.endsWith('/alpha/whoami') ? { user: { id: 'user-1' }, org: null } : creditsPayload));
});
expect(urls).toEqual([
'https://api.commandcode.ai/alpha/whoami',
'https://api.commandcode.ai/alpha/billing/credits',
]);
});
it('does not expose credentials in authentication errors', async () => {
await expect(fetchCommandCodeUsage('secret', async () => new Response('', { status: 401 }))).rejects.toThrow('authentication failed');
});
it('reads OAuth access credentials from the OpenCode auth file', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(new Response(JSON.stringify({ org: { id: 'org-1' } })))
.mockResolvedValueOnce(new Response(JSON.stringify(creditsPayload)));
vi.stubGlobal('fetch', fetchMock);
const result = await fetchQuota({ 'command-code': { type: 'oauth', access: 'test-token' } });
expect(result).toMatchObject({ providerId: 'command-code', ok: true, configured: true });
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer test-token');
vi.unstubAllGlobals();
});
});
@@ -7,8 +7,9 @@
import { buildResult } from '../utils/index.js';
import * as claude from './claude.js';
import * as claude from './claude/index.js';
import * as codex from './codex.js';
import * as commandCode from './command-code.js';
import * as copilot from './copilot.js';
import * as crof from './crof.js';
import * as cursor from './cursor.js';
@@ -29,6 +30,12 @@ import * as opencodeGo from './opencode-go.js';
import * as xai from './xai.js';
const registry = {
'command-code': {
providerId: commandCode.providerId,
providerName: commandCode.providerName,
isConfigured: commandCode.isConfigured,
fetchQuota: commandCode.fetchQuota
},
claude: {
providerId: claude.providerId,
providerName: claude.providerName,
@@ -151,6 +158,8 @@ const registry = {
}
};
const pendingFetches = new Map();
export const listConfiguredQuotaProviders = () => {
const configured = [];
@@ -167,7 +176,7 @@ export const listConfiguredQuotaProviders = () => {
return configured;
};
export const fetchQuotaForProvider = async (providerId) => {
const fetchQuotaForProviderUncoalesced = async (providerId) => {
const provider = registry[providerId];
if (!provider) {
@@ -193,6 +202,17 @@ export const fetchQuotaForProvider = async (providerId) => {
}
};
export const fetchQuotaForProvider = (providerId) => {
const existing = pendingFetches.get(providerId);
if (existing) return existing;
const pending = fetchQuotaForProviderUncoalesced(providerId).finally(() => {
if (pendingFetches.get(providerId) === pending) pendingFetches.delete(providerId);
});
pendingFetches.set(providerId, pending);
return pending;
};
export const fetchClaudeQuota = claude.fetchQuota;
export const fetchOpenaiQuota = openai.fetchQuota;
export const fetchGoogleQuota = google.fetchGoogleQuota;
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import * as google from './google/index.js';
import { listConfiguredQuotaProviders } from './index.js';
import { fetchQuotaForProvider, listConfiguredQuotaProviders } from './index.js';
describe('quota provider registry', () => {
it('exposes google provider configuration helpers through the provider module', () => {
@@ -14,4 +14,13 @@ describe('quota provider registry', () => {
it('can list configured providers without missing provider exports', () => {
expect(() => listConfiguredQuotaProviders()).not.toThrow();
});
it('coalesces concurrent refreshes by provider ID', async () => {
const first = fetchQuotaForProvider('unsupported-test-provider');
const second = fetchQuotaForProvider('unsupported-test-provider');
expect(first).toBe(second);
await first;
expect(fetchQuotaForProvider('unsupported-test-provider')).not.toBe(first);
});
});
+26 -6
View File
@@ -4,6 +4,7 @@ import {
normalizeAuthEntry,
buildResult,
toUsageWindow,
toNumber,
resolveWindowSeconds,
resolveWindowLabel,
normalizeTimestamp
@@ -13,6 +14,20 @@ export const providerId = 'zai-coding-plan';
export const providerName = 'z.ai';
const aliases = ['zai-coding-plan', 'zai', 'z.ai'];
// CREDIT_LIMIT entries carry `usage` (total credits), `currentValue` (consumed),
// and `remaining`; TOKENS_LIMIT entries only carry a percentage.
const formatCreditAmount = (value) => {
if (value < 1000) return value.toLocaleString('en-US');
return `${Math.round(value / 100) / 10}k`;
};
const formatCreditValueLabel = (limit) => {
const used = toNumber(limit?.currentValue);
const total = toNumber(limit?.usage);
if (used === null || total === null) return null;
return `${formatCreditAmount(used)} / ${formatCreditAmount(total)} credits`;
};
export const isConfigured = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
@@ -56,16 +71,20 @@ export const fetchQuota = async () => {
const payload = await response.json();
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
const windows = {};
for (const tokensLimit of limits.filter((limit) => limit?.type === 'TOKENS_LIMIT')) {
const windowSeconds = resolveWindowSeconds(tokensLimit);
// The API renamed TOKENS_LIMIT to CREDIT_LIMIT; field semantics stayed the same,
// so both limit types map to the same windows.
for (const limit of limits.filter((entry) => entry?.type === 'TOKENS_LIMIT' || entry?.type === 'CREDIT_LIMIT')) {
const windowSeconds = resolveWindowSeconds(limit);
const windowLabel = resolveWindowLabel(windowSeconds);
const resetAt = tokensLimit?.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
const usedPercent = typeof tokensLimit?.percentage === 'number' ? tokensLimit.percentage : null;
const resetAt = limit?.nextResetTime ? normalizeTimestamp(limit.nextResetTime) : null;
const usedPercent = typeof limit?.percentage === 'number' ? limit.percentage : null;
const creditValueLabel = formatCreditValueLabel(limit);
windows[windowLabel] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt
resetAt,
valueLabel: creditValueLabel
});
}
@@ -83,7 +102,8 @@ export const fetchQuota = async () => {
providerName,
ok: true,
configured: true,
usage: { windows }
usage: { windows },
planLabel: typeof payload?.data?.level === 'string' && payload.data.level ? payload.data.level : null
});
} catch (error) {
return buildResult({
@@ -51,4 +51,37 @@ describe('Z.ai quota provider', () => {
resetAt: 1787128459979,
});
});
it('maps CREDIT_LIMIT entries to windows with credit value labels and plan level', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse({
code: 200,
data: {
limits: [
{ type: 'CREDIT_LIMIT', unit: 3, number: 5, usage: 12000, currentValue: 65, remaining: 11934, percentage: 1, nextResetTime: 1787257978907 },
{ type: 'CREDIT_LIMIT', unit: 6, number: 1, usage: 60000, currentValue: 65, remaining: 59934, percentage: 1, nextResetTime: 1787844668997 },
],
level: 'pro',
},
})));
const result = await fetchQuota();
const windows = result.usage.windows;
expect(result.ok).toBe(true);
expect(result.planLabel).toBe('pro');
expect(windows['5h']).toMatchObject({
usedPercent: 1,
remainingPercent: 99,
windowSeconds: 5 * 60 * 60,
resetAt: 1787257978907,
valueLabel: '65 / 12k credits',
});
expect(windows.weekly).toMatchObject({
usedPercent: 1,
remainingPercent: 99,
windowSeconds: 7 * 24 * 60 * 60,
resetAt: 1787844668997,
valueLabel: '65 / 60k credits',
});
});
});
@@ -53,13 +53,14 @@ export const toUsageWindow = ({ usedPercent, windowSeconds, resetAt, valueLabel
};
};
export const buildResult = ({ providerId, providerName, ok, configured, usage, error }) => ({
export const buildResult = ({ providerId, providerName, ok, configured, usage, error, planLabel }) => ({
providerId,
providerName,
ok,
configured,
usage: usage ?? null,
...(error ? { error } : {}),
...(planLabel ? { planLabel } : {}),
fetchedAt: Date.now()
});