From cb711417e5c447a950364011ab60f4f05227b818 Mon Sep 17 00:00:00 2001 From: ChickenBreast-ky Date: Tue, 3 Feb 2026 13:26:33 +0900 Subject: [PATCH 1/2] fix(provider): strip properties/required from non-object types in Gemini schema Gemini API rejects tool schemas where 'properties' or 'required' fields are present on non-object types. This commonly occurs with MCP tools (e.g., Notion MCP) that define these fields on array or other types. Error example: GenerateContentRequest.tools[0].function_declarations[80].parameters.properties[data].properties: only allowed for OBJECT type This fix extends sanitizeGemini to remove properties/required from any schema node where type is not 'object'. Fixes issues similar to #3140, #8036 --- packages/opencode/src/provider/transform.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index c05bf75c46f..b4f1aaca4d5 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -772,6 +772,12 @@ export namespace ProviderTransform { result.items = {} } + // Remove properties/required from non-object types (Gemini rejects these) + if (result.type && result.type !== "object") { + delete result.properties + delete result.required + } + return result } From f00840b6d33e961da21388af9dfb2e772ed383c3 Mon Sep 17 00:00:00 2001 From: ChickenBreast-ky Date: Tue, 3 Feb 2026 14:07:08 +0900 Subject: [PATCH 2/2] test(provider): add tests for non-object properties removal in Gemini schema Add test cases for sanitizeGemini to verify: - properties removed from non-object types (string, number, array) - required removed from non-object types - nested non-object types are handled recursively - object types retain properties and required - non-gemini providers are unaffected --- .../opencode/test/provider/transform.test.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 8e28f1209e1..0743049fe06 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -293,6 +293,116 @@ describe("ProviderTransform.schema - gemini array items", () => { }) }) +describe("ProviderTransform.schema - gemini non-object properties removal", () => { + const geminiModel = { + providerID: "google", + api: { + id: "gemini-3-pro", + }, + } as any + + test("removes properties from non-object types", () => { + const schema = { + type: "object", + properties: { + data: { + type: "string", + properties: { invalid: { type: "string" } }, + }, + }, + } as any + + const result = ProviderTransform.schema(geminiModel, schema) as any + + expect(result.properties.data.type).toBe("string") + expect(result.properties.data.properties).toBeUndefined() + }) + + test("removes required from non-object types", () => { + const schema = { + type: "object", + properties: { + data: { + type: "array", + items: { type: "string" }, + required: ["invalid"], + }, + }, + } as any + + const result = ProviderTransform.schema(geminiModel, schema) as any + + expect(result.properties.data.type).toBe("array") + expect(result.properties.data.required).toBeUndefined() + }) + + test("removes properties and required from nested non-object types", () => { + const schema = { + type: "object", + properties: { + outer: { + type: "object", + properties: { + inner: { + type: "number", + properties: { bad: { type: "string" } }, + required: ["bad"], + }, + }, + }, + }, + } as any + + const result = ProviderTransform.schema(geminiModel, schema) as any + + expect(result.properties.outer.properties.inner.type).toBe("number") + expect(result.properties.outer.properties.inner.properties).toBeUndefined() + expect(result.properties.outer.properties.inner.required).toBeUndefined() + }) + + test("keeps properties and required on object types", () => { + const schema = { + type: "object", + properties: { + data: { + type: "object", + properties: { name: { type: "string" } }, + required: ["name"], + }, + }, + } as any + + const result = ProviderTransform.schema(geminiModel, schema) as any + + expect(result.properties.data.type).toBe("object") + expect(result.properties.data.properties).toBeDefined() + expect(result.properties.data.required).toEqual(["name"]) + }) + + test("does not affect non-gemini providers", () => { + const openaiModel = { + providerID: "openai", + api: { + id: "gpt-4", + }, + } as any + + const schema = { + type: "object", + properties: { + data: { + type: "string", + properties: { invalid: { type: "string" } }, + }, + }, + } as any + + const result = ProviderTransform.schema(openaiModel, schema) as any + + expect(result.properties.data.properties).toBeDefined() + }) +}) + describe("ProviderTransform.message - DeepSeek reasoning content", () => { test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => { const msgs = [