Просмотр исходного кода

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]>
Dmitry Halushka 2 месяцев назад
Родитель
Сommit
ee946d8128
2 измененных файлов с 55 добавлено и 9 удалено
  1. 32 3
      packages/opencode/src/provider/transform.ts
  2. 23 6
      packages/opencode/src/session/prompt.ts

+ 32 - 3
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
   }

+ 23 - 6
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