The opencode-claude integration registers its provider as `claude-code`, which never matched the `claude` quota provider, so the collapsed Usage section in the work status panel showed no limit for a model from that integration. Claude windows also reported no duration, leaving the headline to fall back to whichever row came first instead of the limit that runs out soonest. The session and weekly windows now carry their length; extra usage stays without one because it is a monthly spend cap.
128 lines
4.5 KiB
JavaScript
128 lines
4.5 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';
|
|
|
|
// 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 };
|
|
};
|