mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
fix(antigravity): preserve thought token usage (#13055)
Right fix: `thoughtsTokenCount` is real output the caller paid for, so folding it into `completion_tokens` and surfacing it as `completion_tokens_details.reasoning_tokens` matches what every other reasoning-capable provider reports. --- Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs. - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK - complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline - 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately. Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written. ⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch). Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- **fix(antigravity):** Preserve upstream thought-token usage in normalized completion and reasoning token counts ([#13055](https://github.com/diegosouzapw/OmniRoute/pull/13055)) — thanks @pacocartones
|
||||
@@ -18,8 +18,7 @@ export type AntigravityCollectedStream = {
|
||||
|
||||
// Both run once per SSE data line / per text part (processAntigravitySSEPayload),
|
||||
// so the literals are hoisted to module constants.
|
||||
const TEXTUAL_TOOL_CALL_RE =
|
||||
/^[\s\S]*?\[Tool call:\s*([^\]\n]+)\]\s*\nArguments:\s*([\s\S]+?)\s*$/;
|
||||
const TEXTUAL_TOOL_CALL_RE = /^[\s\S]*?\[Tool call:\s*([^\]\n]+)\]\s*\nArguments:\s*([\s\S]+?)\s*$/;
|
||||
|
||||
export function stripZeroWidth(value: unknown): unknown {
|
||||
if (typeof value === "string") {
|
||||
@@ -145,10 +144,14 @@ export function processAntigravitySSEPayload(
|
||||
}
|
||||
if (parsed?.response?.usageMetadata) {
|
||||
const um = parsed.response.usageMetadata;
|
||||
const thoughtsTokens = typeof um.thoughtsTokenCount === "number" ? um.thoughtsTokenCount : 0;
|
||||
collected.usage = {
|
||||
prompt_tokens: um.promptTokenCount || 0,
|
||||
completion_tokens: um.candidatesTokenCount || 0,
|
||||
completion_tokens: (um.candidatesTokenCount || 0) + thoughtsTokens,
|
||||
total_tokens: um.totalTokenCount || 0,
|
||||
...(thoughtsTokens > 0
|
||||
? { completion_tokens_details: { reasoning_tokens: thoughtsTokens } }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
if (Array.isArray(parsed?.remainingCredits)) {
|
||||
|
||||
@@ -36,6 +36,7 @@ type ChatCompletionPayload = {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
completion_tokens_details?: { reasoning_tokens: number };
|
||||
};
|
||||
};
|
||||
|
||||
@@ -482,6 +483,33 @@ test("AntigravityExecutor.collectStreamToResponse turns SSE Gemini chunks into a
|
||||
});
|
||||
});
|
||||
|
||||
test("AntigravityExecutor.collectStreamToResponse preserves upstream thought token usage", async () => {
|
||||
const executor = new AntigravityExecutor();
|
||||
const response = new Response(
|
||||
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"Done"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":3,"thoughtsTokenCount":7,"totalTokenCount":15}}}\n\n',
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
}
|
||||
);
|
||||
|
||||
const result = await executor.collectStreamToResponse(
|
||||
response,
|
||||
"gemini-3.7-pro-high",
|
||||
"https://example.com",
|
||||
{ Authorization: "Bearer ag-token" },
|
||||
{ request: {} }
|
||||
);
|
||||
const payload = (await result.response.json()) as ChatCompletionPayload;
|
||||
|
||||
assert.deepEqual(payload.usage, {
|
||||
prompt_tokens: 5,
|
||||
completion_tokens: 10,
|
||||
total_tokens: 15,
|
||||
completion_tokens_details: { reasoning_tokens: 7 },
|
||||
});
|
||||
});
|
||||
|
||||
test("AntigravityExecutor.collectStreamToResponse converts textual tool call SSE to structured tool_calls", async () => {
|
||||
const executor = new AntigravityExecutor();
|
||||
const response = new Response(
|
||||
|
||||
Reference in New Issue
Block a user