From f496738d7f754754f5af7a5aa58780d4113a74c4 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 3 Jul 2026 08:35:02 -0300 Subject: [PATCH] fix(translator): strip multipleOf from antigravity/gemini tool schemas (port from 9router#2309) (#6052) `multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset, so leaving it in function_declaration parameters triggered a hard upstream 400 ("Unknown name multipleOf"). Add it to GEMINI_UNSUPPORTED_SCHEMA_KEYS so it is stripped at every schema level; minimum/maximum stay (Gemini accepts them). Reported-by: abil0321 (https://github.com/decolua/9router/issues/2309) --- CHANGELOG.md | 2 + open-sse/translator/helpers/geminiHelper.ts | 4 ++ tests/unit/gemini-multipleof-2309.test.ts | 41 +++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/unit/gemini-multipleof-2309.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index efc5455064..c26adf97a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ - **kiro (system prompt leaked as raw user text):** when Claude Code routed through the Kiro/CodeWhisperer backend, the `system` message was normalized to a `user` turn with no wrapper, so the entire system prompt (environment info, tool definitions, memory instructions, etc.) appeared as if the user had typed it — polluting the model context. System-origin content is now wrapped in `` tags before being merged into the Kiro user message, so the model can distinguish it from real user input. Real user turns are untouched. Regression guard: `tests/unit/kiro-system-reminder-2306.test.ts`. (thanks @VitzS7) +- **antigravity/gemini tool calls (`400 Unknown name "multipleOf"`):** requests routed to antigravity/gemini models with tools that declare a `multipleOf` numeric constraint failed with a hard upstream `400` (`Invalid JSON payload received. Unknown name "multipleOf"`). `multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset and was not being stripped from `function_declarations`. It is now removed at every schema level (top-level, nested, and array `items`), alongside the other unsupported constraints; `minimum`/`maximum` remain untouched. Regression guard: `tests/unit/gemini-multipleof-2309.test.ts`. (thanks @abil0321) + ### 📝 Maintenance - **test (deflake `setup-claude`):** `tests/unit/cli/setup-claude.test.ts` failed ~50% of runs with `Unable to deserialize cloned data due to invalid or unsupported version` at file teardown (all subtests passed), randomly reddening `Unit Tests fast-path (2/2)` / `Fast Quality Gates` across the PR→release queue. Root cause: `node --test` streams each file's report to the parent as V8-serialized frames on fd 1 (stdout), and the CLI helper under test (`syncClaudeProfilesFromModels`) prints progress via `console.log` — that stdout output interleaved with the serialized frames and corrupted the stream. The test now silences the stdout-writing `console` methods for the file's duration (no assertion inspects stdout), making it deterministic (15/15 green locally). ([#5959](https://github.com/diegosouzapw/OmniRoute/issues/5959)) diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 515765a2f2..5fd500b2da 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -12,6 +12,10 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([ "maxLength", "exclusiveMinimum", "exclusiveMaximum", + // `multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset; + // leaving it in function_declarations triggers a hard upstream 400 + // ("Unknown name \"multipleOf\""). `minimum`/`maximum` ARE accepted and kept. + "multipleOf", // NOTE: `pattern` is intentionally NOT in this set. Antigravity (Gemini-derived // surface) accepts `pattern` on string constraints, and glob/grep/file-search // tools depend on it to express their argument regex. Removing it produced diff --git a/tests/unit/gemini-multipleof-2309.test.ts b/tests/unit/gemini-multipleof-2309.test.ts new file mode 100644 index 0000000000..5a4f7f3fc6 --- /dev/null +++ b/tests/unit/gemini-multipleof-2309.test.ts @@ -0,0 +1,41 @@ +/** + * #2309 — antigravity/gemini returned [400] "Invalid JSON payload received. + * Unknown name \"multipleOf\" at 'request.tools[0].function_declarations[...]" + * + * Root cause: `multipleOf` (a JSON Schema numeric constraint) was NOT listed in + * `GEMINI_UNSUPPORTED_SCHEMA_KEYS`, so `cleanJSONSchemaForAntigravity` left it in + * the function-declaration parameters. The Gemini/antigravity upstream (OpenAPI + * 3.0 schema subset) rejects `multipleOf` with a hard 400. + * + * Fix: add `multipleOf` to the unsupported-keys set so it is stripped at every + * level (top-level property, nested object, and inside array `items`). Sibling + * numeric constraints `minimum`/`maximum` ARE accepted by Gemini and must stay. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + cleanJSONSchemaForAntigravity, + GEMINI_UNSUPPORTED_SCHEMA_KEYS, +} from "../../open-sse/translator/helpers/geminiHelper.ts"; + +test("#2309 multipleOf is stripped at all levels for antigravity/gemini schemas", () => { + const schema = { + type: "object", + properties: { + count: { type: "integer", multipleOf: 2, minimum: 0 }, + ratio: { type: "number", multipleOf: 0.5 }, + tags: { type: "array", items: { type: "number", multipleOf: 10 } }, + }, + }; + + const cleaned = JSON.stringify(cleanJSONSchemaForAntigravity(schema)); + + assert.ok(!cleaned.includes("multipleOf"), "multipleOf must be removed"); + // Gemini DOES support minimum/maximum — those must survive. + assert.ok(cleaned.includes("minimum"), "minimum must be preserved"); +}); + +test("#2309 multipleOf is in GEMINI_UNSUPPORTED_SCHEMA_KEYS", () => { + assert.ok(GEMINI_UNSUPPORTED_SCHEMA_KEYS.has("multipleOf")); +});