From e9c6fd4517fd734e2dd54258cf32d9111d8f5876 Mon Sep 17 00:00:00 2001 From: KosBeg <10928175+KosBeg@users.noreply.github.com> Date: Sun, 25 Jan 2026 22:58:15 +0200 Subject: [PATCH] fix(codex): ignore duplicate token_count totals --- apps/codex/src/data-loader.ts | 93 ++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/apps/codex/src/data-loader.ts b/apps/codex/src/data-loader.ts index ef23a8f5..f3141186 100644 --- a/apps/codex/src/data-loader.ts +++ b/apps/codex/src/data-loader.ts @@ -22,6 +22,20 @@ type RawUsage = { total_tokens: number; }; +function isSameRawUsage(left: RawUsage | null, right: RawUsage | null): boolean { + if (left == null || right == null) { + return false; + } + + return ( + left.input_tokens === right.input_tokens && + left.cached_input_tokens === right.cached_input_tokens && + left.output_tokens === right.output_tokens && + left.reasoning_output_tokens === right.reasoning_output_tokens && + left.total_tokens === right.total_tokens + ); +} + function ensureNumber(value: unknown): number { return typeof value === 'number' && Number.isFinite(value) ? value : 0; } @@ -291,9 +305,12 @@ export async function loadTokenUsageEvents(options: LoadOptions = {}): Promise { + await using fixture = await createFixture({ + sessions: { + 'duplicate.jsonl': [ + JSON.stringify({ + timestamp: '2025-09-11T18:25:30.000Z', + type: 'turn_context', + payload: { + model: 'gpt-5', + }, + }), + JSON.stringify({ + timestamp: '2025-09-11T18:25:40.000Z', + type: 'event_msg', + payload: { + type: 'token_count', + info: { + total_token_usage: { + input_tokens: 1_000, + cached_input_tokens: 250, + output_tokens: 200, + reasoning_output_tokens: 0, + total_tokens: 1_200, + }, + last_token_usage: { + input_tokens: 1_000, + cached_input_tokens: 250, + output_tokens: 200, + reasoning_output_tokens: 0, + total_tokens: 1_200, + }, + }, + }, + }), + JSON.stringify({ + timestamp: '2025-09-11T18:25:41.000Z', + type: 'event_msg', + payload: { + type: 'token_count', + info: { + total_token_usage: { + input_tokens: 1_000, + cached_input_tokens: 250, + output_tokens: 200, + reasoning_output_tokens: 0, + total_tokens: 1_200, + }, + last_token_usage: { + input_tokens: 1_000, + cached_input_tokens: 250, + output_tokens: 200, + reasoning_output_tokens: 0, + total_tokens: 1_200, + }, + }, + }, + }), + ].join('\n'), + }, + }); + + const { events } = await loadTokenUsageEvents({ + sessionDirs: [fixture.getPath('sessions')], + }); + + expect(events).toHaveLength(1); + expect(events[0]!.inputTokens).toBe(1_000); + expect(events[0]!.cachedInputTokens).toBe(250); + expect(events[0]!.outputTokens).toBe(200); + expect(events[0]!.totalTokens).toBe(1_200); + }); + it('falls back to legacy model when metadata is missing entirely', async () => { await using fixture = await createFixture({ sessions: {