From 036e36258457ab47708880f07031e8b2c3cca8a7 Mon Sep 17 00:00:00 2001 From: Tomasz Zajac Date: Sat, 12 Sep 2026 19:54:41 +0200 Subject: [PATCH] fix: token counter undercounted cached Anthropic requests; Forge clarify UI polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The token counter silently dropped cache_read_input_tokens and cache_creation_input_tokens from Anthropic's usage response, counting only the fresh/non-cached portion as `inputTokens` — since the recent caching optimizations mean nearly every request now involves a cache read or write, the displayed total badly undercounted real spend. Folds both into `inputTokens`, matching how OpenAI's `prompt_tokens` already reports the full total with cached tokens as a subset. Also fixes two UI issues in the Radical Forge wizard: the absolutely- positioned close button overlapped the token-count badge once it grew wide (e.g. "25.9K tokens"), and the clarify form's submit button was labeled "Continue" — the same word as the footer's "Continue →" button, which does something different (advances the wizard step vs. confirms clarify answers for the current stage). Co-Authored-By: Claude Sonnet 5 --- src/renderer/src/ai/providers/claude.ts | 22 ++++++++++++++++--- .../src/components/RadicalForgeModal.tsx | 7 +++++- src/renderer/src/index.css | 4 ++++ tests/aiProviders.test.ts | 15 +++++++++++-- tests/aiRunner.test.ts | 12 ++++++---- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/ai/providers/claude.ts b/src/renderer/src/ai/providers/claude.ts index deed4b0..c9aa54a 100644 --- a/src/renderer/src/ai/providers/claude.ts +++ b/src/renderer/src/ai/providers/claude.ts @@ -87,10 +87,26 @@ function splitSystem(messages: ChatMessage[]): { system: AnthropicSystemBlock[]; return { system, rest } } -function parseUsage(usage: { input_tokens?: number; output_tokens?: number; cache_read_input_tokens?: number } | undefined): TokenUsage | undefined { +/** Anthropic's `input_tokens` is deliberately ONLY the fresh, non-cached + * portion of the prompt — a cache hit/write moves those tokens into + * `cache_read_input_tokens`/`cache_creation_input_tokens` instead, so a + * well-cached request can report `input_tokens: 21` even for a 10K-token + * prompt. `TokenUsage.inputTokens` is meant to be the TOTAL (matching + * OpenAI's `prompt_tokens`, which already includes its cached portion — + * see providers/openai.ts) — folding all three in here is what makes the + * displayed counter track real spend instead of silently undercounting + * every cached request (i.e. nearly every request, once caching is on). */ +function parseUsage(usage: { + input_tokens?: number + output_tokens?: number + cache_read_input_tokens?: number + cache_creation_input_tokens?: number +} | undefined): TokenUsage | undefined { if (!usage) return undefined + const cacheRead = usage.cache_read_input_tokens ?? 0 + const cacheCreation = usage.cache_creation_input_tokens ?? 0 return { - inputTokens: usage.input_tokens ?? 0, + inputTokens: (usage.input_tokens ?? 0) + cacheRead + cacheCreation, outputTokens: usage.output_tokens ?? 0, ...(usage.cache_read_input_tokens !== undefined ? { cachedInputTokens: usage.cache_read_input_tokens } : {}), } @@ -141,7 +157,7 @@ async function claudeChat(req: ChatRequest, cfg: ProviderConfig): Promise ))}
+ {/* Deliberately NOT labeled "Continue" — that's the + footer button's job (advancing to the next wizard + step once this stage has generated). This one only + confirms the answers above and reveals the Generate + button for the current stage. */}