From 6828e68fe0ae26606a3759b5046bd25863fe744e Mon Sep 17 00:00:00 2001 From: Vadym O Date: Mon, 19 Oct 2026 02:03:12 +0800 Subject: [PATCH] fix(usage): a leg refused before any send does not make the row unreported A multi-attempt row takes its usage status from aggregateAttemptUsage, which let any usage-less attempt mark the whole row "unreported". A combo leg the gateway refused locally (input admission, a local cooldown) never reached upstream and carries no usage, so a combo that then served the turn on its next leg with fully reported usage was still logged as unreported. The status now ignores usage-less attempts that never sent or were answered locally -- the same "spent nothing" rule summary.ts already prices by. An attempt that was sent and reported nothing still leaves the row unreported. The existing "folds partial and unsupported" test built its unreported 503 without a send; it now notes the send, which is the case it describes. Co-Authored-By: Claude Code --- src/server/request-log.ts | 14 ++++++++----- .../request-log-attempt-identity.test.ts | 21 +++++++++++++++++++ tests/usage/request-log.test.ts | 8 +++---- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/server/request-log.ts b/src/server/request-log.ts index 096a028bff..06cb2cad60 100644 --- a/src/server/request-log.ts +++ b/src/server/request-log.ts @@ -1847,16 +1847,20 @@ export function finishRequestAttempt( export function aggregateAttemptUsage( attempts: readonly PersistedUsageAttempt[], ): FinalizedUsageResult { - const status: UsageStatus = attempts.length > 0 - && attempts.every(attempt => attempt.usageStatus === "unsupported") + // A usage-less attempt that never reached upstream spent nothing (summary.ts `spentNothing`), + // so it cannot leave the row's usage unknown. + const measured = attempts.filter(attempt => attempt.usage + || (attempt.sendCount !== 0 && attempt.locallyAnswered !== true)); + const status: UsageStatus = measured.length > 0 + && measured.every(attempt => attempt.usageStatus === "unsupported") ? "unsupported" - : attempts.some(attempt => ( + : measured.some(attempt => ( attempt.usageStatus === "unreported" || attempt.usageStatus === "unsupported" )) ? "unreported" - : attempts.some(attempt => attempt.usageStatus === "estimated") + : measured.some(attempt => attempt.usageStatus === "estimated") ? "estimated" - : attempts.length > 0 + : measured.length > 0 ? "reported" : "unreported"; diff --git a/tests/usage/request-log-attempt-identity.test.ts b/tests/usage/request-log-attempt-identity.test.ts index e6ca57f4c1..2c01b4f7c8 100644 --- a/tests/usage/request-log-attempt-identity.test.ts +++ b/tests/usage/request-log-attempt-identity.test.ts @@ -56,4 +56,25 @@ describe("request log attempt identity", () => { }, }); }); + + test("a combo leg refused before any send does not make the served row unreported", () => { + // core-combo seals an input-admission refusal like this: no send, no usage. + const refused = beginRequestAttempt(1, "first", "model-a", "openai-chat"); + finishRequestAttempt(refused, 400, 1); + const served = beginRequestAttempt(2, "second", "model-b", "openai-chat"); + noteAttemptSend(served, undefined); + finishRequestAttempt(served, 200, 8, { inputTokens: 10, outputTokens: 2 }); + + expect(refused.usageStatus).toBe("unreported"); + expect(aggregateAttemptUsage([refused, served])).toEqual({ + status: "reported", + totalTokens: 12, + usage: { inputTokens: 10, outputTokens: 2, totalTokens: 12 }, + }); + // A leg that was sent and reported nothing still leaves the total unknown. + const sentSilent = beginRequestAttempt(1, "first", "model-a", "openai-chat"); + noteAttemptSend(sentSilent, undefined); + finishRequestAttempt(sentSilent, 502, 1); + expect(aggregateAttemptUsage([sentSilent, served]).status).toBe("unreported"); + }); }); diff --git a/tests/usage/request-log.test.ts b/tests/usage/request-log.test.ts index d4e805fc4b..4dbcf591ea 100644 --- a/tests/usage/request-log.test.ts +++ b/tests/usage/request-log.test.ts @@ -547,11 +547,9 @@ describe("request log metadata", () => { 1, { inputTokens: 4, outputTokens: 1 }, ); - const unreported = finishRequestAttempt( - beginRequestAttempt(2, "b", "m2", "openai-chat"), - 503, - 1, - ); + const sent = beginRequestAttempt(2, "b", "m2", "openai-chat"); + noteAttemptSend(sent, undefined); + const unreported = finishRequestAttempt(sent, 503, 1); expect(aggregateAttemptUsage([reported, unreported])).toMatchObject({ status: "unreported", usage: { inputTokens: 4, outputTokens: 1 },