From fe1fd055d557acaff484be5876da27f85e6bd6dc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 10 Apr 2026 14:53:35 +0100 Subject: [PATCH] fix: sanitize Gemini tool schema required fields (#64284) (thanks @xxxxxmax) --- CHANGELOG.md | 1 + src/agents/schema/clean-for-gemini.test.ts | 11 ++++++++++- src/agents/schema/clean-for-gemini.ts | 8 +++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 498988f7cd0..84752f46242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ Docs: https://docs.openclaw.ai - Plugins: treat duplicate `registerService` calls from the same plugin id as idempotent so snapshot and activation loads no longer emit spurious `service already registered` diagnostics. (#62033, #64128) Thanks @ly85206559. - Discord/TTS: route auto voice replies through the native voice-note path so Discord receives Opus voice messages instead of regular audio attachments. (#64096) Thanks @LiuHuaize. - Config/plugins: use plugin-owned command alias metadata when `plugins.allow` contains runtime command names like `dreaming`, and point users at the owning plugin instead of stale plugin-not-found guidance. (#64242) Thanks @feiskyer. +- Agents/Gemini: strip orphaned `required` entries from Gemini tool schemas so provider validation no longer rejects tools after schema cleanup or union flattening. (#64284) Thanks @xxxxxmax. ## 2026.4.9 diff --git a/src/agents/schema/clean-for-gemini.test.ts b/src/agents/schema/clean-for-gemini.test.ts index 910a0fed6fc..a5e79d5db9e 100644 --- a/src/agents/schema/clean-for-gemini.test.ts +++ b/src/agents/schema/clean-for-gemini.test.ts @@ -68,12 +68,21 @@ describe("cleanSchemaForGemini", () => { expect(cleaned.required).toBeUndefined(); }); - it("leaves required as-is when properties is absent", () => { + it("removes required from object schemas when properties is absent", () => { const cleaned = cleanSchemaForGemini({ type: "object", required: ["a", "b"], }) as { required?: string[] }; + expect(cleaned.required).toBeUndefined(); + }); + + it("leaves required as-is for non-object schemas when properties is absent", () => { + const cleaned = cleanSchemaForGemini({ + type: "array", + required: ["a", "b"], + }) as { required?: string[] }; + expect(cleaned.required).toEqual(["a", "b"]); }); diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index 5e58942f692..783b95798c7 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -214,12 +214,18 @@ function simplifyUnionVariants(params: { obj: Record; variants: // Gemini rejects object schemas whose `required` entries do not exist in `properties`. function sanitizeRequiredFields(schema: Record): Record { + if (!Array.isArray(schema.required)) { + return schema; + } + if ( - !Array.isArray(schema.required) || !schema.properties || typeof schema.properties !== "object" || Array.isArray(schema.properties) ) { + if (schema.type === "object") { + delete schema.required; + } return schema; }