refactor(tool): migrate tool framework + all 18 built-in tools to Effect Schema#23244
Merged
kitlangton merged 2 commits intodevfrom Apr 23, 2026
Merged
refactor(tool): migrate tool framework + all 18 built-in tools to Effect Schema#23244kitlangton merged 2 commits intodevfrom
kitlangton merged 2 commits intodevfrom
Conversation
Pre-migration safety net for the upcoming tool-by-tool zod\u2192Schema conversion. Every tool's parameters schema now has: 1. A JSON Schema snapshot (`z.toJSONSchema` with `io: "input"`) \u2014 this captures exactly what the LLM sees at tool registration time, so any drift caused by a future migration fails the snapshot. 2. Parse-accept/parse-reject assertions per tool pinning the user-visible behavioural contract (required fields, refinement bounds, enum membership, default values). To make the snapshots possible without standing up each tool's full Effect runtime, every tool file now exports its parameters schema as `Parameters` at module scope: - 9 tools already had a module-level const \u2014 just added `export`, and standardised the name to `Parameters` (uppercase) where it was previously `parameters`. - 9 tools had their schema inline inside `Tool.define` \u2014 hoisted to module scope under the same `Parameters` name and wired back through. Zero behaviour change: Tool.define still sees the same schema, runtime validation path is identical, SDK (types.gen.ts + openapi.json) is byte-identical, and the full 2054-test suite passes. 18 JSON Schema snapshots and 43 explicit parse/reject assertions for the 18 built-in tools (apply_patch, bash, codesearch, edit, glob, grep, invalid, lsp, multiedit, plan, question, read, skill, task, todo, webfetch, websearch, write).
a49b5ad to
4b3788e
Compare
4b3788e to
61691ae
Compare
TTK95
pushed a commit
to TTK95/opencode
that referenced
this pull request
Apr 24, 2026
Conflict resolutions:
- packages/{opencode,plugin,web}/package.json: bump to 1.14.22-dev_ttk
- tool/bash.ts: port run_in_background param from zod to Effect Schema
(upstream refactored tool params to Schema.Struct in anomalyco#23244)
- tool/task.ts: port isolation=worktree param from zod to Effect Schema,
keep worktree helper imports (path/os/fs/spawn/Instance/Log)
TTK95
pushed a commit
to TTK95/opencode
that referenced
this pull request
Apr 24, 2026
…Schema Matches the Tool.define schema migration in upstream anomalyco#23244. Without this the tool registry can't construct these tools and runtime errors out with "undefined is not an object (evaluating 'X.context')" on every prompt.
xywsxp
pushed a commit
to xywsxp/opencode
that referenced
this pull request
Apr 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Framework (
tool/tool.ts)Tool.Def.parameterstyped asSchema.Decoder<unknown>(replacesz.ZodType).Schema.decodeUnknownEffect+Effect.mapErrorinstead ofEffect.trywrappingtoolInfo.parameters.parse(args)— matches idiomatic patterns already used elsewhere and satisfies AGENTS.md's avoid-try/catch rule.formatValidationError?(error: unknown)— the hook is no longer zod-specific. No tool currently implements it.JSON Schema pipeline
Extracted
EffectZod.toJsonSchema(schema)intoutil/effect-zod.ts. Three emit sites share one implementation:session/prompt.ts— every LLM tool registrationserver/routes/instance/experimental.ts— was previously callingz.toJSONSchema(t.parameters)directly; would have failed at runtime oncet.parametersbecame a Schema. Fixed here.test/tool/parameters.test.ts— snapshot test mirrors the production path so any future drift fails the snapshot.Tools (17 files — multiedit was deleted by #23701 on dev)
Mechanical conversion of each tool's
Parameters:z.object({...})Schema.Struct({...})z.string().describe("x")Schema.String.annotate({ description: "x" })z.string().optional()Schema.optional(Schema.String)z.number().int().min(1)Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1))z.enum([...])Schema.Literals([...])z.array(X)Schema.mutable(Schema.Array(X))z.number().min(1000).max(50000).default(5000)Schema.withDecodingDefault(Effect.succeed(5000))z.infer<typeof Parameters>Schema.Schema.Type<typeof Parameters>Notable simplifications:
read.ts: droppedz.coerce.number()onoffset/limit. LLM tool-call path receives typed JSON; string coercion was only relevant to CLI callers, of which there are none. JSON Schema output identical.todo.ts: inlined the three todo fields rather than pullingTodo.Info.shapefrom the still-zodsession/todo.ts. Removes the last zod import from the tool file; LLM-visible JSON Schema is identical.question.ts: referencesQuestion.Prompt(already aSchema.Class) directly instead ofQuestion.Prompt.zod.Plugin tools (
tool/registry.ts)Plugin tool args are defined via zod (
@opencode-ai/pluginAPI — external). The registry wraps the derivedz.object(def.args)in aSchema.declare(typeGuard)so it slots into the Schema-typed framework, and annotates it withZodOverrideso the walker emits the original zod object when generating JSON Schema for the LLM. Runtime behaviour and JSON Schema for plugin tools unchanged.Safety net (originally PR #23244)
Parametersschema at module scope.test/tool/parameters.test.ts. These caught zero drift during the migration.Rebase notes
Rebased onto current dev (was ~18 days old). Conflicts resolved:
multieditwas deleted on dev by chore: kill unused tool #23701; dropped from both commits.edit.ts— kept HEAD'slocks/lock()helper added on dev plus branch's Schema migration.read.ts— kept HEAD's newreadSample/isBinaryFilehelpers plus branch's Schema migration.skill.ts— kept HEAD's refactored execute body (no more nestedreturn { description, parameters, execute }wrapper) plus branch's Schema migration.todo.ts— kept branch's inlined Schema (matches HEAD's zod-inlined pattern semantically; same LLM-visible JSON Schema).util/effect-zod.ts— merged dev'szodObjecthelper with branch'stoJsonSchemahelper.Verification
bun typecheckcleanbun run test— 2123 pass / 0 fail (full suite)types.gen.tsandopenapi.json)Scope notes
@/util/effect-zod) and centralEffectZod.toJsonSchemahelper remain — they convert Schema → zod at the edge whereaiSDK wants zod-style JSON Schema. Retirable when the runtime LLM layer accepts Effect Schema natively.session/todo.tsstill exports a zodInfo; migration deferred (the tool'stodowriteParameters inlines the fields rather than depending on it).