From ee946d8128bfe17c373df4043b6fe2d36f9e628d Mon Sep 17 00:00:00 2001 From: Dmitry Halushka <47303106+ZGltYQ@users.noreply.github.com> Date: Wed, 26 Nov 2025 08:58:20 +0200 Subject: [PATCH] fix: transform MCP tool schemas for Google/Gemini compatibility (#4538) Co-authored-by: Aiden Cline Co-authored-by: Github Action Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/provider/transform.ts | 37 ++++++++++++++++++--- packages/opencode/src/session/prompt.ts | 29 ++++++++++++---- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 2b9c53bbf..1cf53e5c6 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -254,7 +254,7 @@ export namespace ProviderTransform { return standardLimit } - export function schema(_providerID: string, _modelID: string, schema: JSONSchema.BaseSchema) { + export function schema(providerID: string, modelID: string, schema: JSONSchema.BaseSchema) { /* if (["openai", "azure"].includes(providerID)) { if (schema.type === "object" && schema.properties) { @@ -271,11 +271,40 @@ export namespace ProviderTransform { } } } - - if (providerID === "google") { - } */ + // Convert integer enums to string enums for Google/Gemini + if (providerID === "google" || modelID.includes("gemini")) { + const convertIntEnumsToStrings = (obj: any): any => { + if (obj === null || typeof obj !== "object") { + return obj + } + + if (Array.isArray(obj)) { + return obj.map(convertIntEnumsToStrings) + } + + const result: any = {} + for (const [key, value] of Object.entries(obj)) { + if (key === "enum" && Array.isArray(value)) { + // Convert all enum values to strings + result[key] = value.map((v) => String(v)) + // If we have integer type with enum, change type to string + if (result.type === "integer" || result.type === "number") { + result.type = "string" + } + } else if (typeof value === "object" && value !== null) { + result[key] = convertIntEnumsToStrings(value) + } else { + result[key] = value + } + } + return result + } + + schema = convertIntEnumsToStrings(schema) + } + return schema } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index c6721202a..a9c85caf1 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -594,6 +594,21 @@ export namespace SessionPrompt { // @ts-expect-error args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID) } + // Transform tool schemas for provider compatibility + if (args.params.tools && Array.isArray(args.params.tools)) { + args.params.tools = args.params.tools.map((tool: any) => { + // Tools at middleware level have inputSchema, not parameters + if (tool.inputSchema && typeof tool.inputSchema === "object") { + // Transform the inputSchema for provider compatibility + return { + ...tool, + inputSchema: ProviderTransform.schema(model.providerID, model.modelID, tool.inputSchema), + } + } + // If no inputSchema, return tool unchanged + return tool + }) + } return args.params }, }, @@ -733,6 +748,8 @@ export namespace SessionPrompt { if (Wildcard.all(key, enabledTools) === false) continue const execute = item.execute if (!execute) continue + + // Wrap execute to add plugin hooks and format output item.execute = async (args, opts) => { await Plugin.trigger( "tool.execute.before", @@ -760,17 +777,17 @@ export namespace SessionPrompt { const textParts: string[] = [] const attachments: MessageV2.FilePart[] = [] - for (const item of result.content) { - if (item.type === "text") { - textParts.push(item.text) - } else if (item.type === "image") { + for (const contentItem of result.content) { + if (contentItem.type === "text") { + textParts.push(contentItem.text) + } else if (contentItem.type === "image") { attachments.push({ id: Identifier.ascending("part"), sessionID: input.sessionID, messageID: input.processor.message.id, type: "file", - mime: item.mimeType, - url: `data:${item.mimeType};base64,${item.data}`, + mime: contentItem.mimeType, + url: `data:${contentItem.mimeType};base64,${contentItem.data}`, }) } // Add support for other types if needed