项目文件夹

文件
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 行
6.5 KiB
TypeScript

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 turn’s real first call', () => {
const rollout = [
taskStarted(),
tokenCount(0, 0), // interrupted/placeholder call — no usable ratio
tokenCount(40318, 15744), // the turn’s 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();
});
});