diff --git a/open-sse/handlers/usageExtractor.ts b/open-sse/handlers/usageExtractor.ts index e2e87983a9..92affd1c39 100644 --- a/open-sse/handlers/usageExtractor.ts +++ b/open-sse/handlers/usageExtractor.ts @@ -25,10 +25,12 @@ export function extractUsageFromResponse(responseBody, provider) { cached_tokens: responseBody.usage.prompt_tokens_details?.cached_tokens ?? responseBody.usage.input_tokens_details?.cached_tokens ?? - responseBody.usage.prompt_cache_hit_tokens, + responseBody.usage.prompt_cache_hit_tokens ?? + responseBody.usage.cached_tokens, reasoning_tokens: responseBody.usage.completion_tokens_details?.reasoning_tokens ?? - responseBody.usage.output_tokens_details?.reasoning_tokens, + responseBody.usage.output_tokens_details?.reasoning_tokens ?? + responseBody.usage.reasoning_tokens, }; } diff --git a/open-sse/utils/usageTracking.ts b/open-sse/utils/usageTracking.ts index 15e7d681d4..38913a2263 100644 --- a/open-sse/utils/usageTracking.ts +++ b/open-sse/utils/usageTracking.ts @@ -366,10 +366,12 @@ export function extractUsage(chunk) { cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? chunk.usage.input_tokens_details?.cached_tokens ?? - chunk.usage.prompt_cache_hit_tokens, + chunk.usage.prompt_cache_hit_tokens ?? + chunk.usage.cached_tokens, reasoning_tokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? - chunk.usage.output_tokens_details?.reasoning_tokens, + chunk.usage.output_tokens_details?.reasoning_tokens ?? + chunk.usage.reasoning_tokens, }); } diff --git a/tests/unit/usage-extractor.test.ts b/tests/unit/usage-extractor.test.ts index 4d64bf6eca..6c7bdf5273 100644 --- a/tests/unit/usage-extractor.test.ts +++ b/tests/unit/usage-extractor.test.ts @@ -292,3 +292,43 @@ test("extractUsage reads OpenAI streaming chunk with prompt_tokens_details", () assert.equal(usage.cached_tokens, 50); assert.equal(usage.reasoning_tokens, 20); }); + +// ── Flat field extraction tests (Xiaomi MiMo-style providers) ── + +test("extractUsageFromResponse reads flat cached_tokens and reasoning_tokens from OpenAI-compatible usage", () => { + const usage = extractUsageFromResponse( + { + usage: { + prompt_tokens: 258, + completion_tokens: 50, + total_tokens: 308, + cached_tokens: 192, + reasoning_tokens: 49, + }, + }, + "xiaomi-mimo" + ); + + assert.deepEqual(usage, { + prompt_tokens: 258, + completion_tokens: 50, + cached_tokens: 192, + reasoning_tokens: 49, + }); +}); + +test("extractUsage reads flat cached_tokens and reasoning_tokens from streaming chunk", () => { + const usage = extractUsage({ + choices: [{ delta: {}, finish_reason: "stop" }], + usage: { + prompt_tokens: 258, + completion_tokens: 50, + total_tokens: 308, + cached_tokens: 192, + reasoning_tokens: 49, + }, + }); + + assert.equal(usage.cached_tokens, 192); + assert.equal(usage.reasoning_tokens, 49); +});