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

* 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

* ci: re-run dast-smoke (Build CLI bundle runner timeout flake)

---------

Co-authored-by: Rouzbeh <rqzbeh@users.noreply.github.com>
This commit is contained in:
Rouzbeh†
2026-08-16 06:46:32 +03:30
committed by GitHub
parent e44a409aa9
commit df226e55f4
3 changed files with 49 additions and 5 deletions

View File

@@ -0,0 +1 @@
- **fix(usage):** read Gemini `usageMetadata` out of the antigravity `{ response: {...} }` envelope so non-streaming requests log real token usage instead of `IN 0 | OUT 0` (port of decolua/9router#59d858b) ([#10430](https://github.com/diegosouzapw/OmniRoute/pull/10430)) — thanks @rqzbeh

View File

@@ -93,14 +93,17 @@ export function extractUsageFromResponse(responseBody, provider) {
};
}
// Gemini format
if (responseBody.usageMetadata && typeof responseBody.usageMetadata === "object") {
// Gemini format. Antigravity / gemini-cli wrap the payload in
// { response: { ... } } — read the envelope so non-streaming requests do
// not silently log zero usage (port of decolua/9router#59d858b).
const usageMetadata = responseBody.usageMetadata || responseBody.response?.usageMetadata;
if (usageMetadata && typeof usageMetadata === "object") {
// Gemini reports thoughts outside candidates. Fold them into completion so
// every provider keeps reasoning as a subset of completion tokens.
const thoughts = responseBody.usageMetadata.thoughtsTokenCount || 0;
const thoughts = usageMetadata.thoughtsTokenCount || 0;
return {
prompt_tokens: responseBody.usageMetadata.promptTokenCount || 0,
completion_tokens: (responseBody.usageMetadata.candidatesTokenCount || 0) + thoughts,
prompt_tokens: usageMetadata.promptTokenCount || 0,
completion_tokens: (usageMetadata.candidatesTokenCount || 0) + thoughts,
reasoning_tokens: thoughts,
};
}

View File

@@ -232,6 +232,46 @@ test("extractUsageFromResponse reads Gemini usageMetadata and thinking tokens",
});
});
test("extractUsageFromResponse reads Gemini usageMetadata from the antigravity response envelope", () => {
// Antigravity / gemini-cli wrap non-streaming payloads in { response: {...} }
// (port of decolua/9router#59d858b — previously logged zero usage).
const usage = extractUsageFromResponse(
{
response: {
usageMetadata: {
promptTokenCount: 42,
candidatesTokenCount: 13,
thoughtsTokenCount: 4,
cachedContentTokenCount: 7,
},
},
},
"antigravity"
);
assert.deepEqual(usage, {
prompt_tokens: 42,
completion_tokens: 17,
reasoning_tokens: 4,
});
});
test("extractUsageFromResponse prefers top-level usageMetadata over the envelope", () => {
const usage = extractUsageFromResponse(
{
usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 2 },
response: { usageMetadata: { promptTokenCount: 99, candidatesTokenCount: 99 } },
},
"gemini"
);
assert.deepEqual(usage, {
prompt_tokens: 1,
completion_tokens: 2,
reasoning_tokens: 0,
});
});
test("extractUsageFromResponse returns null when usage is missing", () => {
const usage = extractUsageFromResponse(
{