fix(ai): preserved zero cache placeholder usage

- Required positive cache-read fields before treating terminal usage accounting as complete.

- Covered zero placeholder usage followed by trailing cached-token details.

Fixes #5022
This commit is contained in:
roboomp
2026-07-10 04:23:54 +00:00
parent f9ead2ac43
commit 8495931e03
2 changed files with 12 additions and 7 deletions
@@ -156,16 +156,16 @@ function firstPositiveNumber(...values: unknown[]): number {
return 0;
}
function hasCacheReadTokenField(rawUsage: object): boolean {
function hasPositiveCacheReadTokenField(rawUsage: object): boolean {
const usageLike = rawUsage as OpenAICompletionsUsageLike;
if (typeof usageLike.cached_tokens === "number") return true;
if (typeof usageLike.prompt_cache_hit_tokens === "number") return true;
if (typeof usageLike.cached_tokens === "number" && usageLike.cached_tokens > 0) return true;
if (typeof usageLike.prompt_cache_hit_tokens === "number" && usageLike.prompt_cache_hit_tokens > 0) return true;
const rawPromptTokenDetails = usageLike.prompt_tokens_details;
if (typeof rawPromptTokenDetails !== "object" || rawPromptTokenDetails === null) return false;
const promptTokenDetails = rawPromptTokenDetails as OpenAICompletionsPromptTokenDetails;
return typeof promptTokenDetails.cached_tokens === "number";
return typeof promptTokenDetails.cached_tokens === "number" && promptTokenDetails.cached_tokens > 0;
}
/**
@@ -1012,7 +1012,7 @@ const streamOpenAICompletionsOnce = (
const applyUsagePayload = (rawUsage: object): void => {
output.usage = parseChunkUsage(rawUsage, model, premiumRequestsTotal);
sawUsagePayload = true;
awaitTrailingUsageDetails = !hasCacheReadTokenField(rawUsage);
awaitTrailingUsageDetails = !hasPositiveCacheReadTokenField(rawUsage);
};
const timedOpenaiStream = iterateWithIdleTimeout(openaiStream, {
idleTimeoutMs,
@@ -88,11 +88,16 @@ describe("terminal frame without connection close", () => {
expect(Date.now() - startedAt).toBeLessThan(2_000);
}, 10_000);
it("openai-completions: keeps reading usage-only cache details after finish usage", async () => {
it("openai-completions: ignores zero cache placeholder until trailing positive cache details arrive", async () => {
const fetchMock = createNeverClosingFetch([
completionChunk({
choices: [{ index: 0, delta: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
prompt_tokens_details: { cached_tokens: 0 },
},
}),
completionChunk({
choices: [],