From 65e1960029b48a3b53fcc33d70cbeeb9b772a0cd Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 19 Aug 2026 12:02:48 -0300 Subject: [PATCH] fix(usage): repair zero-reported input_tokens on non-trivial requests (#10705) (#10757) Co-authored-by: Markus Hartung --- ...10705-zero-input-token-sanitization-bug.md | 1 + open-sse/utils/usageTracking.ts | 11 +++++++- ...e-tracking-zero-input-tokens-10705.test.ts | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/10705-zero-input-token-sanitization-bug.md create mode 100644 tests/unit/usage-tracking-zero-input-tokens-10705.test.ts diff --git a/changelog.d/fixes/10705-zero-input-token-sanitization-bug.md b/changelog.d/fixes/10705-zero-input-token-sanitization-bug.md new file mode 100644 index 0000000000..aa22dbd64a --- /dev/null +++ b/changelog.d/fixes/10705-zero-input-token-sanitization-bug.md @@ -0,0 +1 @@ +- fix(usage): repair provider-reported input_tokens: 0 on non-trivial requests instead of passing it through unrepaired (#10705) diff --git a/open-sse/utils/usageTracking.ts b/open-sse/utils/usageTracking.ts index 034c34d93f..c89527518e 100644 --- a/open-sse/utils/usageTracking.ts +++ b/open-sse/utils/usageTracking.ts @@ -484,7 +484,16 @@ export function sanitizeProviderUsageForRequest( const format = resolveUsageFormat(usage, targetFormat); const reportedInput = getReportedInputTokens(usage, format); - if (reportedInput <= 0 || isInputTokenCountPlausible(reportedInput, body)) { + // #10705: reportedInput === 0 was always accepted, on the theory this guard only + // needed to catch providers over-reporting huge counts. But a real, non-trivial + // request body can legitimately have its input tokens under-reported to exactly 0 + // by a relay provider. Only treat 0 as plausible when the request body itself is + // trivial (no serialized body, or a body too small to plausibly need any tokens); + // otherwise fall through to the same local-estimate repair used for over-reports. + const bodyBytesForZeroCheck = reportedInput === 0 ? getSerializedBodyBytes(body) : null; + const zeroIsPlausible = + reportedInput === 0 && (bodyBytesForZeroCheck === null || bodyBytesForZeroCheck === 0); + if (zeroIsPlausible || (reportedInput > 0 && isInputTokenCountPlausible(reportedInput, body))) { return usage; } diff --git a/tests/unit/usage-tracking-zero-input-tokens-10705.test.ts b/tests/unit/usage-tracking-zero-input-tokens-10705.test.ts new file mode 100644 index 0000000000..130556960b --- /dev/null +++ b/tests/unit/usage-tracking-zero-input-tokens-10705.test.ts @@ -0,0 +1,26 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { sanitizeProviderUsageForRequest } = await import("../../open-sse/utils/usageTracking.ts"); +const { FORMATS } = await import("../../open-sse/translator/formats.ts"); + +test("issue #10705: provider-reported input_tokens: 0 on a non-trivial request must be repaired, not passed through", () => { + const body = { + model: "claude-fable-5", + messages: [ + { role: "user", content: "你好,我想测试一下这个模型,请问你能帮我写一段代码吗?" }, + { role: "assistant", content: "你好!继续测试也行" }, + { role: "user", content: "帮我写一个 quicksort 的 python 实现,并解释一下时间复杂度。" }, + ], + }; + + const usage = { input_tokens: 0, output_tokens: 43 }; + const result = sanitizeProviderUsageForRequest(usage, body, FORMATS.CLAUDE); + assert.ok(result && result.input_tokens > 0, "input_tokens: 0 for a real body must be repaired"); +}); + +test("issue #10705: input_tokens: 0 for a genuinely empty/no-body request stays 0", () => { + const usage = { input_tokens: 0, output_tokens: 5 }; + const result = sanitizeProviderUsageForRequest(usage, undefined, FORMATS.CLAUDE); + assert.equal(result, usage, "no body to estimate from — must pass through unchanged"); +});