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.
87 lines
2.6 KiB
JavaScript
87 lines
2.6 KiB
JavaScript
export const formatResetTime = (timestamp) => {
|
|
try {
|
|
const resetDate = new Date(timestamp);
|
|
if (!Number.isFinite(resetDate.getTime())) {
|
|
return null;
|
|
}
|
|
|
|
const now = new Date();
|
|
const isToday = resetDate.toDateString() === now.toDateString();
|
|
|
|
if (isToday) {
|
|
return resetDate.toLocaleTimeString(undefined, {
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
return resetDate.toLocaleString(undefined, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
weekday: 'short',
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
});
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const hasResetTimestamp = (resetAt) => resetAt !== null && resetAt !== undefined && resetAt !== '';
|
|
|
|
export const calculateResetAfterSeconds = (resetAt) => {
|
|
if (!hasResetTimestamp(resetAt)) return null;
|
|
const resetAtTime = new Date(resetAt).getTime();
|
|
if (!Number.isFinite(resetAtTime)) return null;
|
|
const delta = Math.floor((resetAtTime - Date.now()) / 1000);
|
|
return delta < 0 ? 0 : delta;
|
|
};
|
|
|
|
export const toUsageWindow = ({ usedPercent, windowSeconds, resetAt, valueLabel }) => {
|
|
const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
|
|
const resetFormatted = hasResetTimestamp(resetAt) ? formatResetTime(resetAt) : null;
|
|
const hasFiniteUsedPercent = typeof usedPercent === 'number' && Number.isFinite(usedPercent);
|
|
return {
|
|
usedPercent,
|
|
remainingPercent: hasFiniteUsedPercent ? Math.max(0, 100 - usedPercent) : null,
|
|
windowSeconds: windowSeconds ?? null,
|
|
resetAfterSeconds,
|
|
resetAt,
|
|
resetAtFormatted: resetFormatted,
|
|
resetAfterFormatted: resetFormatted,
|
|
...(valueLabel ? { valueLabel } : {})
|
|
};
|
|
};
|
|
|
|
export const buildResult = ({ providerId, providerName, ok, configured, usage, error, planLabel }) => ({
|
|
providerId,
|
|
providerName,
|
|
ok,
|
|
configured,
|
|
usage: usage ?? null,
|
|
...(error ? { error } : {}),
|
|
...(planLabel ? { planLabel } : {}),
|
|
fetchedAt: Date.now()
|
|
});
|
|
|
|
export const durationToLabel = (duration, unit) => {
|
|
if (!duration || !unit) return 'limit';
|
|
if (unit === 'TIME_UNIT_MINUTE') return `${duration}m`;
|
|
if (unit === 'TIME_UNIT_HOUR') return `${duration}h`;
|
|
if (unit === 'TIME_UNIT_DAY') return `${duration}d`;
|
|
return 'limit';
|
|
};
|
|
|
|
export const durationToSeconds = (duration, unit) => {
|
|
if (!duration || !unit) return null;
|
|
if (unit === 'TIME_UNIT_MINUTE') return duration * 60;
|
|
if (unit === 'TIME_UNIT_HOUR') return duration * 3600;
|
|
if (unit === 'TIME_UNIT_DAY') return duration * 86400;
|
|
return null;
|
|
};
|
|
|
|
export const formatMoney = (value) => {
|
|
if (typeof value !== 'number' || !Number.isFinite(value)) return null;
|
|
return value.toFixed(2);
|
|
};
|