Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion apps/server/src/provider/Drivers/claudeUsage.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "@effect/vitest";

import { parseClaudeLine } from "./claudeUsage.ts";
import { claudeUsageFormat, parseClaudeLine } from "./claudeUsage.ts";

function claudeLine(overrides: {
messageId: string;
Expand Down Expand Up @@ -66,6 +66,35 @@ describe("parseClaudeLine", () => {
expect(text?.totals).toEqual(toolUse?.totals);
});

it("skips local placeholders in both reader paths but keeps real zero-token responses", () => {
const projected = {
type: "assistant",
timestamp: "2026-08-07T04:05:13.944Z",
message: {
model: "claude-fable-5",
usage: { input_tokens: 0, output_tokens: 0 },
},
};
const expected = [
expect.objectContaining({
model: "claude-fable-5",
totals: {
uncachedInputTokens: 0,
cachedInputTokens: 0,
cacheCreationTokens: 0,
outputTokens: 0,
reasoningTokens: 0,
},
}),
];
expect(claudeUsageFormat.parseLine(JSON.stringify(projected), undefined)).toEqual(expected);
expect(claudeUsageFormat.parseProjected(projected, undefined)).toEqual(expected);

const synthetic = { ...projected, message: { ...projected.message, model: "<synthetic>" } };
expect(claudeUsageFormat.parseLine(JSON.stringify(synthetic), undefined)).toEqual([]);
expect(claudeUsageFormat.parseProjected(synthetic, undefined)).toEqual([]);
});

it("ignores records that are not assistant messages", () => {
expect(parseClaudeLine(JSON.stringify({ type: "user", message: {} }))).toBeNull();
expect(parseClaudeLine("not json")).toBeNull();
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/provider/Drivers/claudeUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
} from "@t3tools/provider-core/server/usage";
import * as HostProcess from "@t3tools/shared/HostProcess";

// Claude Code uses this model for local placeholders that never reached the API.
export const CLAUDE_SYNTHETIC_MODEL = "<synthetic>";

/**
* T3 Code writes one record per assistant *content block*, and every one of those records repeats
* the same complete `usage` object for the parent message. Summing them overcounts by roughly
Expand Down Expand Up @@ -45,7 +48,7 @@ function parseClaudeRecord(parsed: unknown): UsageRecord | null {
if (timestampMs === null) return null;

const model = typeof messageRecord["model"] === "string" ? messageRecord["model"] : "";
if (model.length === 0) return null;
if (model.length === 0 || model === CLAUDE_SYNTHETIC_MODEL) return null;

const messageId = typeof messageRecord["id"] === "string" ? messageRecord["id"] : null;
const requestId = typeof record["requestId"] === "string" ? record["requestId"] : null;
Expand Down
45 changes: 36 additions & 9 deletions apps/server/src/usage/usageScanCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,40 @@ function cacheWith(entries: readonly [string, number, readonly UsageRecord[]][])
}

describe("scan cache round trip", () => {
it("restores records unchanged", () => {
it("restores cached history unchanged except for Claude local placeholders", () => {
const original = cacheWith([
[
"/a.jsonl",
100,
[record(), record({ dedupeKey: "msg_2:", model: "claude-opus-5-5", speed: "fast" })],
],
["/b.jsonl", 200, [record({ sessionId: "session-b", reportedCostUsd: 1.5 })]],
[
"/b.jsonl",
200,
[
record({
sessionId: "session-b",
reportedCostUsd: 1.5,
totals: {
uncachedInputTokens: 0,
cachedInputTokens: 0,
cacheCreationTokens: 0,
outputTokens: 0,
reasoningTokens: 0,
},
}),
],
],
]);
original.set("/grok.jsonl", {
size: 40,
mtimeMs: 300,
provider: "grok",
records: [
record({ provider: "grok", model: "grok-4.5-build", dedupeKey: "s:p:grok-4.5-build" }),
record({ provider: "grok", model: "<synthetic>", dedupeKey: "s:p:<synthetic>" }),
],
tailRecords: [record({ provider: "grok", model: "grok-4.5-build", dedupeKey: null })],
tailRecords: [record({ provider: "grok", model: "<synthetic>", dedupeKey: null })],
position: position({ resumeOffset: 30, guardLength: 30, guardHash: 123 }),
});
original.set("/codex.jsonl", {
Expand All @@ -99,16 +116,26 @@ describe("scan cache round trip", () => {
}),
});

original.set("/a.jsonl", {
...original.get("/a.jsonl")!,
tailRecords: [record({ dedupeKey: "msg_tail:" })],
});
const claude = original.get("/a.jsonl")!;
const cached = new Map(original);
cached.set("/a.jsonl", {
...claude,
records: [...claude.records, record({ model: "<synthetic>" })],
tailRecords: [
...claude.tailRecords,
record({ model: "<synthetic>", dedupeKey: "msg_tail:" }),
],
});
const restored = decodeScanCache(
JSON.parse(JSON.stringify(encodeScanCache(original))),
JSON.parse(JSON.stringify(encodeScanCache(cached))),
TEST_FORMAT_MAP,
);

expect(restored.size).toBe(4);
expect(restored.get("/a.jsonl")).toEqual(original.get("/a.jsonl"));
expect(restored.get("/b.jsonl")).toEqual(original.get("/b.jsonl"));
expect(restored.get("/grok.jsonl")).toEqual(original.get("/grok.jsonl"));
expect(restored.get("/codex.jsonl")).toEqual(original.get("/codex.jsonl"));
expect(restored).toEqual(original);
});

it("drops an entry whose persisted parse state is corrupt", () => {
Expand Down
5 changes: 5 additions & 0 deletions apps/server/src/usage/usageScanCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
} from "@t3tools/provider-core/server/usage";
import * as Schema from "effect/Schema";

import { CLAUDE_SYNTHETIC_MODEL } from "../provider/Drivers/claudeUsage.ts";
import { GUARD_LENGTH, type TranscriptParsePosition } from "./usageTranscriptReader.ts";

const USAGE_SCAN_CACHE_VERSION = 5 as const;
Expand Down Expand Up @@ -237,6 +238,10 @@ export function decodeScanCache(
return null;
}

// Unchanged and deleted transcripts may only exist in the cache, so the
// parser cannot remove their previously saved local placeholders.
if (provider === "claude" && model === CLAUDE_SYNTHETIC_MODEL) continue;

records.push({
provider,
timestampMs,
Expand Down
Loading