From 40c2eebcff253050b221282950e0383cc637ab05 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sat, 8 Aug 2026 19:01:53 +0900 Subject: [PATCH] fix(usage): reject entries without provider labels --- src/usage/log.ts | 14 ++++++++++---- tests/usage-log.test.ts | 7 +++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/usage/log.ts b/src/usage/log.ts index 23432c0764..0718e81e99 100644 --- a/src/usage/log.ts +++ b/src/usage/log.ts @@ -615,8 +615,8 @@ export function readUsageEntries(): PersistedUsageEntry[] { for (const line of lines) { if (!line.trim()) continue; try { - const parsed = JSON.parse(line) as PersistedUsageEntry; - if (parsed && typeof parsed === "object" && typeof parsed.requestId === "string") { + const parsed: unknown = JSON.parse(line); + if (isPersistedUsageEntry(parsed)) { entries.push(normalizeUsageEntry(parsed)); } } catch { @@ -626,13 +626,19 @@ export function readUsageEntries(): PersistedUsageEntry[] { return entries; } +function isPersistedUsageEntry(value: unknown): value is PersistedUsageEntry { + if (!value || typeof value !== "object") return false; + const entry = value as Partial; + return typeof entry.requestId === "string" && typeof entry.provider === "string"; +} + function parseUsageLines(lines: string[]): PersistedUsageEntry[] { const entries: PersistedUsageEntry[] = []; for (const line of lines) { if (!line.trim()) continue; try { - const parsed = JSON.parse(line) as PersistedUsageEntry; - if (parsed && typeof parsed === "object" && typeof parsed.requestId === "string") { + const parsed: unknown = JSON.parse(line); + if (isPersistedUsageEntry(parsed)) { entries.push(normalizeUsageEntry(parsed)); } } catch { diff --git a/tests/usage-log.test.ts b/tests/usage-log.test.ts index b80c2cef36..f97c9eed68 100644 --- a/tests/usage-log.test.ts +++ b/tests/usage-log.test.ts @@ -141,7 +141,7 @@ describe("usage log", () => { test("usage byte-prefix truncation and entry-count truncation report independent metadata", async () => { writeFileSync( usageLogPath(), - `${Array.from({ length: 500_001 }, (_, index) => JSON.stringify({ requestId: String(index) })).join("\n")}\n`, + `${Array.from({ length: 500_001 }, (_, index) => JSON.stringify({ requestId: String(index), provider: "p" })).join("\n")}\n`, ); const snapshot = await readUsageSnapshotForManagement(); expect(snapshot.entries).toHaveLength(500_000); @@ -654,14 +654,17 @@ describe("usage log", () => { }]); }); - test("skips malformed JSONL lines while keeping valid entries", () => { + test("skips malformed JSONL and provider-less entries while keeping valid entries", async () => { writeFileSync(usageLogPath(), [ "{\"requestId\":\"a\",\"timestamp\":1,\"provider\":\"p\",\"model\":\"m\",\"status\":200,\"durationMs\":1,\"usageStatus\":\"unreported\"}", "{not-json", + "{\"requestId\":\"missing-provider\",\"timestamp\":2}", + "{\"requestId\":\"invalid-provider\",\"timestamp\":2,\"provider\":42}", "{\"requestId\":\"b\",\"timestamp\":2,\"provider\":\"p\",\"model\":\"m\",\"status\":200,\"durationMs\":1,\"usageStatus\":\"reported\",\"usage\":{\"inputTokens\":1,\"outputTokens\":2},\"totalTokens\":3}", ].join("\n")); expect(readUsageEntries().map(entry => entry.requestId)).toEqual(["a", "b"]); + expect((await readUsageEntriesForManagement()).map(entry => entry.requestId)).toEqual(["a", "b"]); }); test("keeps missing usage distinct from zero usage", () => {