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.
132 lines
3.9 KiB
JavaScript
132 lines
3.9 KiB
JavaScript
/**
|
|
* 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;
|
|
|
|
const fingerprintOf = (credential) =>
|
|
createHash('sha256').update(`${credential.accessToken} |