fix(codex): normalize cached harness input tokens

This commit is contained in:
Vincent Koc
2026-04-26 16:24:02 -07:00
parent 9be8d43c31
commit 7902c769da
2 changed files with 31 additions and 17 deletions

View File

@@ -167,7 +167,7 @@ describe("CodexAppServerEventProjector", () => {
outputTokens: 100_000, outputTokens: 100_000,
}, },
last: { last: {
totalTokens: 14, totalTokens: 12,
inputTokens: 5, inputTokens: 5,
cachedInputTokens: 2, cachedInputTokens: 2,
outputTokens: 7, outputTokens: 7,
@@ -186,12 +186,12 @@ describe("CodexAppServerEventProjector", () => {
expect(result.assistantTexts).toEqual(["hello"]); expect(result.assistantTexts).toEqual(["hello"]);
expect(result.messagesSnapshot.map((message) => message.role)).toEqual(["user", "assistant"]); expect(result.messagesSnapshot.map((message) => message.role)).toEqual(["user", "assistant"]);
expect(result.lastAssistant?.content).toEqual([{ type: "text", text: "hello" }]); 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({ expect(result.lastAssistant?.usage).toMatchObject({
input: 5, input: 3,
output: 7, output: 7,
cacheRead: 2, cacheRead: 2,
totalTokens: 14, totalTokens: 12,
}); });
expect(result.replayMetadata.replaySafe).toBe(true); expect(result.replayMetadata.replaySafe).toBe(true);
}); });
@@ -289,7 +289,7 @@ describe("CodexAppServerEventProjector", () => {
tokenUsage: { tokenUsage: {
total: { total_tokens: 1_000_000 }, total: { total_tokens: 1_000_000 },
last_token_usage: { last_token_usage: {
total_tokens: 20, total_tokens: 17,
input_tokens: 8, input_tokens: 8,
cached_input_tokens: 3, cached_input_tokens: 3,
output_tokens: 9, output_tokens: 9,
@@ -300,12 +300,12 @@ describe("CodexAppServerEventProjector", () => {
const result = projector.buildResult(buildEmptyToolTelemetry()); 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({ expect(result.lastAssistant?.usage).toMatchObject({
input: 8, input: 5,
output: 9, output: 9,
cacheRead: 3, cacheRead: 3,
totalTokens: 20, totalTokens: 17,
}); });
}); });

View File

@@ -61,6 +61,13 @@ const CURRENT_TOKEN_USAGE_KEYS = [
"last_token_usage", "last_token_usage",
] as const; ] 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; const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
export class CodexAppServerEventProjector { export class CodexAppServerEventProjector {
@@ -910,17 +917,24 @@ function readNumberAlias(record: JsonObject, keys: readonly string[]): number |
} }
function normalizeCodexTokenUsage(record: JsonObject): ReturnType<typeof normalizeUsage> { function normalizeCodexTokenUsage(record: JsonObject): ReturnType<typeof normalizeUsage> {
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({ return normalizeUsage({
input: readNumberAlias(record, ["inputTokens", "input_tokens", "input", "promptTokens"]), input,
output: readNumberAlias(record, ["outputTokens", "output_tokens", "output"]), output: readNumberAlias(record, ["outputTokens", "output_tokens", "output"]),
cacheRead: readNumberAlias(record, [ cacheRead,
"cachedInputTokens",
"cached_input_tokens",
"cacheRead",
"cache_read",
"cache_read_input_tokens",
"cached_tokens",
]),
cacheWrite: readNumberAlias(record, [ cacheWrite: readNumberAlias(record, [
"cacheWrite", "cacheWrite",
"cache_write", "cache_write",