From 20467d987dc3f98629f090584307c2c60ca2cdee Mon Sep 17 00:00:00 2001 From: scoootscooob Date: Sun, 1 Mar 2026 19:07:14 -0800 Subject: [PATCH] fix(usage): clamp negative input token counts to zero Some OpenAI-format providers (via pi-ai) pre-subtract cached_tokens from prompt_tokens upstream. When cached_tokens exceeds prompt_tokens due to provider inconsistencies the subtraction produces a negative input value that flows through to the TUI status bar and /usage dashboard. Clamp rawInput to 0 in normalizeUsage() so downstream consumers never see nonsensical negative token counts. Closes #30765 Co-Authored-By: Claude Opus 4.6 --- src/agents/usage.test.ts | 17 +++++++++++++++++ src/agents/usage.ts | 6 +++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/agents/usage.test.ts b/src/agents/usage.test.ts index ade9e151d8d..b89b736761b 100644 --- a/src/agents/usage.test.ts +++ b/src/agents/usage.test.ts @@ -88,6 +88,23 @@ describe("normalizeUsage", () => { }); }); + it("clamps negative input to zero (pre-subtracted cached_tokens > prompt_tokens)", () => { + // pi-ai OpenAI-format providers subtract cached_tokens from prompt_tokens + // upstream. When cached_tokens exceeds prompt_tokens the result is negative. + const usage = normalizeUsage({ + input: -4900, + output: 200, + cacheRead: 5000, + }); + expect(usage).toEqual({ + input: 0, + output: 200, + cacheRead: 5000, + cacheWrite: undefined, + total: undefined, + }); + }); + it("returns undefined when no valid fields are provided", () => { const usage = normalizeUsage(null); expect(usage).toBeUndefined(); diff --git a/src/agents/usage.ts b/src/agents/usage.ts index b7bc0f85cf2..251cb56155c 100644 --- a/src/agents/usage.ts +++ b/src/agents/usage.ts @@ -90,9 +90,13 @@ export function normalizeUsage(raw?: UsageLike | null): NormalizedUsage | undefi return undefined; } - const input = asFiniteNumber( + // Some providers (pi-ai OpenAI-format) pre-subtract cached_tokens from + // prompt_tokens upstream. When cached_tokens > prompt_tokens the result is + // negative, which is nonsensical. Clamp to 0. + const rawInput = asFiniteNumber( raw.input ?? raw.inputTokens ?? raw.input_tokens ?? raw.promptTokens ?? raw.prompt_tokens, ); + const input = rawInput !== undefined && rawInput < 0 ? 0 : rawInput; const output = asFiniteNumber( raw.output ?? raw.outputTokens ??