From ff679ab86e0113a009d8aeeeee464139bd090874 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 7 Aug 2026 11:23:33 -0300 Subject: [PATCH] fix(sse): move Antigravity client system content to first user message to avoid upstream 429 (#9030) Closes #9030 --- .../fixes/9030-antigravity-system-429s.md | 1 + .../translator/request/openai-to-gemini.ts | 21 ++- ...repro-9030-antigravity-system-429s.test.ts | 131 ++++++++++++++++++ .../unit/translator-openai-to-gemini.test.ts | 5 +- 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 changelog.d/fixes/9030-antigravity-system-429s.md create mode 100644 tests/unit/repro-9030-antigravity-system-429s.test.ts diff --git a/changelog.d/fixes/9030-antigravity-system-429s.md b/changelog.d/fixes/9030-antigravity-system-429s.md new file mode 100644 index 0000000000..da2ebecece --- /dev/null +++ b/changelog.d/fixes/9030-antigravity-system-429s.md @@ -0,0 +1 @@ +- fix(sse): move Antigravity client system content to first user message to avoid upstream 429 RESOURCE_EXHAUSTED on oversized systemInstruction (#9030) diff --git a/open-sse/translator/request/openai-to-gemini.ts b/open-sse/translator/request/openai-to-gemini.ts index f34ed7d082..14cc4d4e5e 100644 --- a/open-sse/translator/request/openai-to-gemini.ts +++ b/open-sse/translator/request/openai-to-gemini.ts @@ -734,11 +734,24 @@ function wrapInCloudCodeEnvelope(model, cloudCodeRequest, credentials = null) { envelope._toolNameMap = cloudCodeRequest._toolNameMap; } + // #9030 — Client system content must NOT be combined with default in systemInstruction + // + // The upstream Antigravity / Cloud Code endpoint rejects oversized systemInstruction + // with 429 RESOURCE_EXHAUSTED. Keep only the lightweight ANTIGRAVITY_DEFAULT_SYSTEM + // in systemInstruction and relocate any client system content (which can be very + // large — Hermes ~125k tokens) to the first user message. const defaultPart: GeminiPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM }; - if (envelope.request.systemInstruction?.parts) { - envelope.request.systemInstruction.parts.unshift(defaultPart); - } else { - envelope.request.systemInstruction = { role: "system", parts: [defaultPart] }; + const clientParts = envelope.request.systemInstruction?.parts?.slice() ?? []; + envelope.request.systemInstruction = { role: "system", parts: [defaultPart] }; + + if (clientParts.length > 0) { + // Prepend client system parts to the first user message so they still guide + // the model's behavior early in the conversation. + if (envelope.request.contents && envelope.request.contents.length > 0) { + envelope.request.contents[0].parts.unshift(...clientParts); + } else { + envelope.request.contents = [{ role: "user", parts: [...clientParts] }]; + } } // Strip Gemini built-in tool *names* out of functionDeclarations: Antigravity's diff --git a/tests/unit/repro-9030-antigravity-system-429s.test.ts b/tests/unit/repro-9030-antigravity-system-429s.test.ts new file mode 100644 index 0000000000..5aa8b5d613 --- /dev/null +++ b/tests/unit/repro-9030-antigravity-system-429s.test.ts @@ -0,0 +1,131 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { openaiToAntigravityRequest } = + await import("../../open-sse/translator/request/openai-to-gemini.ts"); +const { ANTIGRAVITY_DEFAULT_SYSTEM } = await import("../../open-sse/config/constants.ts"); + +type PartsArray = Array<{ text?: string; functionCall?: unknown; functionResponse?: unknown }>; + +/** + * #9030 — Hermes + Antigravity system messages cause 429s + * + * Root cause: openai-to-gemini.ts's wrapInCloudCodeEnvelope prepends + * ANTIGRAVITY_DEFAULT_SYSTEM to systemInstruction.parts that already contain + * the client's system message. This doubles the systemInstruction size well + * beyond what the upstream Antigravity/Google Cloud Code endpoint accepts, + * causing a 429 RESOURCE_EXHAUSTED. + * + * Fix: move client system content to the first user message and keep only the + * default system in systemInstruction. + * + * This repro test asserts that the fix is in place — after translation, the + * systemInstruction contains ONLY ANTIGRAVITY_DEFAULT_SYSTEM, and the client's + * system text is in contents[0]. + */ +test("OpenAI -> Antigravity: client system message must NOT be in systemInstruction with default (#9030)", () => { + const result = openaiToAntigravityRequest( + "gemini-2.5-flash", + { + messages: [ + { role: "system", content: "You are a helpful assistant specialized in coding tasks." }, + { role: "user", content: "Write a function to sort an array." }, + ], + }, + false + ); + + // systemInstruction must contain ONLY the ANTIGRAVITY_DEFAULT_SYSTEM + assert.ok(result.request.systemInstruction, "systemInstruction must exist"); + assert.equal( + result.request.systemInstruction.parts.length, + 1, + "systemInstruction must have exactly 1 part (only ANTIGRAVITY_DEFAULT_SYSTEM)" + ); + assert.equal( + result.request.systemInstruction.parts[0].text, + ANTIGRAVITY_DEFAULT_SYSTEM, + "systemInstruction must contain only ANTIGRAVITY_DEFAULT_SYSTEM" + ); + + // The client system message must be moved to the first user content + const parts = result.request.contents[0]?.parts as PartsArray | undefined; + assert.ok(parts, "contents[0] must exist"); + const firstUserText = parts.map((p) => p.text ?? "").join(""); + assert.ok( + firstUserText.includes("You are a helpful assistant specialized in coding tasks."), + "client system text must be in first user message contents[0]" + ); + assert.ok( + firstUserText.includes("Write a function to sort an array."), + "user message must also be in contents[0]" + ); +}); + +test("OpenAI -> Antigravity: systemInstruction with only ANTIGRAVITY_DEFAULT_SYSTEM when no client system (#9030)", () => { + const result = openaiToAntigravityRequest( + "gemini-2.5-flash", + { + messages: [ + { role: "user", content: "Hello, how are you?" }, + ], + }, + false + ); + + // systemInstruction must still have only the ANTIGRAVITY_DEFAULT_SYSTEM + assert.ok(result.request.systemInstruction, "systemInstruction must exist"); + assert.equal( + result.request.systemInstruction.parts.length, + 1, + "systemInstruction must have exactly 1 part" + ); + assert.equal( + result.request.systemInstruction.parts[0].text, + ANTIGRAVITY_DEFAULT_SYSTEM, + "systemInstruction must contain ANTIGRAVITY_DEFAULT_SYSTEM" + ); +}); + +test("OpenAI -> Antigravity: multiple system messages all moved to first user content (#9030)", () => { + const result = openaiToAntigravityRequest( + "gemini-2.5-flash", + { + messages: [ + { role: "system", content: "Rule one: be concise." }, + { role: "system", content: "Rule two: use TypeScript." }, + { role: "user", content: "Write code." }, + ], + }, + false + ); + + // systemInstruction must contain ONLY ANTIGRAVITY_DEFAULT_SYSTEM + assert.equal( + result.request.systemInstruction.parts.length, + 1, + "systemInstruction must have exactly 1 part" + ); + assert.equal( + result.request.systemInstruction.parts[0].text, + ANTIGRAVITY_DEFAULT_SYSTEM, + "systemInstruction must contain only ANTIGRAVITY_DEFAULT_SYSTEM" + ); + + // The combined system messages + user message must be in contents[0] + const parts = result.request.contents[0]?.parts as PartsArray | undefined; + assert.ok(parts, "contents[0] must exist"); + const firstUserText = parts.map((p) => p.text ?? "").join(""); + assert.ok( + firstUserText.includes("Rule one: be concise."), + "first system text must be in contents[0]" + ); + assert.ok( + firstUserText.includes("Rule two: use TypeScript."), + "second system text must be in contents[0]" + ); + assert.ok( + firstUserText.includes("Write code."), + "user message must be in contents[0]" + ); +}); diff --git a/tests/unit/translator-openai-to-gemini.test.ts b/tests/unit/translator-openai-to-gemini.test.ts index ce1b0934db..403d1d895e 100644 --- a/tests/unit/translator-openai-to-gemini.test.ts +++ b/tests/unit/translator-openai-to-gemini.test.ts @@ -863,7 +863,10 @@ test("OpenAI -> Antigravity maps Claude-family models to Gemini-compatible schem assert.match(result.requestId, /^agent\/\d+\/[0-9a-f]{8}$/); assert.equal(result.enabledCreditTypes, undefined); assert.equal(result.request.systemInstruction.parts[0].text, ANTIGRAVITY_DEFAULT_SYSTEM); - assert.equal(result.request.systemInstruction.parts[1].text, "Project rules"); + assert.equal(result.request.systemInstruction.parts.length, 1, "systemInstruction must contain only ANTIGRAVITY_DEFAULT_SYSTEM (#9030)"); + // #9030 — Client system content moved to first user message to avoid upstream 429s + assert.equal(result.request.contents[0].parts[0].text, "Project rules"); + assert.equal(result.request.contents[0].parts[1].text, "Read a file"); assert.equal((result as any).request?.generationConfig.maxOutputTokens, undefined); assert.equal((result as any).request?.messages, undefined); assert.equal((result as any).request?.system, undefined);