mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-18 02:22:32 +00:00
fix(tool): bridge custom tool zod metadata (#27770)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
"",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user