From 33e3d1ebca05c640b654640c5fdfc7b210a8748b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:21:47 -0400 Subject: [PATCH] fix(core): tolerate missing tool input schemas (#39130) Co-authored-by: Dax Raad --- packages/core/src/tool/runtime.ts | 4 +++- .../test/session-runner-tool-registry.test.ts | 16 ++++++++++++++++ packages/core/test/tool-schema.test.ts | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/core/src/tool/runtime.ts b/packages/core/src/tool/runtime.ts index 80872798e02..5c5b767c8c8 100644 --- a/packages/core/src/tool/runtime.ts +++ b/packages/core/src/tool/runtime.ts @@ -58,7 +58,8 @@ const encodeOutput = (schema: Tool.ValueSchema, value: unknown) => { const isStandardSchema = ( schema: Tool.ValueSchema, -): schema is StandardSchemaV1 & StandardJSONSchemaV1 => "~standard" in schema +): schema is StandardSchemaV1 & StandardJSONSchemaV1 => + typeof schema === "object" && schema !== null && "~standard" in schema const validateStandard = ( schema: StandardSchemaV1 & StandardJSONSchemaV1, @@ -85,6 +86,7 @@ const standardFailure = (prefix: string, error: unknown) => new Tool.Error({ message: `${prefix}: ${error instanceof Error ? error.message : String(error)}` }) const inputJsonSchema = (schema: Tool.ValueSchema): JsonSchema.JsonSchema => { + if (schema === undefined || schema === null) return {} if (isStandardSchema(schema)) return schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) as JsonSchema.JsonSchema return Schema.isSchema(schema) ? toJsonSchema(schema) : (schema as JsonSchema.JsonSchema) diff --git a/packages/core/test/session-runner-tool-registry.test.ts b/packages/core/test/session-runner-tool-registry.test.ts index 37a32fa4c7a..1398a749791 100644 --- a/packages/core/test/session-runner-tool-registry.test.ts +++ b/packages/core/test/session-runner-tool-registry.test.ts @@ -140,6 +140,22 @@ describe("Tool", () => { }), ) + it.effect("snapshots external tools with missing input schemas", () => + Effect.gen(function* () { + const service = yield* Tool.Service + yield* service.transform((draft) => + draft.add({ + ...make(), + input: undefined, + } as unknown as Info), + ) + + const snapshot = yield* service.snapshot() + expect(snapshot.definitions.map((tool) => tool.name)).toEqual(["execute"]) + expect(snapshot.codeModeCatalog?.[0]?.signature).toContain("tools.echo") + }), + ) + it.effect("keeps execute available without Code Mode tools unless explicitly denied", () => Effect.gen(function* () { const service = yield* Tool.Service diff --git a/packages/core/test/tool-schema.test.ts b/packages/core/test/tool-schema.test.ts index 72e053ab8bb..4008e0a8c44 100644 --- a/packages/core/test/tool-schema.test.ts +++ b/packages/core/test/tool-schema.test.ts @@ -143,3 +143,18 @@ test("raw JSON schemas are render-only and omitted output means model-only", asy content: [{ type: "text", text: '{"value":1}' }], }) }) + +test("missing external input schemas fall back to an empty schema", () => { + const tool = { + name: "external", + description: "External tool", + input: undefined, + execute: () => Effect.succeed({ content: "unused" }), + } as unknown as Info + + expect(definition(tool)).toEqual({ + name: "external", + description: "External tool", + inputSchema: {}, + }) +})