From 2357590a62e623a998fc836e3e70fb8ecc130fbc Mon Sep 17 00:00:00 2001 From: WITALO ROCHA Date: Thu, 23 Jul 2026 04:34:09 -0300 Subject: [PATCH] fix(gemini): strip OpenAI "strict" tool-schema keyword for Antigravity (#7901) RubyLLM (and other OpenAI-convention clients) embed strict:true/false directly inside a function tools parameters JSON schema. Gemini/Antigravity rejects the unrecognized keyword with a 400 ("Unknown name strict ... Cannot find field"), the same failure class as the existing multipleOf entry. Broke every Chatwit Captain tool-calling call routed through witdev_antigravity/gemini-*. Reconstructed onto current release/v3.8.49 tip (preserves #8231 CIVIC_INTEGRITY exclusion; the author's stale base showed it as a spurious revert). Co-authored-by: Witroch4 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- open-sse/translator/helpers/geminiHelper.ts | 5 ++ tests/unit/gemini-strict-tool-schema.test.ts | 66 ++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/unit/gemini-strict-tool-schema.test.ts diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 19ce6387ee..727fcbbdec 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -16,6 +16,11 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([ // leaving it in function_declarations triggers a hard upstream 400 // ("Unknown name \"multipleOf\""). `minimum`/`maximum` ARE accepted and kept. "multipleOf", + // OpenAI "strict" tool-calling mode embeds `strict: true/false` directly inside + // a function's `parameters` schema (RubyLLM and other OpenAI-convention clients + // do this by default). Gemini's function_declarations schema doesn't recognize + // it and 400s the same way ("Unknown name \"strict\" ... Cannot find field"). + "strict", // 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-strict-tool-schema.test.ts b/tests/unit/gemini-strict-tool-schema.test.ts new file mode 100644 index 0000000000..9745353527 --- /dev/null +++ b/tests/unit/gemini-strict-tool-schema.test.ts @@ -0,0 +1,66 @@ +/** + * antigravity/gemini returned [400] "Invalid JSON payload received. + * Unknown name \"strict\" at 'request.tools[0].function_declarations[0].parameters': + * Cannot find field." + * + * Root cause: OpenAI-convention clients (RubyLLM and others) embed + * `strict: true/false` directly inside a tool's `parameters` JSON schema as part + * of OpenAI's strict tool-calling mode. `strict` was NOT listed in + * `GEMINI_UNSUPPORTED_SCHEMA_KEYS`, so `cleanJSONSchemaForAntigravity` left it in + * the function-declaration parameters, and the Gemini/antigravity upstream + * (OpenAPI 3.0 schema subset) rejects the unrecognized keyword with a hard 400. + * + * Fix: add `strict` to the unsupported-keys set so it is stripped at every level. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + cleanJSONSchemaForAntigravity, + GEMINI_UNSUPPORTED_SCHEMA_KEYS, +} from "../../open-sse/translator/helpers/geminiHelper.ts"; +import { openaiToGeminiRequest } from "../../open-sse/translator/request/openai-to-gemini.ts"; + +test("strict is stripped at all levels for antigravity/gemini schemas", () => { + const schema = { + type: "object", + strict: true, + properties: { + query: { type: "string" }, + filters: { type: "object", strict: false, properties: {} }, + }, + }; + + const cleaned = JSON.stringify(cleanJSONSchemaForAntigravity(schema)); + + assert.ok(!cleaned.includes("strict"), "strict must be removed"); + assert.ok(cleaned.includes("query"), "unrelated properties must be preserved"); +}); + +test("strict is in GEMINI_UNSUPPORTED_SCHEMA_KEYS", () => { + assert.ok(GEMINI_UNSUPPORTED_SCHEMA_KEYS.has("strict")); +}); + +test("OpenAI -> Gemini request strips strict from OpenAI-style function tool parameters", () => { + const body = { + messages: [{ role: "user", content: "hi" }], + tools: [ + { + type: "function", + function: { + name: "search_conversations", + description: "search", + parameters: { type: "object", strict: true, properties: { query: { type: "string" } } }, + }, + }, + ], + }; + + const result = openaiToGeminiRequest("gemini-3.5-flash-low", body, false) as { + tools?: Array<{ functionDeclarations?: Array<{ parameters: unknown }> }>; + }; + + const parameters = result.tools?.[0]?.functionDeclarations?.[0]?.parameters; + assert.ok(parameters, "expected a translated function declaration"); + assert.ok(!JSON.stringify(parameters).includes("strict"), "strict must not reach the upstream request"); +});