133 lines · 4.0 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test";import { normalizeClaudeTurnTokenUsage } from "./ClaudeTurnTokenUsage.ts";import {  accumulateCodexTurnTokenUsage,  completeCodexTurnTokenUsage,  getCodexTurnAccumulator,  makeCodexTurnTokenUsageState,} from "./CodexTurnTokenUsage.ts"; const breakdown = (input: number, cached: number, output: number) => ({  totalTokens: input + output,  inputTokens: input,  cachedInputTokens: cached,  outputTokens: output,  reasoningOutputTokens: 0,});const update = (total: ReturnType<typeof breakdown>, last = total) => ({  total,  last,  modelContextWindow: 200_000,}); describe("billable native turn usage", () => {  it("adds each Codex response once and excludes the prior resumed history", () => {    const state = makeCodexTurnTokenUsageState();    state.activeTurnId = "turn";    const first = update(breakdown(1010, 505, 102), breakdown(10, 5, 2));    accumulateCodexTurnTokenUsage(state, "turn", first);    accumulateCodexTurnTokenUsage(state, "turn", first);    accumulateCodexTurnTokenUsage(      state,      "turn",      update(breakdown(1030, 515, 108), breakdown(20, 10, 6)),    );    expect(completeCodexTurnTokenUsage(state, "turn", true)).toEqual({      usageStatus: "complete",      usageScope: "main_agent",      hasSubagents: false,      inputTokens: 30,      cachedInputTokens: 15,      outputTokens: 8,      reasoningTokens: 0,    });  });   it("keeps previous responses when compaction resets Codex cumulative counters", () => {    const state = makeCodexTurnTokenUsageState();    state.activeTurnId = "turn";    accumulateCodexTurnTokenUsage(state, "turn", update(breakdown(100, 40, 20)));    accumulateCodexTurnTokenUsage(state, "turn", update(breakdown(20, 5, 3)));    getCodexTurnAccumulator(state, "turn").hasSubagents = true;    expect(completeCodexTurnTokenUsage(state, "turn", false)).toMatchObject({      usageStatus: "partial",      hasSubagents: true,      inputTokens: 120,      cachedInputTokens: 45,      outputTokens: 23,    });  });   it("does not bill late Codex updates against the next active turn", () => {    const state = makeCodexTurnTokenUsageState();    state.activeTurnId = "next";    accumulateCodexTurnTokenUsage(state, "previous", update(breakdown(100, 10, 20)));    accumulateCodexTurnTokenUsage(      state,      "next",      update(breakdown(104, 12, 21), breakdown(4, 2, 1)),    );    expect(completeCodexTurnTokenUsage(state, "next", true)).toMatchObject({      inputTokens: 4,      outputTokens: 1,    });    expect(completeCodexTurnTokenUsage(state, "missing", false).usageStatus).toBe("unavailable");  });   it("counts Claude cached input inside total input and bounds thinking output", () => {    expect(      normalizeClaudeTurnTokenUsage(        {          subtype: "success",          usage: {            input_tokens: 100,            cache_read_input_tokens: 40,            cache_creation_input_tokens: 10,            output_tokens: 20,            output_tokens_details: { thinking_tokens: 25 },          },        },        true,        "completed",      ),    ).toEqual({      usageStatus: "complete",      usageScope: "main_agent",      hasSubagents: true,      inputTokens: 150,      cachedInputTokens: 40,      cacheCreationTokens: 10,      outputTokens: 20,      reasoningTokens: 20,    });  });   it("does not turn a zeroed Claude crash result into a measured zero-cost turn", () => {    expect(      normalizeClaudeTurnTokenUsage(        {          subtype: "error_during_execution",          usage: {            input_tokens: 0,            output_tokens: 0,          },        },        false,        "failed",      ).usageStatus,    ).toBe("unavailable");    expect(      normalizeClaudeTurnTokenUsage(        {          subtype: "success",          usage: {            input_tokens: 4,            output_tokens: 2,          },        },        false,        "interrupted",      ),    ).toMatchObject({ usageStatus: "partial", inputTokens: 4, outputTokens: 2 });  });});