summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDmitry Halushka <[email protected]>2025-11-26 08:58:20 +0200
committerGitHub <[email protected]>2025-11-26 00:58:20 -0600
commitee946d8128bfe17c373df4043b6fe2d36f9e628d (patch)
treef6b6e4a9a5cafb8d4f118eca8a6bd0b4bf087f9c
parentec8f2e078e519f0bf4fae56892ea7fe073301d50 (diff)
downloadopencode-ee946d8128bfe17c373df4043b6fe2d36f9e628d.tar.gz
opencode-ee946d8128bfe17c373df4043b6fe2d36f9e628d.zip
fix: transform MCP tool schemas for Google/Gemini compatibility (#4538)
Co-authored-by: Aiden Cline <[email protected]> Co-authored-by: Github Action <[email protected]> Co-authored-by: Aiden Cline <[email protected]>
-rw-r--r--packages/opencode/src/provider/transform.ts35
-rw-r--r--packages/opencode/src/session/prompt.ts29
2 files changed, 55 insertions, 9 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,10 +271,39 @@ export namespace ProviderTransform {
}
}
}
+ */
+
+ // 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 (providerID === "google") {
+ 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