diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index b8b34d2..c93b67e 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -296,12 +296,21 @@ function parseCodexJsonInput(value: JsonValue): JsonValue { try { return JSON.parse(value); } catch { return value; } } -function codexUsage(payload: JsonRecord) { +interface CodexUsage { + inputTokens: number | null; + outputTokens: number | null; +} + +function codexUsage(payload: JsonRecord): CodexUsage { const usage = payload?.info?.last_token_usage || payload?.info?.total_token_usage || payload?.last_token_usage || null; - if (!usage) return {}; + if (!usage) return { inputTokens: null, outputTokens: null }; return { - inputTokens: usage.input_tokens ?? null, - outputTokens: usage.output_tokens ?? null, + inputTokens: typeof usage.input_tokens === 'number' && Number.isFinite(usage.input_tokens) + ? usage.input_tokens + : null, + outputTokens: typeof usage.output_tokens === 'number' && Number.isFinite(usage.output_tokens) + ? usage.output_tokens + : null, }; } diff --git a/packages/core/src/providers/codex.ts b/packages/core/src/providers/codex.ts index 7111be2..1e9be23 100644 --- a/packages/core/src/providers/codex.ts +++ b/packages/core/src/providers/codex.ts @@ -256,9 +256,9 @@ export function* parse(unit: IndexUnit, _cursor: Cursor): Generator { db.close(); }); +test('Codex token_count without usage remains persistable', () => { + const threadId = '019e8951-3e7d-7343-a3e3-05bff48a3180'; + const path = writeCodexFixture([ + { + type: 'session_meta', + timestamp: '2026-07-14T12:21:21.000Z', + payload: { id: threadId, cwd: '/tmp/demo', timestamp: '2026-07-14T12:21:21.000Z' }, + }, + { + type: 'event_msg', + timestamp: '2026-07-14T12:21:30.000Z', + payload: { type: 'agent_message', message: 'hello' }, + }, + { + type: 'event_msg', + timestamp: '2026-07-14T12:21:31.000Z', + payload: { type: 'token_count', info: null }, + }, + ]); + const db = new DatabaseSync(':memory:'); + db.exec(SCHEMA); + + assert.doesNotThrow(() => { + persist(db, { key: path, sessionId: `codex:${threadId}` }, parseCodex({ + key: path, + sessionId: `codex:${threadId}`, + }, null)); + }); + const usage = db.prepare( + 'SELECT input_tokens, output_tokens FROM messages WHERE text = ?', + ).get('hello'); + assert.equal(usage.input_tokens, null); + assert.equal(usage.output_tokens, null); + db.close(); +}); + test('provider-classified hidden context never reaches session detail', () => { const threadId = '019e8951-3e7d-7343-a3e3-05bff48a317e'; const path = writeCodexFixture([