Clarify Zod requirement for tool args, document Zod vs TypeBox layers

OpenCode's registry calls z.object(def.args) on every plugin tool definition,
so Zod is mandatory for the tool schema — no JSON Schema escape hatch. TypeBox
is used for our own config validation where we control the schema. The two
serve different layers with different constraints. Updated both the overview
and ADR-001 with a clear table showing which library serves which concern.
This commit is contained in:
2026-04-29 07:36:23 +00:00
parent c241aaaf7a
commit 27e2763d02
2 changed files with 13 additions and 2 deletions

View File

@@ -27,7 +27,7 @@ This follows the pattern established by open-memory, which exposes 9 operations
- `op` field name is unambiguous in OpenCode's context
**Negative:**
- The `op` and `args` fields are not individually validated by the outer schema — validation happens inside the dispatch handler
- The `op` and `args` fields are not individually validated by the outer Zod schema — validation happens inside the dispatch handler
- Agent must call help to discover operations; the tool description can only hint
- Slightly more overhead per call (string dispatch vs direct function call)
@@ -36,6 +36,10 @@ This follows the pattern established by open-memory, which exposes 9 operations
- Validation errors are clear and include usage guidance
- The help operation provides complete reference with examples
## Zod Requirement
The tool args schema **must** use Zod because OpenCode's registry calls `z.object(def.args)` on every plugin tool definition. There is no JSON Schema alternative for tool definitions — this is the SDK's contract. TypeBox is used for internal config validation where we control the schema, but the tool definition boundary requires Zod. This is a small surface area (one schema for one tool) and doesn't extend beyond the `taskgraph` tool definition.
## Note on Schema Libraries
The tool's outer parameter schema uses **Zod** (from `@opencode-ai/plugin`'s `tool()` helper) because that's what OpenCode's plugin SDK provides for tool definitions. The plugin's internal config schema uses **TypeBox** (from `@alkdev/typebox`, already a dependency via `@alkdev/taskgraph`) for compile-time types and runtime `Value.Check()`. These are two different concerns: Zod for OpenCode's tool interface, TypeBox for our own config. No conflict — each is used where it's the native choice.

View File

@@ -379,7 +379,14 @@ No hooks in v1. Future: task status injection into system prompt (similar to ope
Single tool with `{op: string, args?: Record<string, unknown>}` schema. The `op` field dispatches to an operation handler via the registry. Unknown operation names produce a friendly error directing to `taskgraph({op: "help"})`.
The tool's parameter schema uses **Zod** (from `@opencode-ai/plugin`'s `tool()` helper) because that's what OpenCode's plugin SDK provides for tool definitions. The plugin's internal config schema uses **TypeBox** for compile-time types and runtime `Value.Check()`. These are two different concerns: Zod for the tool's external interface (what the LLM sees), TypeBox for our own config (what we validate at startup).
**Zod is required for the tool args schema.** OpenCode's plugin SDK defines `tool()` with `args: z.ZodRawShape`, and OpenCode's registry does `z.object(def.args)` to construct the parameter schema. There is no JSON Schema escape hatch — Zod is the only option for tool definitions. This is a small surface area (one schema for one tool), not a design choice.
The plugin's internal config validation uses **TypeBox** (`@alkdev/typebox/value`) for `Value.Check()` and `Value.Cast()`. These are two different concerns at two different layers:
| Layer | Concern | Library | Why |
|-------|----------|---------|-----|
| Tool definition (what the LLM sees) | Parameter schema for `taskgraph({op, args})` | Zod | Required by OpenCode's registry — it calls `z.object(def.args)` |
| Plugin config (what opencode.json provides) | Validate and apply defaults to `{source: {type, tasksPath}}` | TypeBox | Better JSON Schema ergonomics, already a dependency, matches `@alkdev/taskgraph`'s validation pattern |
The `source` is passed from the plugin entry to `createTools()` and stored in the registry for all operations to use.