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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(usage): repair provider-reported input_tokens: 0 on non-trivial requests instead of passing it through unrepaired (#10705)
11 changes: 10 additions & 1 deletion open-sse/utils/usageTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
26 changes: 26 additions & 0 deletions tests/unit/usage-tracking-zero-input-tokens-10705.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
Loading