فهرست منبع

fix(tool): bridge custom tool zod metadata (#27770)

Aiden Cline 3 ماه پیش
والد
کامیت
48122b31cc
3فایلهای تغییر یافته به همراه31 افزوده شده و 18 حذف شده
  1. 28 9
      packages/opencode/src/tool/registry.ts
  2. 2 2
      packages/opencode/test/tool/registry.test.ts
  3. 1 7
      packages/plugin/src/tool.ts

+ 28 - 9
packages/opencode/src/tool/registry.ts

@@ -145,14 +145,7 @@ export const layer: Layer.Layer<
           const entries = Object.entries(def.args)
           const allZod = entries.every((entry) => isZodType(entry[1]))
           const zodParams = allZod ? z.object(def.args) : undefined
-          // Newer @opencode-ai/plugin versions precompute JSON Schema with the
-          // Zod instance that owns arg metadata. Fall back for older/manual
-          // custom tools that only expose raw Zod args.
-          const jsonSchema = zodParams
-            ? isJsonSchemaDefinition(def.jsonSchema)
-              ? (def.jsonSchema as JSONSchema7)
-              : zodJsonSchema(zodParams)
-            : legacyJsonSchema(entries)
+          const jsonSchema = zodParams ? zodJsonSchema(zodParams) : legacyJsonSchema(entries)
           const parameters = zodParams
             ? Schema.declare<unknown>((u): u is unknown => zodParams.safeParse(u).success)
             : Schema.Unknown
@@ -424,7 +417,7 @@ function legacyJsonSchema(entries: [string, unknown][]): JSONSchema7 {
 }
 
 function zodJsonSchema(schema: z.ZodType): JSONSchema7 {
-  const result = normalizeZodJsonSchema(z.toJSONSchema(schema, { io: "input" }))
+  const result = normalizeZodJsonSchema(z.toJSONSchema(schema, { io: "input", metadata: zodMetadataRegistry(schema) }))
   if (!isJsonSchemaObject(result)) throw new Error("plugin tool Zod schema produced a non-object JSON Schema")
   const { $defs, ...rest } = result
   return (
@@ -432,6 +425,32 @@ function zodJsonSchema(schema: z.ZodType): JSONSchema7 {
   ) as JSONSchema7
 }
 
+function zodMetadataRegistry(schema: z.ZodType) {
+  const registry = z.registry<Record<string, unknown>>()
+  const seen = new WeakSet<object>()
+  const collect = (value: unknown) => {
+    if (typeof value !== "object" || value === null) return
+    if (seen.has(value)) return
+    seen.add(value)
+
+    if (isZodType(value)) {
+      const metadata = typeof value.meta === "function" ? value.meta() : undefined
+      const description = typeof value.description === "string" ? value.description : undefined
+      const merged = {
+        ...(metadata && typeof metadata === "object" ? metadata : {}),
+        ...(description ? { description } : {}),
+      }
+      if (Object.keys(merged).length) registry.add(value, merged)
+      collect(value._zod.def)
+      return
+    }
+
+    for (const item of Object.values(value)) collect(item)
+  }
+  collect(schema)
+  return registry
+}
+
 function normalizeZodJsonSchema(value: unknown): unknown {
   if (Array.isArray(value)) return value.map((item) => normalizeZodJsonSchema(item))
   if (typeof value !== "object" || value === null) return value

+ 2 - 2
packages/opencode/test/tool/registry.test.ts

@@ -264,7 +264,7 @@ describe("tool.registry", () => {
   )
 
   it.instance(
-    "preserves Zod arg descriptions from config-scoped plugin packages",
+    "preserves Zod arg descriptions from older config-scoped plugin packages",
     () =>
       Effect.gen(function* () {
         const test = yield* TestInstance
@@ -291,7 +291,7 @@ describe("tool.registry", () => {
             [
               "import { z } from 'zod'",
               "export function tool(input) {",
-              "  return { ...input, jsonSchema: z.toJSONSchema(z.object(input.args), { target: 'draft-7', io: 'input' }) }",
+              "  return input",
               "}",
               "tool.schema = z",
               "",

+ 1 - 7
packages/plugin/src/tool.ts

@@ -48,13 +48,7 @@ export function tool<Args extends z.ZodRawShape>(input: {
   args: Args
   execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>
 }) {
-  return {
-    ...input,
-    // Generate JSON Schema here with the same Zod instance that created
-    // `tool.schema` args. Zod metadata such as `.describe()` is stored in a
-    // module-local registry, so converting later from opencode can lose it.
-    jsonSchema: z.toJSONSchema(z.object(input.args), { target: "draft-7", io: "input" }),
-  }
+  return input
 }
 tool.schema = z