mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 05:12:11 +03:00
* fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331) * fix(sse): scope #8331's usage-buffer fix around Claude-Code-compatible providers The #8331 fix correctly stopped folding the 2000-token context-window safety margin into client-visible prompt_tokens/input_tokens/total_tokens for normal API metering clients. But it also silently changed the response shape for Claude-Code-compatible providers, whose own context accounting reads the buffered number straight out of usage — regressing tests/unit/cc-compatible-provider.test.ts (expected 2007, got 7). Fold the computed context_budget_* fields back into the visible usage fields for that one path only (applyClientUsageBuffer's new preserveContextBudgetInVisibleUsage option, gated on the existing isClaudeCodeCompatible flag in chatCore.ts). Every other caller keeps the real, unbuffered #8331 numbers.
138 lines
5.4 KiB
TypeScript
138 lines
5.4 KiB
TypeScript
// Regression guard for #8331 — the DEFAULT_BUFFER_TOKENS (2000) context-window safety margin
|
|
// was leaking straight into the CLIENT-VISIBLE usage.prompt_tokens/input_tokens/total_tokens,
|
|
// so a real 69-token upstream request was reported to the client as 2069. call_logs.tokens_in
|
|
// and the raw upstream body always showed the true 69 — only the client-facing response was
|
|
// inflated.
|
|
//
|
|
// Fix: addBufferToUsage() (open-sse/utils/usageTracking.ts) no longer mutates the metering
|
|
// fields. The buffer is still computed (context-fit/CLI-headroom use is preserved) but is now
|
|
// surfaced only via separate context_budget_* fields, which filterUsageForFormat() does not
|
|
// allow-list — so it never reaches a client response, streaming or non-streaming.
|
|
//
|
|
// Covers both client-visible seams:
|
|
// 1. Non-streaming JSON usage — via applyClientUsageBuffer() (chatCore.ts:4236 call site).
|
|
// 2. Streaming SSE usage frame — via the same addBufferToUsage()+filterUsageForFormat() chain
|
|
// used verbatim by open-sse/utils/stream.ts:1018 and :1897.
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
addBufferToUsage,
|
|
filterUsageForFormat,
|
|
invalidateBufferTokensCache,
|
|
} from "../../open-sse/utils/usageTracking.ts";
|
|
import { applyClientUsageBuffer } from "../../open-sse/handlers/chatCore/clientUsageBuffer.ts";
|
|
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
|
|
|
function resetEnv(saved: string | undefined) {
|
|
if (saved === undefined) {
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
} else {
|
|
process.env.USAGE_TOKEN_BUFFER = saved;
|
|
}
|
|
}
|
|
|
|
test("#8331 non-streaming: client-facing usage.prompt_tokens equals raw upstream value, not +2000", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
invalidateBufferTokensCache();
|
|
|
|
// Reporter's real-world shape: 69 prompt tokens, 5 completion tokens.
|
|
const upstreamUsage = { prompt_tokens: 69, completion_tokens: 5, total_tokens: 74 };
|
|
const translatedResponse: {
|
|
usage?: unknown;
|
|
choices?: Array<{ message?: { content?: unknown } }>;
|
|
} = { usage: upstreamUsage };
|
|
|
|
applyClientUsageBuffer(translatedResponse, { messages: [] }, "openai");
|
|
|
|
const clientUsage = translatedResponse.usage as Record<string, unknown>;
|
|
assert.equal(clientUsage.prompt_tokens, 69, "must report the real upstream prompt_tokens");
|
|
assert.equal(clientUsage.completion_tokens, 5);
|
|
assert.equal(clientUsage.total_tokens, 74, "must report the real upstream total_tokens");
|
|
// The safety buffer must never leak into the client payload under any field name.
|
|
assert.equal("context_budget_prompt_tokens" in clientUsage, false);
|
|
assert.equal("context_budget_total_tokens" in clientUsage, false);
|
|
|
|
resetEnv(saved);
|
|
invalidateBufferTokensCache();
|
|
});
|
|
|
|
test("#8331 streaming: SSE usage frame (addBufferToUsage + filterUsageForFormat chain) is not inflated", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
invalidateBufferTokensCache();
|
|
|
|
// Mirrors the exact call chain used at stream.ts:1018/:1897 to build the final SSE usage chunk.
|
|
const upstreamUsage = { prompt_tokens: 69, completion_tokens: 5, total_tokens: 74 };
|
|
const buffered = addBufferToUsage(upstreamUsage);
|
|
const clientFrameUsage = filterUsageForFormat(buffered, FORMATS.OPENAI) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
|
|
assert.equal(clientFrameUsage.prompt_tokens, 69, "SSE usage frame must not be buffer-inflated");
|
|
assert.equal(clientFrameUsage.total_tokens, 74);
|
|
assert.equal("context_budget_prompt_tokens" in clientFrameUsage, false);
|
|
|
|
resetEnv(saved);
|
|
invalidateBufferTokensCache();
|
|
});
|
|
|
|
test("#8331 Claude-format streaming frame: input_tokens stays real, not buffered", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
invalidateBufferTokensCache();
|
|
|
|
const upstreamUsage = { input_tokens: 69, output_tokens: 5 };
|
|
const buffered = addBufferToUsage(upstreamUsage);
|
|
const clientFrameUsage = filterUsageForFormat(buffered, FORMATS.CLAUDE) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
|
|
assert.equal(clientFrameUsage.input_tokens, 69);
|
|
|
|
resetEnv(saved);
|
|
invalidateBufferTokensCache();
|
|
});
|
|
|
|
test("#8331 addBufferToUsage still computes the safety margin internally (buffer not deleted)", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
invalidateBufferTokensCache();
|
|
|
|
// The context-fit safety margin must still be computable for internal/future consumers —
|
|
// this fix decouples it from client metering, it does not remove the feature.
|
|
const result = addBufferToUsage({
|
|
prompt_tokens: 69,
|
|
completion_tokens: 5,
|
|
total_tokens: 74,
|
|
}) as Record<string, unknown>;
|
|
|
|
assert.equal(result.prompt_tokens, 69);
|
|
assert.equal(result.context_budget_prompt_tokens, 2069, "buffer must still be computed");
|
|
assert.equal(result.context_budget_total_tokens, 2074);
|
|
|
|
resetEnv(saved);
|
|
invalidateBufferTokensCache();
|
|
});
|
|
|
|
test("#8331 estimated usage remains fully unbuffered (unaffected by the fix)", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
delete process.env.USAGE_TOKEN_BUFFER;
|
|
invalidateBufferTokensCache();
|
|
|
|
const result = addBufferToUsage({
|
|
prompt_tokens: 6,
|
|
completion_tokens: 1,
|
|
total_tokens: 7,
|
|
estimated: true,
|
|
}) as Record<string, unknown>;
|
|
|
|
assert.equal(result.prompt_tokens, 6);
|
|
assert.equal("context_budget_prompt_tokens" in result, false);
|
|
|
|
resetEnv(saved);
|
|
invalidateBufferTokensCache();
|
|
});
|