Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

170 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import {
codexSessionIdFromRunEvents,
extractCodexLastTurnFirstCallUsage,
readCodexRolloutFirstCall,
} from '../src/codex-rollout-usage.js';
// Shapes mirror real codex 0.133.0 rollout JSONL (`~/.codex/sessions/**/rollout-*.jsonl`):
// each turn opens with a `task_started` event_msg, and every model call emits a
// `token_count` event_msg whose `info.last_token_usage` is that single call's
// usage (OpenAI-style: input_tokens INCLUDES the cached_input_tokens subset).
function taskStarted(): string {
return JSON.stringify({ type: 'event_msg', payload: { type: 'task_started' } });
}
function tokenCount(input: number | null, cached: number | null): string {
return JSON.stringify({
type: 'event_msg',
payload: {
type: 'token_count',
info:
input === null
? { last_token_usage: null }
: { last_token_usage: { input_tokens: input, cached_input_tokens: cached } },
},
});
}
describe('extractCodexLastTurnFirstCallUsage', () => {
it('reports the first call of the LAST turn, not the whole-session aggregate', () => {
// Real numbers from a 3-turn session: turn-1 is a cold open (18.5%), turn-2
// a warm resume (96.2%). The daemon just finished turn-3 (95%).
const rollout = [
taskStarted(),
tokenCount(13137, 2432), // turn-1 first call
tokenCount(13160, 2560), // within-turn-1 follow-up — must be ignored
taskStarted(),
tokenCount(13171, 12672), // turn-2 first call (the 96% spec number)
taskStarted(),
tokenCount(13342, 12672), // turn-3 first call — this is what we want
tokenCount(20000, 18000), // within-turn-3 follow-up — must be ignored
].join('\n');
const result = extractCodexLastTurnFirstCallUsage(rollout);
expect(result).toEqual({
first_call_input_tokens: 13342,
first_call_cache_read_input_tokens: 12672,
first_call_cache_hit_ratio: 12672 / 13342,
});
});
it('skips zero-input placeholder calls and uses the turns real first call', () => {
const rollout = [
taskStarted(),
tokenCount(0, 0), // interrupted/placeholder call — no usable ratio
tokenCount(40318, 15744), // the turns real opening call
].join('\n');
const result = extractCodexLastTurnFirstCallUsage(rollout);
expect(result?.first_call_input_tokens).toBe(40318);
expect(result?.first_call_cache_hit_ratio).toBeCloseTo(15744 / 40318);
});
it('treats a missing cached subset as a zero cache hit on a cold open', () => {
const rollout = [taskStarted(), tokenCount(15761, null)].join('\n');
const result = extractCodexLastTurnFirstCallUsage(rollout);
expect(result).toEqual({
first_call_input_tokens: 15761,
first_call_cache_read_input_tokens: 0,
first_call_cache_hit_ratio: 0,
});
});
it('returns null when the last turn produced no usable token_count yet', () => {
const rollout = [
taskStarted(),
tokenCount(13171, 12672),
taskStarted(), // a fresh turn opened but no call has landed
tokenCount(null, null),
].join('\n');
expect(extractCodexLastTurnFirstCallUsage(rollout)).toBeNull();
});
it('tolerates blank lines and non-JSON noise without throwing', () => {
const rollout = ['', 'not json', taskStarted(), ' ', tokenCount(13342, 12672)].join('\n');
const result = extractCodexLastTurnFirstCallUsage(rollout);
expect(result?.first_call_cache_hit_ratio).toBeCloseTo(12672 / 13342);
});
});
describe('codexSessionIdFromRunEvents', () => {
const statusEvent = (extra: Record<string, unknown>) => ({
event: 'agent',
data: { type: 'status', label: 'initializing', ...extra },
});
it('returns the thread id from the initializing status event', () => {
const events = [
statusEvent({ sessionId: '019eef4f-7409-7c82-bebe-30504eed3959' }),
{ event: 'agent', data: { type: 'status', label: 'thinking' } },
];
expect(codexSessionIdFromRunEvents(events)).toBe(
'019eef4f-7409-7c82-bebe-30504eed3959',
);
});
it('ignores status events without a session id and non-status events', () => {
const events = [
{ event: 'agent', data: { type: 'usage', usage: { input_tokens: 1 } } },
statusEvent({ label: 'thinking' }),
];
expect(codexSessionIdFromRunEvents(events)).toBeNull();
});
it('returns null for malformed input', () => {
expect(codexSessionIdFromRunEvents(null)).toBeNull();
expect(codexSessionIdFromRunEvents('nope')).toBeNull();
expect(codexSessionIdFromRunEvents([{ event: 'agent' }, 42])).toBeNull();
});
});
describe('readCodexRolloutFirstCall', () => {
// Plants a rollout under <home>/sessions/<y>/<m>/<d>/ exactly as codex names
// them, so the locate-and-read layer is covered (not just the pure extractor).
function plantRollout(sessionId: string, lines: string[]): string {
const home = mkdtempSync(path.join(tmpdir(), 'codex-home-'));
const dayDir = path.join(home, 'sessions', '2026', '06', '24');
mkdirSync(dayDir, { recursive: true });
writeFileSync(
path.join(dayDir, `rollout-2026-06-24T12-00-00-${sessionId}.jsonl`),
lines.join('\n'),
);
return home;
}
const rollout = (sid: string) => [
JSON.stringify({ type: 'event_msg', payload: { type: 'task_started' } }),
JSON.stringify({
type: 'event_msg',
payload: {
type: 'token_count',
info: { last_token_usage: { input_tokens: 13342, cached_input_tokens: 12672 } },
},
}),
];
it('locates the rollout by session id under CODEX_HOME and extracts first-call', async () => {
const sid = '019eef4f-7409-7c82-bebe-30504eed3959';
const home = plantRollout(sid, rollout(sid));
const result = await readCodexRolloutFirstCall({ codexHome: home, sessionId: sid });
expect(result?.first_call_input_tokens).toBe(13342);
expect(result?.first_call_cache_hit_ratio).toBeCloseTo(12672 / 13342);
});
it('returns null (never throws) when the session id has no matching rollout', async () => {
const home = plantRollout('some-other-session', rollout('some-other-session'));
await expect(
readCodexRolloutFirstCall({ codexHome: home, sessionId: 'missing-session' }),
).resolves.toBeNull();
});
it('returns null when no session id is given', async () => {
await expect(
readCodexRolloutFirstCall({ codexHome: '/nonexistent', sessionId: null }),
).resolves.toBeNull();
});
});