From 7902c769dabe5fdd45c7bd2edef2eb823e9d5dd4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 26 Apr 2026 16:24:02 -0700 Subject: [PATCH] fix(codex): normalize cached harness input tokens --- .../src/app-server/event-projector.test.ts | 16 +++++----- .../codex/src/app-server/event-projector.ts | 32 +++++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/extensions/codex/src/app-server/event-projector.test.ts b/extensions/codex/src/app-server/event-projector.test.ts index c6997ea0a31..3ceaa739b4f 100644 --- a/extensions/codex/src/app-server/event-projector.test.ts +++ b/extensions/codex/src/app-server/event-projector.test.ts @@ -167,7 +167,7 @@ describe("CodexAppServerEventProjector", () => { outputTokens: 100_000, }, last: { - totalTokens: 14, + totalTokens: 12, inputTokens: 5, cachedInputTokens: 2, outputTokens: 7, @@ -186,12 +186,12 @@ describe("CodexAppServerEventProjector", () => { expect(result.assistantTexts).toEqual(["hello"]); expect(result.messagesSnapshot.map((message) => message.role)).toEqual(["user", "assistant"]); expect(result.lastAssistant?.content).toEqual([{ type: "text", text: "hello" }]); - expect(result.attemptUsage).toMatchObject({ input: 5, output: 7, cacheRead: 2, total: 14 }); + expect(result.attemptUsage).toMatchObject({ input: 3, output: 7, cacheRead: 2, total: 12 }); expect(result.lastAssistant?.usage).toMatchObject({ - input: 5, + input: 3, output: 7, cacheRead: 2, - totalTokens: 14, + totalTokens: 12, }); expect(result.replayMetadata.replaySafe).toBe(true); }); @@ -289,7 +289,7 @@ describe("CodexAppServerEventProjector", () => { tokenUsage: { total: { total_tokens: 1_000_000 }, last_token_usage: { - total_tokens: 20, + total_tokens: 17, input_tokens: 8, cached_input_tokens: 3, output_tokens: 9, @@ -300,12 +300,12 @@ describe("CodexAppServerEventProjector", () => { const result = projector.buildResult(buildEmptyToolTelemetry()); - expect(result.attemptUsage).toMatchObject({ input: 8, output: 9, cacheRead: 3, total: 20 }); + expect(result.attemptUsage).toMatchObject({ input: 5, output: 9, cacheRead: 3, total: 17 }); expect(result.lastAssistant?.usage).toMatchObject({ - input: 8, + input: 5, output: 9, cacheRead: 3, - totalTokens: 20, + totalTokens: 17, }); }); diff --git a/extensions/codex/src/app-server/event-projector.ts b/extensions/codex/src/app-server/event-projector.ts index 6b5d4805d57..dc40dc6260b 100644 --- a/extensions/codex/src/app-server/event-projector.ts +++ b/extensions/codex/src/app-server/event-projector.ts @@ -61,6 +61,13 @@ const CURRENT_TOKEN_USAGE_KEYS = [ "last_token_usage", ] as const; +const CODEX_PROMPT_TOTAL_INPUT_KEYS = [ + "inputTokens", + "input_tokens", + "promptTokens", + "prompt_tokens", +] as const; + const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20; export class CodexAppServerEventProjector { @@ -910,17 +917,24 @@ function readNumberAlias(record: JsonObject, keys: readonly string[]): number | } function normalizeCodexTokenUsage(record: JsonObject): ReturnType { + const promptTotalInput = readNumberAlias(record, CODEX_PROMPT_TOTAL_INPUT_KEYS); + const cacheRead = readNumberAlias(record, [ + "cachedInputTokens", + "cached_input_tokens", + "cacheRead", + "cache_read", + "cache_read_input_tokens", + "cached_tokens", + ]); + const input = + promptTotalInput !== undefined && cacheRead !== undefined + ? Math.max(0, promptTotalInput - cacheRead) + : (promptTotalInput ?? readNumber(record, "input")); + return normalizeUsage({ - input: readNumberAlias(record, ["inputTokens", "input_tokens", "input", "promptTokens"]), + input, output: readNumberAlias(record, ["outputTokens", "output_tokens", "output"]), - cacheRead: readNumberAlias(record, [ - "cachedInputTokens", - "cached_input_tokens", - "cacheRead", - "cache_read", - "cache_read_input_tokens", - "cached_tokens", - ]), + cacheRead, cacheWrite: readNumberAlias(record, [ "cacheWrite", "cache_write",