Summary
Anthropic's Messages API rejects tool definitions whose input_schema has anyOf, oneOf, or allOf at the top level of the object, returning:
Error: tools.2.custom.input_schema: input_schema does not support oneOf, allOf, or anyOf at the top level
This crashes the entire session — no response is returned, and the agent errors out immediately.
Root Cause
ProviderTransform.schema() in packages/opencode/src/provider/transform.ts has per-provider schema sanitization for Moonshot/Kimi and Google/Gemini, but no sanitization for Anthropic. When an MCP tool produces a JSON Schema with a top-level combiner (e.g. { anyOf: [...] }), it is passed unmodified to the Anthropic Messages API, which rejects it.
PR #1100 (fix(llm): emit anyOf as sole schema key for Vertex tool defs) fixed the same class of problem for Vertex/Gemini but did not cover @ai-sdk/anthropic.
Note: json-schema.ts's normalize() already collapses single-element anyOf arrays and flattens other common cases — but this runs at the tool-definition build time for opencode-native tools. MCP tools that arrive with their schemas pre-formed (tool.inputSchema set directly, not derived via ToolJsonSchema.fromTool()) can bypass that normalization and still carry a top-level combiner into lowerTool() → input_schema: tool.inputSchema.
Affected Code
packages/opencode/src/provider/transform.ts — the schema() export function (currently ~line 1339). The Gemini branch (lines ~1377–1453) rewrites integer enums and enforces items on arrays but does not strip top-level combiners. There is no Anthropic branch at all.
Proposed Fix
Add an Anthropic-specific pass in schema() that:
- Detects
@ai-sdk/anthropic (and @ai-sdk/google-vertex/anthropic) via model.api.npm
- Recursively walks the schema and, at any level where
anyOf/oneOf/allOf is the only key (or the sole meaningful key), unwraps the single-element case to the element itself — mirroring what normalize() in json-schema.ts already does
- For multi-element top-level combiners that cannot be safely collapsed (e.g. a true union of two different object shapes), falls back to
{ type: "object" } to avoid the API rejection — a lossy but non-crashing result
The fix should be consistent with the existing normalize() logic in json-schema.ts lines 56–76.
Repro
- Configure an agent with
@ai-sdk/anthropic as the provider
- Connect an MCP server whose tools expose a schema with a top-level
anyOf (e.g. an optional/nullable parameter encoded as { anyOf: [{ type: "string" }, { type: "null" }] } at the root of input_schema)
- Send any message — the first API call immediately fails with the error above
Environment
- opencode fork (Rhythm), observed on
claude-opus-4-8 and claude-sonnet-4-6 via @ai-sdk/anthropic
- The error is provider-side validation, not a transient failure — it reproduces on every call
Summary
Anthropic's Messages API rejects tool definitions whose
input_schemahasanyOf,oneOf, orallOfat the top level of the object, returning:This crashes the entire session — no response is returned, and the agent errors out immediately.
Root Cause
ProviderTransform.schema()inpackages/opencode/src/provider/transform.tshas per-provider schema sanitization for Moonshot/Kimi and Google/Gemini, but no sanitization for Anthropic. When an MCP tool produces a JSON Schema with a top-level combiner (e.g.{ anyOf: [...] }), it is passed unmodified to the Anthropic Messages API, which rejects it.PR #1100 (
fix(llm): emit anyOf as sole schema key for Vertex tool defs) fixed the same class of problem for Vertex/Gemini but did not cover@ai-sdk/anthropic.Note:
json-schema.ts'snormalize()already collapses single-elementanyOfarrays and flattens other common cases — but this runs at the tool-definition build time for opencode-native tools. MCP tools that arrive with their schemas pre-formed (tool.inputSchemaset directly, not derived viaToolJsonSchema.fromTool()) can bypass that normalization and still carry a top-level combiner intolowerTool()→input_schema: tool.inputSchema.Affected Code
packages/opencode/src/provider/transform.ts— theschema()export function (currently ~line 1339). The Gemini branch (lines ~1377–1453) rewrites integer enums and enforcesitemson arrays but does not strip top-level combiners. There is no Anthropic branch at all.Proposed Fix
Add an Anthropic-specific pass in
schema()that:@ai-sdk/anthropic(and@ai-sdk/google-vertex/anthropic) viamodel.api.npmanyOf/oneOf/allOfis the only key (or the sole meaningful key), unwraps the single-element case to the element itself — mirroring whatnormalize()injson-schema.tsalready does{ type: "object" }to avoid the API rejection — a lossy but non-crashing resultThe fix should be consistent with the existing
normalize()logic injson-schema.tslines 56–76.Repro
@ai-sdk/anthropicas the provideranyOf(e.g. an optional/nullable parameter encoded as{ anyOf: [{ type: "string" }, { type: "null" }] }at the root ofinput_schema)Environment
claude-opus-4-8andclaude-sonnet-4-6via@ai-sdk/anthropic