fix(usage): surface Gemini cachedContentTokenCount as cached_tokens (#10465)

* fix(usage): read Gemini usageMetadata out of the antigravity response envelope

Port decolua/9router#59d858b: antigravity/gemini-cli wrap non-streaming
payloads in { response: {...} }, so extractUsageFromResponse only saw the
top-level usageMetadata and every non-streaming antigravity request logged
zero usage (IN 0 | OUT 0) and zeroed usage-dashboard rows. Top-level
metadata keeps priority; OpenAI/Claude branches untouched.

* chore(changelog): fragment for #10430 antigravity usage envelope

* fix(usage): surface Gemini cachedContentTokenCount as cached_tokens

Review follow-up on #10430: the Gemini branch of extractUsageFromResponse
ignored cachedContentTokenCount, so non-streaming cache-hit tokens never
reached the cached_tokens field the OpenAI/Claude/Responses branches
already populate (and the streaming path surfaces at usageTracking.ts:684).

Adds cached_tokens: usageMetadata.cachedContentTokenCount || 0, updates
the three Gemini assertions (envelope fixture already carried
cachedContentTokenCount: 7), and adds a dedicated regression test.

* chore(changelog): fragment for #10465 Gemini cached_tokens surfacing

* test(fix): refresh expired alibaba quota sample validity and onnxruntime pin for v3.8.50 base

- alibaba-free-tier-quota-fetcher.test.ts: sample quotaValidityPeriod
  (2026-08-16 16:00 UTC) is in the past, making every quota entry classify
  as expired/not_capable; bump to 2028-01-01 UTC so the text/merge
  classification tests exercise the intended path again.
- optional-transformers-dependency.test.ts: onnxruntime-node pin assertion
  updated from ~1.24.3 to ~1.27.0 to match package.json (bumped by #10403);
  the regular-not-optional intent is unchanged.

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
This commit is contained in:
Rouzbeh†
2026-08-17 14:58:03 +03:30
committed by GitHub
parent 6c50137eeb
commit 7f5275ed6b
3 changed files with 28 additions and 0 deletions

View File

@@ -0,0 +1 @@
- **fix(usage):** surface Gemini `cachedContentTokenCount` into `cached_tokens` for non-streaming requests so cache-hit accounting matches the OpenAI/Claude/Responses branches and the streaming path (follow-up to the #10430 envelope fix) ([#10465](https://github.com/diegosouzapw/OmniRoute/pull/10465)) — thanks @rqzbeh

View File

@@ -104,6 +104,7 @@ export function extractUsageFromResponse(responseBody, provider) {
return {
prompt_tokens: usageMetadata.promptTokenCount || 0,
completion_tokens: (usageMetadata.candidatesTokenCount || 0) + thoughts,
cached_tokens: usageMetadata.cachedContentTokenCount || 0,
reasoning_tokens: thoughts,
};
}

View File

@@ -228,6 +228,7 @@ test("extractUsageFromResponse reads Gemini usageMetadata and thinking tokens",
assert.deepEqual(usage, {
prompt_tokens: 11,
completion_tokens: 7,
cached_tokens: 0,
reasoning_tokens: 2,
});
});
@@ -252,6 +253,7 @@ test("extractUsageFromResponse reads Gemini usageMetadata from the antigravity r
assert.deepEqual(usage, {
prompt_tokens: 42,
completion_tokens: 17,
cached_tokens: 7,
reasoning_tokens: 4,
});
});
@@ -268,10 +270,34 @@ test("extractUsageFromResponse prefers top-level usageMetadata over the envelope
assert.deepEqual(usage, {
prompt_tokens: 1,
completion_tokens: 2,
cached_tokens: 0,
reasoning_tokens: 0,
});
});
test("extractUsageFromResponse surfaces Gemini cachedContentTokenCount as cached_tokens", () => {
// Review follow-up on #10430: match the OpenAI/Claude/Responses branches and
// the streaming path (usageTracking.ts) by surfacing Gemini cache-hit tokens.
const usage = extractUsageFromResponse(
{
usageMetadata: {
promptTokenCount: 30,
candidatesTokenCount: 10,
thoughtsTokenCount: 3,
cachedContentTokenCount: 12,
},
},
"gemini"
);
assert.deepEqual(usage, {
prompt_tokens: 30,
completion_tokens: 13,
cached_tokens: 12,
reasoning_tokens: 3,
});
});
test("extractUsageFromResponse returns null when usage is missing", () => {
const usage = extractUsageFromResponse(
{