import { describe, expect, it } from "@effect/vitest"; import { GROK_COST_USD_TICKS_PER_DOLLAR, initialCodexScanState, parseClaudeLine, parseCodexLine, parseGrokLine, totalTokens,} from "./usageTranscripts.ts"; /** Shaped after a real Claude Code assistant record. */function claudeLine(overrides: { messageId: string; contentType: string; model?: string; outputTokens?: number;}): string { return JSON.stringify({ type: "assistant", timestamp: "2026-08-07T04:05:13.944Z", sessionId: "5a128faa-8253-489e-b935-6c08e8e670c0", cwd: "/home/theo/project", message: { id: overrides.messageId, role: "assistant", model: overrides.model ?? "claude-fable-5", content: [{ type: overrides.contentType }], usage: { input_tokens: 2, cache_creation_input_tokens: 66818, cache_read_input_tokens: 1000, output_tokens: overrides.outputTokens ?? 286, }, }, });} describe("parseClaudeLine", () => { it("extracts token totals and a dedupe key", () => { const record = parseClaudeLine(claudeLine({ messageId: "msg_1", contentType: "text" })); expect(record).not.toBeNull(); expect(record?.provider).toBe("claude"); expect(record?.model).toBe("claude-fable-5"); expect(record?.totals).toEqual({ uncachedInputTokens: 2, cachedInputTokens: 1000, cacheCreationTokens: 66818, outputTokens: 286, reasoningTokens: 0, }); expect(record?.dedupeKey).toBe("msg_1:"); }); it("gives every content block of one message the same dedupe key", () => { // T3 Code writes one record per content block, each repeating the parent // message's full usage. Summing them would overcount ~2.4x on real data. const text = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "text" })); const toolUse = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "tool_use" })); expect(text?.dedupeKey).toBe(toolUse?.dedupeKey); expect(text?.totals).toEqual(toolUse?.totals); }); it("ignores records that are not assistant messages", () => { expect(parseClaudeLine(JSON.stringify({ type: "user", message: {} }))).toBeNull(); expect(parseClaudeLine("not json")).toBeNull(); });}); describe("parseCodexLine", () => { const sessionMeta = JSON.stringify({ type: "session_meta", timestamp: "2026-08-01T05:17:41.289Z", payload: { type: "session_meta", id: "019fbbc1-b12c-7360-a685-28c181f0025f" }, }); const turnContext = JSON.stringify({ type: "turn_context", timestamp: "2026-08-01T05:17:42.694Z", payload: { type: "turn_context", model: "gpt-5.6-sol" }, }); const tokenCount = (inputTokens: number, cached: number, output: number, reasoning: number) => JSON.stringify({ type: "event_msg", timestamp: "2026-08-01T05:17:49.919Z", payload: { type: "token_count", info: { last_token_usage: { input_tokens: inputTokens, cached_input_tokens: cached, cache_write_input_tokens: 0, output_tokens: output, reasoning_output_tokens: reasoning, }, }, }, }); it("attributes usage to the model from the preceding turn context", () => { const state = initialCodexScanState(); parseCodexLine(sessionMeta, state); parseCodexLine(turnContext, state); const record = parseCodexLine(tokenCount(19239, 11008, 299, 116), state); expect(record?.provider).toBe("codex"); expect(record?.model).toBe("gpt-5.6-sol"); expect(record?.sessionId).toBe("019fbbc1-b12c-7360-a685-28c181f0025f"); // Codex reports input_tokens inclusive of the cached portion. expect(record?.totals.uncachedInputTokens).toBe(19239 - 11008); expect(record?.totals.cachedInputTokens).toBe(11008); expect(record?.totals.reasoningTokens).toBe(116); }); it("skips a repeated token_count so deltas are not double counted", () => { const state = initialCodexScanState(); parseCodexLine(turnContext, state); const first = parseCodexLine(tokenCount(100, 0, 10, 0), state); const repeat = parseCodexLine(tokenCount(100, 0, 10, 0), state); expect(first).not.toBeNull(); expect(repeat).toBeNull(); }); it("drops usage that arrives before any model is known", () => { const state = initialCodexScanState(); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).toBeNull(); }); it("does not let a pre-model event poison the duplicate signature", () => { // A token_count before its turn_context is dropped; the identical event // re-emitted once the model is known must still be counted. const state = initialCodexScanState(); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).toBeNull(); parseCodexLine(turnContext, state); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull(); }); // A forked/subagent rollout opens with the parent's history copied in and // every line re-stamped to the fork instant, then the ancestors' session // metas. Counting those again multiplied usage ~1.85x on real data (#5758). describe("forked rollouts", () => { const meta = (overrides: { id: string; timestamp: string; forkedFromId?: string; spawnParentId?: string; }) => JSON.stringify({ type: "session_meta", timestamp: overrides.timestamp, payload: { type: "session_meta", id: overrides.id, ...(overrides.forkedFromId === undefined ? {} : { forked_from_id: overrides.forkedFromId }), ...(overrides.spawnParentId === undefined ? {} : { source: { subagent: { thread_spawn: { parent_thread_id: overrides.spawnParentId } }, }, }), }, }); const stamped = (timestamp: string, line: string) => { const parsed = JSON.parse(line) as { timestamp: string }; parsed.timestamp = timestamp; return JSON.stringify(parsed); }; it("keeps the child session id over copied ancestor metas", () => { const state = initialCodexScanState(); parseCodexLine(meta({ id: "child", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(meta({ id: "parent", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(turnContext, state); const record = parseCodexLine(tokenCount(100, 0, 10, 0), state); expect(record?.sessionId).toBe("child"); }); it("drops the re-stamped copied burst and keeps the first real event", () => { const state = initialCodexScanState(); const forkInstant = "2026-08-01T05:00:00.000Z"; parseCodexLine(meta({ id: "child", timestamp: forkInstant, forkedFromId: "parent" }), state); parseCodexLine(meta({ id: "parent", timestamp: forkInstant }), state); parseCodexLine(stamped(forkInstant, turnContext), state); // Copied history: written in one burst at the fork instant. expect( parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), ).toBeNull(); expect( parseCodexLine(stamped("2026-08-01T05:00:00.002Z", tokenCount(200, 0, 20, 0)), state), ).toBeNull(); // The child's first genuine turn lands seconds later and must count. const real = parseCodexLine( stamped("2026-08-01T05:00:06.000Z", tokenCount(300, 0, 30, 0)), state, ); expect(real).not.toBeNull(); expect(real?.totals.outputTokens).toBe(30); // Suppression never restarts, even for closely spaced later events. const next = parseCodexLine( stamped("2026-08-01T05:00:06.100Z", tokenCount(400, 0, 40, 0)), state, ); expect(next).not.toBeNull(); }); it("recognizes subagent spawns without forked_from_id", () => { const state = initialCodexScanState(); const spawnInstant = "2026-08-01T05:00:00.000Z"; parseCodexLine( meta({ id: "child", timestamp: spawnInstant, spawnParentId: "parent" }), state, ); parseCodexLine(stamped(spawnInstant, turnContext), state); expect( parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), ).toBeNull(); }); it("does not suppress anything in a rollout that is not a fork", () => { const state = initialCodexScanState(); parseCodexLine(meta({ id: "root", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(stamped("2026-08-01T05:00:00.100Z", turnContext), state); const record = parseCodexLine( stamped("2026-08-01T05:00:00.200Z", tokenCount(100, 0, 10, 0)), state, ); expect(record).not.toBeNull(); }); });}); describe("totalTokens", () => { it("does not add reasoning on top of output", () => { expect( totalTokens({ uncachedInputTokens: 10, cachedInputTokens: 20, cacheCreationTokens: 30, outputTokens: 40, reasoningTokens: 25, }), ).toBe(100); });}); describe("parseGrokLine", () => { /** Shaped after a real Grok Build `turn_completed` session update. */ function turnCompleted(overrides?: { sessionId?: string; promptId?: string; timestamp?: number; agentTimestampMs?: number; usage?: Record<string, unknown>; modelUsage?: Record<string, Record<string, unknown>> | null; }): string { const modelUsage = overrides && "modelUsage" in overrides ? overrides.modelUsage : { "grok-4.5-build": { inputTokens: 20_272, outputTokens: 272, totalTokens: 20_544, cachedReadTokens: 11_264, cacheCreationTokens: 0, reasoningTokens: 180, costUsdTicks: 230_272_000, }, }; return JSON.stringify({ timestamp: overrides?.timestamp ?? 1_786_372_566, method: "_x.ai/session/update", params: { sessionId: overrides?.sessionId ?? "019fec1a-12f7-72f2-9b1f-7778a00aea3c", update: { sessionUpdate: "turn_completed", prompt_id: overrides?.promptId ?? "prompt-1", stop_reason: "end_turn", usage: { inputTokens: 20_272, outputTokens: 272, totalTokens: 20_544, cachedReadTokens: 11_264, cacheCreationTokens: 0, reasoningTokens: 180, costUsdTicks: 230_272_000, ...(modelUsage === null ? {} : { modelUsage }), ...overrides?.usage, }, }, _meta: { eventId: "event-1", agentTimestampMs: overrides?.agentTimestampMs ?? 1_786_372_566_485, }, }, }); } it("extracts per-model totals and provider-reported cost ticks", () => { const records = parseGrokLine(turnCompleted()); expect(records).toHaveLength(1); const [record] = records; expect(record?.provider).toBe("grok"); expect(record?.model).toBe("grok-4.5-build"); expect(record?.sessionId).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c"); expect(record?.timestampMs).toBe(1_786_372_566_485); expect(record?.totals).toEqual({ uncachedInputTokens: 20_272 - 11_264, cachedInputTokens: 11_264, cacheCreationTokens: 0, outputTokens: 272, reasoningTokens: 180, }); expect(record?.reportedCostUsd).toBeCloseTo(230_272_000 / GROK_COST_USD_TICKS_PER_DOLLAR, 12); expect(record?.dedupeKey).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c:prompt-1:grok-4.5-build"); }); it("emits one record per model when modelUsage has several entries", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 1000, outputTokens: 50, cachedReadTokens: 400, reasoningTokens: 20, costUsdTicks: 50_000_000, }, "grok-composer-2.5-fast": { inputTokens: 200, outputTokens: 30, cachedReadTokens: 100, reasoningTokens: 0, costUsdTicks: 10_000_000, }, }, }), ); expect(records.map((record) => record.model).toSorted()).toEqual([ "grok-4.5", "grok-composer-2.5-fast", ]); expect(records.every((record) => record.provider === "grok")).toBe(true); expect(records.find((record) => record.model === "grok-4.5")?.reportedCostUsd).toBeCloseTo( 0.005, 12, ); }); it("inherits top-level cost ticks for a single model without its own ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5-build": { inputTokens: 1000, outputTokens: 10, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(1); expect(records[0]?.reportedCostUsd).toBe(1); }); it("falls back to a generic grok model when modelUsage is absent", () => { const records = parseGrokLine(turnCompleted({ modelUsage: null })); expect(records).toHaveLength(1); const [record] = records; expect(record?.provider).toBe("grok"); expect(record?.model).toBe("grok"); expect(record?.totals).toEqual({ uncachedInputTokens: 20_272 - 11_264, cachedInputTokens: 11_264, cacheCreationTokens: 0, outputTokens: 272, reasoningTokens: 180, }); expect(record?.reportedCostUsd).toBeCloseTo(230_272_000 / GROK_COST_USD_TICKS_PER_DOLLAR, 12); expect(record?.dedupeKey).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c:prompt-1:grok"); }); it("pro-rates top-level cost ticks across multi-model turns without per-model ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.75, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.25, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); }); it("pro-rates aggregate cost when a zero-token sibling carries costUsdTicks: 0", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "empty-sibling": { inputTokens: 0, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, costUsdTicks: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); expect(records.every((record) => record.model !== "empty-sibling")).toBe(true); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.75, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.25, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); }); it("allocates leftover aggregate ticks to models that omit per-model ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, costUsdTicks: 0.4 * GROK_COST_USD_TICKS_PER_DOLLAR, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.4, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.6, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); }); Lines 1–500 of 566. Load more
import { describe, expect, it } from "@effect/vitest"; import { GROK_COST_USD_TICKS_PER_DOLLAR, initialCodexScanState, parseClaudeLine, parseCodexLine, parseGrokLine, totalTokens, } from "./usageTranscripts.ts"; /** Shaped after a real Claude Code assistant record. */ function claudeLine(overrides: { messageId: string; contentType: string; model?: string; outputTokens?: number; }): string { return JSON.stringify({ type: "assistant", timestamp: "2026-08-07T04:05:13.944Z", sessionId: "5a128faa-8253-489e-b935-6c08e8e670c0", cwd: "/home/theo/project", message: { id: overrides.messageId, role: "assistant", model: overrides.model ?? "claude-fable-5", content: [{ type: overrides.contentType }], usage: { input_tokens: 2, cache_creation_input_tokens: 66818, cache_read_input_tokens: 1000, output_tokens: overrides.outputTokens ?? 286, }, }, }); } describe("parseClaudeLine", () => { it("extracts token totals and a dedupe key", () => { const record = parseClaudeLine(claudeLine({ messageId: "msg_1", contentType: "text" })); expect(record).not.toBeNull(); expect(record?.provider).toBe("claude"); expect(record?.model).toBe("claude-fable-5"); expect(record?.totals).toEqual({ uncachedInputTokens: 2, cachedInputTokens: 1000, cacheCreationTokens: 66818, outputTokens: 286, reasoningTokens: 0, }); expect(record?.dedupeKey).toBe("msg_1:"); }); it("gives every content block of one message the same dedupe key", () => { // T3 Code writes one record per content block, each repeating the parent // message's full usage. Summing them would overcount ~2.4x on real data. const text = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "text" })); const toolUse = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "tool_use" })); expect(text?.dedupeKey).toBe(toolUse?.dedupeKey); expect(text?.totals).toEqual(toolUse?.totals); }); it("ignores records that are not assistant messages", () => { expect(parseClaudeLine(JSON.stringify({ type: "user", message: {} }))).toBeNull(); expect(parseClaudeLine("not json")).toBeNull(); }); }); describe("parseCodexLine", () => { const sessionMeta = JSON.stringify({ type: "session_meta", timestamp: "2026-08-01T05:17:41.289Z", payload: { type: "session_meta", id: "019fbbc1-b12c-7360-a685-28c181f0025f" }, }); const turnContext = JSON.stringify({ type: "turn_context", timestamp: "2026-08-01T05:17:42.694Z", payload: { type: "turn_context", model: "gpt-5.6-sol" }, }); const tokenCount = (inputTokens: number, cached: number, output: number, reasoning: number) => JSON.stringify({ type: "event_msg", timestamp: "2026-08-01T05:17:49.919Z", payload: { type: "token_count", info: { last_token_usage: { input_tokens: inputTokens, cached_input_tokens: cached, cache_write_input_tokens: 0, output_tokens: output, reasoning_output_tokens: reasoning, }, }, }, }); it("attributes usage to the model from the preceding turn context", () => { const state = initialCodexScanState(); parseCodexLine(sessionMeta, state); parseCodexLine(turnContext, state); const record = parseCodexLine(tokenCount(19239, 11008, 299, 116), state); expect(record?.provider).toBe("codex"); expect(record?.model).toBe("gpt-5.6-sol"); expect(record?.sessionId).toBe("019fbbc1-b12c-7360-a685-28c181f0025f"); // Codex reports input_tokens inclusive of the cached portion. expect(record?.totals.uncachedInputTokens).toBe(19239 - 11008); expect(record?.totals.cachedInputTokens).toBe(11008); expect(record?.totals.reasoningTokens).toBe(116); }); it("skips a repeated token_count so deltas are not double counted", () => { const state = initialCodexScanState(); parseCodexLine(turnContext, state); const first = parseCodexLine(tokenCount(100, 0, 10, 0), state); const repeat = parseCodexLine(tokenCount(100, 0, 10, 0), state); expect(first).not.toBeNull(); expect(repeat).toBeNull(); }); it("drops usage that arrives before any model is known", () => { const state = initialCodexScanState(); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).toBeNull(); }); it("does not let a pre-model event poison the duplicate signature", () => { // A token_count before its turn_context is dropped; the identical event // re-emitted once the model is known must still be counted. const state = initialCodexScanState(); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).toBeNull(); parseCodexLine(turnContext, state); expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull(); }); // A forked/subagent rollout opens with the parent's history copied in and // every line re-stamped to the fork instant, then the ancestors' session // metas. Counting those again multiplied usage ~1.85x on real data (#5758). describe("forked rollouts", () => { const meta = (overrides: { id: string; timestamp: string; forkedFromId?: string; spawnParentId?: string; }) => JSON.stringify({ type: "session_meta", timestamp: overrides.timestamp, payload: { type: "session_meta", id: overrides.id, ...(overrides.forkedFromId === undefined ? {} : { forked_from_id: overrides.forkedFromId }), ...(overrides.spawnParentId === undefined ? {} : { source: { subagent: { thread_spawn: { parent_thread_id: overrides.spawnParentId } }, }, }), }, }); const stamped = (timestamp: string, line: string) => { const parsed = JSON.parse(line) as { timestamp: string }; parsed.timestamp = timestamp; return JSON.stringify(parsed); }; it("keeps the child session id over copied ancestor metas", () => { const state = initialCodexScanState(); parseCodexLine(meta({ id: "child", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(meta({ id: "parent", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(turnContext, state); const record = parseCodexLine(tokenCount(100, 0, 10, 0), state); expect(record?.sessionId).toBe("child"); }); it("drops the re-stamped copied burst and keeps the first real event", () => { const state = initialCodexScanState(); const forkInstant = "2026-08-01T05:00:00.000Z"; parseCodexLine(meta({ id: "child", timestamp: forkInstant, forkedFromId: "parent" }), state); parseCodexLine(meta({ id: "parent", timestamp: forkInstant }), state); parseCodexLine(stamped(forkInstant, turnContext), state); // Copied history: written in one burst at the fork instant. expect( parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), ).toBeNull(); expect( parseCodexLine(stamped("2026-08-01T05:00:00.002Z", tokenCount(200, 0, 20, 0)), state), ).toBeNull(); // The child's first genuine turn lands seconds later and must count. const real = parseCodexLine( stamped("2026-08-01T05:00:06.000Z", tokenCount(300, 0, 30, 0)), state, ); expect(real).not.toBeNull(); expect(real?.totals.outputTokens).toBe(30); // Suppression never restarts, even for closely spaced later events. const next = parseCodexLine( stamped("2026-08-01T05:00:06.100Z", tokenCount(400, 0, 40, 0)), state, ); expect(next).not.toBeNull(); }); it("recognizes subagent spawns without forked_from_id", () => { const state = initialCodexScanState(); const spawnInstant = "2026-08-01T05:00:00.000Z"; parseCodexLine( meta({ id: "child", timestamp: spawnInstant, spawnParentId: "parent" }), state, ); parseCodexLine(stamped(spawnInstant, turnContext), state); expect( parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), ).toBeNull(); }); it("does not suppress anything in a rollout that is not a fork", () => { const state = initialCodexScanState(); parseCodexLine(meta({ id: "root", timestamp: "2026-08-01T05:00:00.000Z" }), state); parseCodexLine(stamped("2026-08-01T05:00:00.100Z", turnContext), state); const record = parseCodexLine( stamped("2026-08-01T05:00:00.200Z", tokenCount(100, 0, 10, 0)), state, ); expect(record).not.toBeNull(); }); }); }); describe("totalTokens", () => { it("does not add reasoning on top of output", () => { expect( totalTokens({ uncachedInputTokens: 10, cachedInputTokens: 20, cacheCreationTokens: 30, outputTokens: 40, reasoningTokens: 25, }), ).toBe(100); }); }); describe("parseGrokLine", () => { /** Shaped after a real Grok Build `turn_completed` session update. */ function turnCompleted(overrides?: { sessionId?: string; promptId?: string; timestamp?: number; agentTimestampMs?: number; usage?: Record<string, unknown>; modelUsage?: Record<string, Record<string, unknown>> | null; }): string { const modelUsage = overrides && "modelUsage" in overrides ? overrides.modelUsage : { "grok-4.5-build": { inputTokens: 20_272, outputTokens: 272, totalTokens: 20_544, cachedReadTokens: 11_264, cacheCreationTokens: 0, reasoningTokens: 180, costUsdTicks: 230_272_000, }, }; return JSON.stringify({ timestamp: overrides?.timestamp ?? 1_786_372_566, method: "_x.ai/session/update", params: { sessionId: overrides?.sessionId ?? "019fec1a-12f7-72f2-9b1f-7778a00aea3c", update: { sessionUpdate: "turn_completed", prompt_id: overrides?.promptId ?? "prompt-1", stop_reason: "end_turn", usage: { inputTokens: 20_272, outputTokens: 272, totalTokens: 20_544, cachedReadTokens: 11_264, cacheCreationTokens: 0, reasoningTokens: 180, costUsdTicks: 230_272_000, ...(modelUsage === null ? {} : { modelUsage }), ...overrides?.usage, }, }, _meta: { eventId: "event-1", agentTimestampMs: overrides?.agentTimestampMs ?? 1_786_372_566_485, }, }, }); } it("extracts per-model totals and provider-reported cost ticks", () => { const records = parseGrokLine(turnCompleted()); expect(records).toHaveLength(1); const [record] = records; expect(record?.provider).toBe("grok"); expect(record?.model).toBe("grok-4.5-build"); expect(record?.sessionId).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c"); expect(record?.timestampMs).toBe(1_786_372_566_485); expect(record?.totals).toEqual({ uncachedInputTokens: 20_272 - 11_264, cachedInputTokens: 11_264, cacheCreationTokens: 0, outputTokens: 272, reasoningTokens: 180, }); expect(record?.reportedCostUsd).toBeCloseTo(230_272_000 / GROK_COST_USD_TICKS_PER_DOLLAR, 12); expect(record?.dedupeKey).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c:prompt-1:grok-4.5-build"); }); it("emits one record per model when modelUsage has several entries", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 1000, outputTokens: 50, cachedReadTokens: 400, reasoningTokens: 20, costUsdTicks: 50_000_000, }, "grok-composer-2.5-fast": { inputTokens: 200, outputTokens: 30, cachedReadTokens: 100, reasoningTokens: 0, costUsdTicks: 10_000_000, }, }, }), ); expect(records.map((record) => record.model).toSorted()).toEqual([ "grok-4.5", "grok-composer-2.5-fast", ]); expect(records.every((record) => record.provider === "grok")).toBe(true); expect(records.find((record) => record.model === "grok-4.5")?.reportedCostUsd).toBeCloseTo( 0.005, 12, ); }); it("inherits top-level cost ticks for a single model without its own ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5-build": { inputTokens: 1000, outputTokens: 10, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(1); expect(records[0]?.reportedCostUsd).toBe(1); }); it("falls back to a generic grok model when modelUsage is absent", () => { const records = parseGrokLine(turnCompleted({ modelUsage: null })); expect(records).toHaveLength(1); const [record] = records; expect(record?.provider).toBe("grok"); expect(record?.model).toBe("grok"); expect(record?.totals).toEqual({ uncachedInputTokens: 20_272 - 11_264, cachedInputTokens: 11_264, cacheCreationTokens: 0, outputTokens: 272, reasoningTokens: 180, }); expect(record?.reportedCostUsd).toBeCloseTo(230_272_000 / GROK_COST_USD_TICKS_PER_DOLLAR, 12); expect(record?.dedupeKey).toBe("019fec1a-12f7-72f2-9b1f-7778a00aea3c:prompt-1:grok"); }); it("pro-rates top-level cost ticks across multi-model turns without per-model ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.75, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.25, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); }); it("pro-rates aggregate cost when a zero-token sibling carries costUsdTicks: 0", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, "empty-sibling": { inputTokens: 0, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, costUsdTicks: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); expect(records.every((record) => record.model !== "empty-sibling")).toBe(true); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.75, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.25, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); }); it("allocates leftover aggregate ticks to models that omit per-model ticks", () => { const records = parseGrokLine( turnCompleted({ modelUsage: { "grok-4.5": { inputTokens: 300, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, costUsdTicks: 0.4 * GROK_COST_USD_TICKS_PER_DOLLAR, }, "grok-composer-2.5-fast": { inputTokens: 100, outputTokens: 0, cachedReadTokens: 0, reasoningTokens: 0, }, }, usage: { costUsdTicks: GROK_COST_USD_TICKS_PER_DOLLAR }, }), ); expect(records).toHaveLength(2); const byModel = Object.fromEntries(records.map((record) => [record.model, record])); expect(byModel["grok-4.5"]?.reportedCostUsd).toBeCloseTo(0.4, 12); expect(byModel["grok-composer-2.5-fast"]?.reportedCostUsd).toBeCloseTo(0.6, 12); const sum = (byModel["grok-4.5"]?.reportedCostUsd ?? 0) + (byModel["grok-composer-2.5-fast"]?.reportedCostUsd ?? 0); expect(sum).toBeCloseTo(1, 12); });