Skip to content

feat: bridge OpenCode tools with their full parameter schema - #24

Open
karimodm wants to merge 1 commit into
openchamber:mainfrom
karimodm:feat/faithful-tool-schemas
Open

karimodm wants to merge 1 commit into
openchamber:mainfrom
karimodm:feat/faithful-tool-schemas

Conversation

@karimodm

Copy link
Copy Markdown

jsonSchemaToZodShape maps only top-level primitive types, which loses most of each OpenCode tool's schema. Enums, nested object/array item shapes, nested required fields and property descriptions are all dropped. For example, Claude sees todowrite as todos: array of anything, and it has to guess the status values and item fields that native OpenCode models get from the schema. That leads to avoidable failed tool calls.

Fix: convert each tool's full JSON Schema with z.fromJSONSchema (zod ≥ 4.2). The SDK serialises tool shapes with its own zod, and a single shape it can't handle breaks tools/list for every tool (see #12). So each converted shape is first listed through a throwaway createSdkMcpServer, and it's used only if that succeeds. If conversion or the probe fails, the tool falls back to the existing primitive mapping. Results are cached per schema string, so the probe runs once per distinct tool schema per process.

Caveats:

  • The probe calls the SDK server's tools/list handler through _requestHandlers, which is not public API. If it moves, the probe finds no handler and every tool falls back to today's behaviour, so nothing breaks.
  • With the zod instances involved here, descriptions on nested item properties still don't survive the SDK's serialisation. Top-level descriptions, enums and nested structure do.

Test: test/tool-schema-fidelity-regression.ts sends a todowrite-shaped schema and a schema with an unresolvable $ref through the proxy, then lists the bridge. todowrite keeps its enum, item shape and nested required, which fails on main. The broken schema falls back and both tools are still listed. It passes with the locked SDK 0.3.224 and with SDK 0.3.260 plus zod 4.5.4. bun test/smoke.ts and tsc --noEmit pass.

This is independent of #20. With both merged, the fallback path also stops emitting z.record.

The primitive JSON Schema -> zod mapping dropped enums, nested item
shapes, nested required fields and property descriptions, so Claude saw
e.g. todowrite as an array of anything. Convert the whole schema with
z.fromJSONSchema instead. Each converted shape is dry-run through a
throwaway SDK server first, since one shape the SDK cannot serialise
fails tools/list for every tool; failures fall back to the old mapping.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant