fix(antigravity): strip generationConfig.thinkingConfig for Claude models (#2217)

Integrated into release/v3.8.0
This commit is contained in:
Anton
2026-05-14 05:15:40 +02:00
committed by GitHub
parent 9ae31e7f05
commit e2b4c2b06e
2 changed files with 49 additions and 10 deletions

View File

@@ -547,6 +547,10 @@ export function openaiToAntigravityRequest(model, body, stream, credentials = nu
// Match real Antigravity client: don't send maxOutputTokens when the user
// hasn't explicitly specified max_tokens / max_completion_tokens.
// The Cloud Code server decides the output limit on its own.
// Note: read hasThinking BEFORE stripping thinkingConfig below — for Claude
// models the Cloud Code envelope still carries a thinkingBudget set upstream
// by applyAntigravityGenerationDefaults, which we must consult here so we
// do not accidentally drop the maxOutputTokens it bumped for us.
const clientRequestedMaxTokens = body.max_tokens ?? body.max_completion_tokens;
const hasThinking = !!envelope.request?.generationConfig?.thinkingConfig?.thinkingBudget;
if (
@@ -557,6 +561,16 @@ export function openaiToAntigravityRequest(model, body, stream, credentials = nu
delete envelope.request.generationConfig.maxOutputTokens;
}
// Claude models on Antigravity use their own native thinking — Gemini's thinkingConfig
// is not understood by the Cloud Code Claude endpoint and must be stripped.
// applyAntigravityGenerationDefaults (inside wrapInCloudCodeEnvelope) already bumped
// maxOutputTokens to thinkingBudget+1 before we get here, so the budget is preserved.
// Must run AFTER the hasThinking-derived maxOutputTokens decision above so the
// budget is accounted for before the field is removed.
if (isClaude && envelope.request?.generationConfig) {
delete envelope.request.generationConfig.thinkingConfig;
}
return envelope;
}

View File

@@ -756,7 +756,11 @@ test("OpenAI -> Antigravity Claude path sanitizes tool names for Gemini schema",
assert.deepEqual(toolResultBlock.response, { result: { ok: true } });
});
test("OpenAI -> Antigravity Claude path applies output cap in generationConfig", () => {
test("OpenAI -> Antigravity Claude path applies output cap and strips thinkingConfig", () => {
// For Claude on Antigravity, applyAntigravityGenerationDefaults must bump
// maxOutputTokens to thinkingBudget+1 BEFORE the envelope strips thinkingConfig
// (because Claude on Cloud Code does not understand Gemini's thinkingConfig
// shape but still benefits from the larger output cap derived from it).
const result = openaiToAntigravityRequest(
"claude-3-7-sonnet",
{
@@ -769,15 +773,14 @@ test("OpenAI -> Antigravity Claude path applies output cap in generationConfig",
);
assert.equal((result as any).request?.generationConfig.maxOutputTokens, 32769);
assert.deepEqual((result as any).request?.generationConfig.thinkingConfig, {
thinkingBudget: 32768,
includeThoughts: true,
});
// thinkingConfig must be stripped for Claude — Cloud Code Claude endpoint
// does not understand the Gemini-shape thinkingConfig field.
assert.equal((result as any).request?.generationConfig.thinkingConfig, undefined);
assert.equal((result as any).request?.max_tokens, undefined);
assert.equal((result as any).request?.thinking, undefined);
});
test("OpenAI -> Antigravity Claude path preserves lower requested output", () => {
test("OpenAI -> Antigravity Claude path preserves lower requested output and strips thinkingConfig", () => {
const result = openaiToAntigravityRequest(
"claude-3-7-sonnet",
{
@@ -790,10 +793,32 @@ test("OpenAI -> Antigravity Claude path preserves lower requested output", () =>
);
assert.equal((result as any).request?.generationConfig.maxOutputTokens, 32769);
assert.deepEqual((result as any).request?.generationConfig.thinkingConfig, {
thinkingBudget: 32768,
includeThoughts: true,
});
assert.equal((result as any).request?.generationConfig.thinkingConfig, undefined);
assert.equal((result as any).request?.max_tokens, undefined);
assert.equal((result as any).request?.thinking, undefined);
});
test("OpenAI -> Antigravity Gemini path preserves thinkingConfig (only Claude is stripped)", () => {
// Negative-control for the Claude-thinkingConfig-strip behavior. Gemini models
// on Antigravity must still receive thinkingConfig — only Claude needs it removed
// (Cloud Code Claude endpoint does not understand the Gemini-shape field).
const result = openaiToAntigravityRequest(
"gemini-2.5-pro",
{
messages: [{ role: "user", content: "Summarize this" }],
max_completion_tokens: 32000,
reasoning_effort: "high",
},
false,
{ projectId: "proj-gemini-thinking" } as any
);
// For Gemini, thinkingConfig must remain in place because the Cloud Code
// Gemini endpoint understands and uses it.
assert.ok(
(result as any).request?.generationConfig.thinkingConfig,
"thinkingConfig must be preserved for Gemini models on Antigravity"
);
assert.equal((result as any).request?.generationConfig.thinkingConfig.thinkingBudget > 0, true);
assert.equal((result as any).request?.generationConfig.thinkingConfig.includeThoughts, true);
});