Files
openchamber/packages/web/server/lib/quota/providers/claude/transforms.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

121 lines
4.1 KiB
JavaScript

/**
* 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';
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 }) => {
if (percent === null && !valueLabel) return;
target[key] = toUsageWindow({ usedPercent: percent, windowSeconds: null, 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 });
continue;
}
if (limit.kind === 'weekly_all') {
addWindow(windows, WEEKLY_WINDOW, { percent, resetAt });
continue;
}
if (limit.kind === 'weekly_scoped' && modelName) {
const modelWindows = {};
addWindow(modelWindows, WEEKLY_WINDOW, { percent, resetAt });
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)
});
}
if (sevenDay) {
addWindow(windows, WEEKLY_WINDOW, {
percent: toNumber(sevenDay.utilization),
resetAt: toTimestamp(sevenDay.resets_at)
});
}
};
/**
* 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 };
};