diff --git a/pi/files/agent/extensions/sub-bar-local.ts b/pi/files/agent/extensions/sub-bar-local.ts index a16c394..e4c3eaa 100644 --- a/pi/files/agent/extensions/sub-bar-local.ts +++ b/pi/files/agent/extensions/sub-bar-local.ts @@ -46,7 +46,8 @@ interface PiAuthShape { } const CACHE_TTL_MS = 5 * 60 * 1000; -const REFRESH_MS = 2 * 60 * 1000; +const ANTHROPIC_CACHE_TTL_MS = 30 * 60 * 1000; +const REFRESH_MS = 5 * 60 * 1000; const PROVIDER_ORDER: ProviderName[] = ["anthropic", "codex", "gemini", "opencode-go"]; const SHORTCUT_TOGGLE = "ctrl+alt+b"; const showToggleState = async (ctx: ExtensionContext, next: boolean, refresh: () => Promise) => { @@ -200,7 +201,12 @@ function buildColumnWidths(totalWidth: number, columns: number): number[] { return widths; } -function formatUsageTwoLines(theme: Theme, usage: UsageSnapshot, width: number): { top: string; bottom?: string } { +function formatUsageTwoLines( + theme: Theme, + usage: UsageSnapshot, + width: number, + statusNote?: string, +): { top: string; bottom?: string } { const provider = theme.bold(theme.fg("accent", usage.displayName)); if (usage.error && usage.windows.length === 0) { const age = getErrorAge(usage.lastSuccessAt); @@ -244,7 +250,11 @@ function formatUsageTwoLines(theme: Theme, usage: UsageSnapshot, width: number): return barForPercent(theme, pct, colWidth); }); - const top = prefix + topCols.join(gap); + let top = prefix + topCols.join(gap); + if (statusNote) { + const note = theme.fg("warning", ` ⚠ ${statusNote}`); + top = truncateToWidth(top + note, width); + } const bottomPrefix = " ".repeat(prefixWidth); const bottom = bottomPrefix + bottomCols.join(gap); @@ -459,6 +469,7 @@ export default function createSubBarLocal(pi: ExtensionAPI) { let activeProvider: ProviderName | "auto" = "auto"; let widgetEnabled = true; let refreshTimer: NodeJS.Timeout | undefined; + let anthropicRetryAfter = 0; const cache: Partial> = {}; function getSelectedProvider(ctx?: ExtensionContext): ProviderName | undefined { @@ -487,10 +498,24 @@ export default function createSubBarLocal(pi: ExtensionAPI) { const safeWidth = Math.max(1, width); const topDivider = theme.fg("dim", "─".repeat(safeWidth)); if (!snapshot) { - const loading = truncateToWidth(theme.fg("dim", `sub bar loading ${provider} usage...`), safeWidth); + const cooldown = provider === "anthropic" && anthropicRetryAfter > Date.now() + ? formatDuration(Math.max(1, Math.floor((anthropicRetryAfter - Date.now()) / 1000))) + : undefined; + const text = cooldown + ? `anthropic usage limited, retry in ${cooldown}` + : `sub bar loading ${provider} usage...`; + const loading = truncateToWidth(theme.fg("dim", text), safeWidth); return [topDivider, padToWidth(loading, safeWidth)]; } - const lines = formatUsageTwoLines(theme, snapshot, safeWidth); + const cooldown = provider === "anthropic" && anthropicRetryAfter > Date.now() + ? formatDuration(Math.max(1, Math.floor((anthropicRetryAfter - Date.now()) / 1000))) + : undefined; + const statusNote = snapshot.error + ? snapshot.error + : cooldown + ? `usage endpoint limited, retry in ${cooldown}` + : undefined; + const lines = formatUsageTwoLines(theme, snapshot, safeWidth, statusNote); const output = [topDivider, lines.top]; if (lines.bottom) output.push(lines.bottom); return output; @@ -507,14 +532,22 @@ export default function createSubBarLocal(pi: ExtensionAPI) { ctx.ui.setWidget("sub-bar-local", undefined); return; } + if (provider === "anthropic" && anthropicRetryAfter > Date.now()) { + render(ctx); + return; + } const cached = cache[provider]?.usage; - if (!force && cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) { + const ttl = provider === "anthropic" ? ANTHROPIC_CACHE_TTL_MS : CACHE_TTL_MS; + if (!force && cached && Date.now() - cached.fetchedAt < ttl) { render(ctx); return; } try { const fresh = await fetchUsage(provider); + if (provider === "anthropic") { + anthropicRetryAfter = 0; + } cache[provider] = { usage: { ...fresh, @@ -526,18 +559,37 @@ export default function createSubBarLocal(pi: ExtensionAPI) { } catch (error) { const message = error instanceof Error ? error.message : "fetch failed"; const fallback = cache[provider]?.usage; - cache[provider] = { - usage: { - provider, - displayName: fallback?.displayName ?? provider, - windows: fallback?.windows ?? [], - error: message, - fetchedAt: Date.now(), + const isAnthropic429 = provider === "anthropic" && message.includes("429"); + + if (isAnthropic429) { + anthropicRetryAfter = Date.now() + 30 * 60 * 1000; + } + + if (isAnthropic429 && fallback) { + cache[provider] = { + usage: { + ...fallback, + fetchedAt: Date.now(), + fromCache: true, + }, lastSuccessAt: cache[provider]?.lastSuccessAt, - fromCache: Boolean(fallback), - }, - lastSuccessAt: cache[provider]?.lastSuccessAt, - }; + }; + } else if (isAnthropic429) { + delete cache[provider]; + } else { + cache[provider] = { + usage: { + provider, + displayName: fallback?.displayName ?? provider, + windows: fallback?.windows ?? [], + error: message, + fetchedAt: Date.now(), + lastSuccessAt: cache[provider]?.lastSuccessAt, + fromCache: Boolean(fallback), + }, + lastSuccessAt: cache[provider]?.lastSuccessAt, + }; + } } render(ctx); @@ -608,7 +660,11 @@ export default function createSubBarLocal(pi: ExtensionAPI) { if (!ctx.hasUI) return; render(ctx); if (activeProvider === "auto") { - await refreshCurrent(ctx, true); + if (getSelectedProvider(ctx) === "anthropic") { + render(ctx); + } else { + await refreshCurrent(ctx, false); + } } });