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.
This commit is contained in:
@@ -162,6 +162,11 @@ export const UsagePage: React.FC = () => {
|
||||
description={
|
||||
isLoading ? (
|
||||
<span className="animate-pulse typography-settings-description text-muted-foreground">{t('settings.usage.page.header.refreshing')}</span>
|
||||
) : selectedResult?.planLabel ? (
|
||||
t('settings.usage.page.header.lastUpdatedWithPlan', {
|
||||
plan: selectedResult.planLabel,
|
||||
time: formatTime(lastUpdated, timeFormatPreference),
|
||||
})
|
||||
) : (
|
||||
t('settings.usage.page.header.lastUpdated', { time: formatTime(lastUpdated, timeFormatPreference) })
|
||||
)
|
||||
|
||||
@@ -1131,6 +1131,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} Nutzung',
|
||||
'settings.usage.page.header.refreshing': 'Aktualisiere Nutzung...',
|
||||
'settings.usage.page.header.lastUpdated': 'Zuletzt aktualisiert: {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': 'Tarif: {plan} · Zuletzt aktualisiert: {time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': 'Im Arbeitsstatusbereich anzeigen',
|
||||
'settings.usage.page.options.showInWorkStatus': 'Im Arbeitsstatusbereich anzeigen',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': 'Wenn aktiviert, ist die Nutzung dieses Anbieters im Arbeitsstatusbereich sichtbar.',
|
||||
|
||||
@@ -2804,8 +2804,7 @@ export const dict = {
|
||||
'contextFileOpen.failure.unreadable': 'Fehler beim Öffnen der Datei',
|
||||
'quota.window.5h': '5-Stunden-Limit',
|
||||
'quota.window.7d': '7-Tage-Limit',
|
||||
'quota.window.7dSonnet': '7-Tage Sonnet-Limit',
|
||||
'quota.window.7dOpus': '7-Tage Opus-Limit',
|
||||
'quota.window.extraUsage': 'Zusätzliche Nutzung',
|
||||
'quota.window.weekly': 'Wöchentliches Limit',
|
||||
'quota.window.daily': 'Täglich',
|
||||
'quota.window.monthly': 'Monatliches Limit',
|
||||
|
||||
@@ -1193,6 +1193,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} Usage',
|
||||
'settings.usage.page.header.refreshing': 'Refreshing usage...',
|
||||
'settings.usage.page.header.lastUpdated': 'Last updated: {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': 'Plan: {plan} · Last updated: {time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': 'Show in work status panel',
|
||||
'settings.usage.page.options.showInWorkStatus': 'Show in Work Status Panel',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': 'When enabled, this provider\'s usage will be visible in the work status panel.',
|
||||
|
||||
@@ -2982,8 +2982,7 @@ export const dict = {
|
||||
'contextFileOpen.failure.unreadable': 'Failed to open file',
|
||||
'quota.window.5h': '5-Hour',
|
||||
'quota.window.7d': '7-Day Limit',
|
||||
'quota.window.7dSonnet': '7-Day Sonnet Limit',
|
||||
'quota.window.7dOpus': '7-Day Opus Limit',
|
||||
'quota.window.extraUsage': 'Extra Usage',
|
||||
'quota.window.weekly': 'Weekly Limit',
|
||||
'quota.window.daily': 'Daily',
|
||||
'quota.window.monthly': 'Monthly Limit',
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const settingsDict = {
|
||||
"settings.usage.page.header.providerUsage": "Uso de {provider}",
|
||||
"settings.usage.page.header.refreshing": "Actualizando uso...",
|
||||
"settings.usage.page.header.lastUpdated": "Última actualización: {time}",
|
||||
"settings.usage.page.header.lastUpdatedWithPlan": "Plan: {plan} · Última actualización: {time}",
|
||||
"settings.usage.page.options.showInWorkStatusAria": "Mostrar en el panel de estado del trabajo",
|
||||
"settings.usage.page.options.showInWorkStatus": "Mostrar en el panel de estado del trabajo",
|
||||
"settings.usage.page.options.showInWorkStatusTooltip": "Cuando esté habilitado, el uso de este proveedor será visible en el panel de estado del trabajo.",
|
||||
|
||||
@@ -2983,8 +2983,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextFileOpen.failure.unreadable": "Failed to open file",
|
||||
"quota.window.5h": "5-Hour",
|
||||
"quota.window.7d": "7-Day Limit",
|
||||
"quota.window.7dSonnet": "7-Day Sonnet Limit",
|
||||
"quota.window.7dOpus": "7-Day Opus Limit",
|
||||
"quota.window.extraUsage": "Uso adicional",
|
||||
"quota.window.weekly": "Weekly Limit",
|
||||
"quota.window.daily": "Daily",
|
||||
"quota.window.monthly": "Monthly Limit",
|
||||
|
||||
@@ -1079,6 +1079,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': 'Utilisation de {provider}',
|
||||
'settings.usage.page.header.refreshing': 'Utilisation rafraîchissante...',
|
||||
'settings.usage.page.header.lastUpdated': 'Dernière mise à jour : {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': 'Forfait : {plan} · Dernière mise à jour : {time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': 'Afficher dans le panneau d’état du travail',
|
||||
'settings.usage.page.options.showInWorkStatus': 'Afficher dans le panneau d’état du travail',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': 'Lorsqu’elle est activée, l’utilisation de ce fournisseur sera visible dans le panneau d’état du travail.',
|
||||
|
||||
@@ -2675,8 +2675,7 @@ export const dict = {
|
||||
'contextFileOpen.failure.unreadable': 'Impossible d’ouvrir le fichier',
|
||||
'quota.window.5h': '5 heures',
|
||||
'quota.window.7d': 'Limite sur 7 jours',
|
||||
'quota.window.7dSonnet': 'Limite Sonnet sur 7 jours',
|
||||
'quota.window.7dOpus': 'Limite Opus sur 7 jours',
|
||||
'quota.window.extraUsage': 'Utilisation supplémentaire',
|
||||
'quota.window.weekly': 'Limite hebdomadaire',
|
||||
'quota.window.daily': 'Quotidien',
|
||||
'quota.window.monthly': 'Limite mensuelle',
|
||||
|
||||
@@ -1194,6 +1194,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} の使用量',
|
||||
'settings.usage.page.header.refreshing': '使用量を更新中...',
|
||||
'settings.usage.page.header.lastUpdated': '最終更新: {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': 'プラン: {plan} · 最終更新: {time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': '作業ステータスパネルに表示',
|
||||
'settings.usage.page.options.showInWorkStatus': '作業ステータスパネルに表示',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': '有効にすると、このプロバイダーの使用量が作業ステータスパネルに表示されます。',
|
||||
|
||||
@@ -2978,8 +2978,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextFileOpen.failure.unreadable': 'ファイルを開けませんでした',
|
||||
'quota.window.5h': '5時間',
|
||||
'quota.window.7d': '7日間制限',
|
||||
'quota.window.7dSonnet': '7日間Sonnet制限',
|
||||
'quota.window.7dOpus': '7日間Opus制限',
|
||||
'quota.window.extraUsage': '追加利用',
|
||||
'quota.window.weekly': '週間制限',
|
||||
'quota.window.daily': '日次',
|
||||
'quota.window.monthly': '月間制限',
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} 사용량',
|
||||
'settings.usage.page.header.refreshing': '사용량 새로고침 중...',
|
||||
'settings.usage.page.header.lastUpdated': '마지막 업데이트: {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': '플랜: {plan} · 마지막 업데이트: {time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': '작업 상태 패널에 표시',
|
||||
'settings.usage.page.options.showInWorkStatus': '작업 상태 패널에 표시',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': '활성화하면 이 제공업체의 사용량이 작업 상태 패널에 표시됩니다.',
|
||||
|
||||
@@ -2982,8 +2982,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextFileOpen.failure.unreadable': 'Failed to open file',
|
||||
'quota.window.5h': '5-Hour',
|
||||
'quota.window.7d': '7-Day Limit',
|
||||
'quota.window.7dSonnet': '7-Day Sonnet Limit',
|
||||
'quota.window.7dOpus': '7-Day Opus Limit',
|
||||
'quota.window.extraUsage': '추가 사용량',
|
||||
'quota.window.weekly': 'Weekly Limit',
|
||||
'quota.window.daily': 'Daily',
|
||||
'quota.window.monthly': 'Monthly Limit',
|
||||
|
||||
@@ -1963,6 +1963,7 @@ export const settingsDict = {
|
||||
'settings.usage.pace.waitSeparator': ' · Czekaj ',
|
||||
'settings.usage.page.empty.selectProvider': 'Wybierz dostawcę, aby wyświetlić szczegóły użycia.',
|
||||
'settings.usage.page.header.lastUpdated': 'Ostatnio aktualizowano: {time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': 'Plan: {plan} · Ostatnia aktualizacja: {time}',
|
||||
'settings.usage.page.header.providerUsage': 'Użycie {provider}',
|
||||
'settings.usage.page.header.refreshing': 'Odświeżanie użycia...',
|
||||
'settings.usage.page.options.showInWorkStatus': 'Pokaż w panelu statusu pracy',
|
||||
|
||||
@@ -2999,8 +2999,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextFileOpen.failure.unreadable': 'Failed to open file',
|
||||
'quota.window.5h': '5-Hour',
|
||||
'quota.window.7d': '7-Day Limit',
|
||||
'quota.window.7dSonnet': '7-Day Sonnet Limit',
|
||||
'quota.window.7dOpus': '7-Day Opus Limit',
|
||||
'quota.window.extraUsage': 'Dodatkowe zużycie',
|
||||
'quota.window.weekly': 'Weekly Limit',
|
||||
'quota.window.daily': 'Daily',
|
||||
'quota.window.monthly': 'Monthly Limit',
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const settingsDict = {
|
||||
"settings.usage.page.header.providerUsage": "Uso de {provider}",
|
||||
"settings.usage.page.header.refreshing": "Atualizando uso...",
|
||||
"settings.usage.page.header.lastUpdated": "Última atualização: {time}",
|
||||
"settings.usage.page.header.lastUpdatedWithPlan": "Plano: {plan} · Última atualização: {time}",
|
||||
"settings.usage.page.options.showInWorkStatusAria": "Mostrar no painel de status do trabalho",
|
||||
"settings.usage.page.options.showInWorkStatus": "Mostrar no painel de status do trabalho",
|
||||
"settings.usage.page.options.showInWorkStatusTooltip": "Quando habilitado, o uso deste provedor ficará visível no painel de status do trabalho.",
|
||||
|
||||
@@ -2983,8 +2983,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextFileOpen.failure.unreadable": "Failed to open file",
|
||||
"quota.window.5h": "5-Hour",
|
||||
"quota.window.7d": "7-Day Limit",
|
||||
"quota.window.7dSonnet": "7-Day Sonnet Limit",
|
||||
"quota.window.7dOpus": "7-Day Opus Limit",
|
||||
"quota.window.extraUsage": "Uso adicional",
|
||||
"quota.window.weekly": "Weekly Limit",
|
||||
"quota.window.daily": "Daily",
|
||||
"quota.window.monthly": "Monthly Limit",
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const settingsDict = {
|
||||
"settings.usage.page.header.providerUsage": "Використання {provider}",
|
||||
"settings.usage.page.header.refreshing": "Оновлення використання...",
|
||||
"settings.usage.page.header.lastUpdated": "Останнє оновлення: {time}",
|
||||
"settings.usage.page.header.lastUpdatedWithPlan": "План: {plan} · Останнє оновлення: {time}",
|
||||
"settings.usage.page.options.showInWorkStatusAria": "Показувати в панелі статусу роботи",
|
||||
"settings.usage.page.options.showInWorkStatus": "Показувати в панелі статусу роботи",
|
||||
"settings.usage.page.options.showInWorkStatusTooltip": "Якщо ввімкнути, використання цього провайдера буде видно в панелі статусу роботи.",
|
||||
|
||||
@@ -2983,8 +2983,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextFileOpen.failure.unreadable": "Failed to open file",
|
||||
"quota.window.5h": "5-Hour",
|
||||
"quota.window.7d": "7-Day Limit",
|
||||
"quota.window.7dSonnet": "7-Day Sonnet Limit",
|
||||
"quota.window.7dOpus": "7-Day Opus Limit",
|
||||
"quota.window.extraUsage": "Додаткове використання",
|
||||
"quota.window.weekly": "Weekly Limit",
|
||||
"quota.window.daily": "Daily",
|
||||
"quota.window.monthly": "Monthly Limit",
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} 用量',
|
||||
'settings.usage.page.header.refreshing': '正在刷新用量...',
|
||||
'settings.usage.page.header.lastUpdated': '最后更新:{time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': '套餐:{plan} · 最后更新:{time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': '在工作状态面板中显示',
|
||||
'settings.usage.page.options.showInWorkStatus': '在工作状态面板中显示',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': '启用后,该提供商的用量会显示在工作状态面板中。',
|
||||
|
||||
@@ -2983,8 +2983,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextFileOpen.failure.unreadable': 'Failed to open file',
|
||||
'quota.window.5h': '5-Hour',
|
||||
'quota.window.7d': '7-Day Limit',
|
||||
'quota.window.7dSonnet': '7-Day Sonnet Limit',
|
||||
'quota.window.7dOpus': '7-Day Opus Limit',
|
||||
'quota.window.extraUsage': '额外用量',
|
||||
'quota.window.weekly': 'Weekly Limit',
|
||||
'quota.window.daily': 'Daily',
|
||||
'quota.window.monthly': 'Monthly Limit',
|
||||
|
||||
@@ -1068,6 +1068,7 @@ export const settingsDict = {
|
||||
'settings.usage.page.header.providerUsage': '{provider} 用量',
|
||||
'settings.usage.page.header.refreshing': '正在重新整理用量...',
|
||||
'settings.usage.page.header.lastUpdated': '最後更新:{time}',
|
||||
'settings.usage.page.header.lastUpdatedWithPlan': '方案:{plan} · 最後更新:{time}',
|
||||
'settings.usage.page.options.showInWorkStatusAria': '在工作狀態面板中顯示',
|
||||
'settings.usage.page.options.showInWorkStatus': '在工作狀態面板中顯示',
|
||||
'settings.usage.page.options.showInWorkStatusTooltip': '啟用後,該供應商的用量會顯示在工作狀態面板中。',
|
||||
|
||||
@@ -2982,8 +2982,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextFileOpen.failure.unreadable': 'Failed to open file',
|
||||
'quota.window.5h': '5-Hour',
|
||||
'quota.window.7d': '7-Day Limit',
|
||||
'quota.window.7dSonnet': '7-Day Sonnet Limit',
|
||||
'quota.window.7dOpus': '7-Day Opus Limit',
|
||||
'quota.window.extraUsage': '額外用量',
|
||||
'quota.window.weekly': 'Weekly Limit',
|
||||
'quota.window.daily': 'Daily',
|
||||
'quota.window.monthly': 'Monthly Limit',
|
||||
|
||||
@@ -113,12 +113,16 @@ export function groupModelsByFamilyWithGetter<T>(
|
||||
* For Google provider with gemini/ and antigravity/ prefixes:
|
||||
* - Gemini 3.x models
|
||||
* - All Claude models
|
||||
* For the Claude provider: every model it reports a limit for.
|
||||
*/
|
||||
export function getDefaultModels(
|
||||
providerId: QuotaProviderId,
|
||||
availableModels: string[]
|
||||
): string[] {
|
||||
return availableModels.filter((model) => {
|
||||
// Anthropic only reports a model here when that model has its own plan
|
||||
// limit, so every one it names is worth showing by default.
|
||||
if (providerId === 'claude') return true;
|
||||
const lower = model.toLowerCase();
|
||||
// Handle gemini/ and antigravity/ prefixes
|
||||
const modelName = lower.includes('/') ? lower.split('/')[1] : lower;
|
||||
|
||||
@@ -75,8 +75,7 @@ export const resolveUsageTone = (percent: number | null): 'safe' | 'warn' | 'cri
|
||||
export const formatWindowLabel = (label: string): string => {
|
||||
if (label === '5h') return t('quota.window.5h');
|
||||
if (label === '7d') return t('quota.window.7d');
|
||||
if (label === '7d-sonnet') return t('quota.window.7dSonnet');
|
||||
if (label === '7d-opus') return t('quota.window.7dOpus');
|
||||
if (label === 'extra_usage') return t('quota.window.extraUsage');
|
||||
if (label === 'weekly') return t('quota.window.weekly');
|
||||
if (label === 'daily') return t('quota.window.daily');
|
||||
if (label === 'monthly') return t('quota.window.monthly');
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface ProviderResult {
|
||||
ok: boolean;
|
||||
configured: boolean;
|
||||
error?: string;
|
||||
/** Subscription tier reported by the provider, when it exposes one. */
|
||||
planLabel?: string | null;
|
||||
usage: ProviderUsage | null;
|
||||
fetchedAt: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user