anomalyco / anomalyco/opencode
Model.compatibility.toolSchema is never set — the Moonshot/Kimi tool-schema projection exists but is dead code
@kitlangton is already working on this.
Since Sep 2, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
Model.compatibility.toolSchema — the field that selects a named tool-schema
projection (gemini / moonshot) before tools are sent to the provider — is
declared, read in every protocol (openai-chat.ts, openai-responses.ts,
anthropic-messages.ts, bedrock-converse.ts, gemini.ts), and has a
correct, tested projection function (ToolSchemaProjection.moonshot,
packages/llm/src/protocols/utils/tool-schema.ts) — but nothing in the
codebase ever sets it. It stays undefined for every model, catalog or
custom, so ToolSchemaProjection.modelCompatibility() always takes its
compatibility === undefined branch and every tool schema reaches Moonshot
completely unprojected.
This looks like the real root cause behind several open Kimi/Moonshot
schema-rejection reports, including #37496, #25495, and #40127.
Where it comes from
#34436 added the compatibility field as, in its own words, "inert model
compatibility metadata", with an explicit note that "request precedence
and compatibility projection behavior remain follow-up PRs". #34454 then
built the projection functions and the read side. The follow-up that
actually sets compatibility.toolSchema for Kimi/Moonshot models never
landed — grep -rn "compatibility:" packages/llm/src packages/opencode/src
(excluding tests) turns up exactly two hits, both inside
packages/llm/src/schema/options.ts's Model class, and both are pure
passthroughs of whatever input.compatibility the caller supplies. No
caller anywhere supplies it.
Reproduction
Real-world trigger: any MCP server tool whose schema uses a $ref into a
sibling $defs entry, with a description alongside the $ref (valid
2020-12 JSON Schema, invalid under Moonshot's stricter "flavored" subset).
Concretely, this project's own memory_feedback tool
(github.com/akitaonrails/ai-memory, an MCP server) exposes:
"signal": {
"description": "Quality signal to record.",
"$ref": "#/$defs/FeedbackKind"
}
Wiring this MCP server into OpenCode and calling any tool with -m moonshot/kimi-k3 (custom @ai-sdk/openai-compatible provider pointed at
api.moonshot.ai) fails on every request, including ones where the
model never touches memory_feedback — because the whole tool list,
including this schema, is sent up front:
Invalid request: tools.function.parameters is not a valid moonshot flavored json schema
details: <At path 'properties.signal': detected infinite recursion without termination condition>
Confirming the fix already exists, just isn't wired
Running the existing ToolSchemaProjection.moonshot() directly against
that exact schema produces a clean, correct, non-recursive result — the
$ref sibling gets stripped and $defs is preserved intact:
import { ToolSchemaProjection } from "./packages/llm/src/protocols/utils/tool-schema"
const inputSchema = {
type: "object",
properties: {
signal: { description: "Quality signal to record.", $ref: "#/$defs/FeedbackKind" },
},
required: ["signal"],
$defs: {
FeedbackKind: {
oneOf: [
{ type: "string", const: "helpful" },
{ type: "string", const: "not_helpful" },
],
},
},
}
console.log(JSON.stringify(ToolSchemaProjection.moonshot(inputSchema as any), null, 2))
// -> properties.signal becomes { "$ref": "#/$defs/FeedbackKind" }, $defs preserved. Correct.
So the projection logic itself isn't the bug — it's simply never invoked for
any real request, because toolSchemaCompatibility is always undefined.
Suggested fix
isKimiFamily() (packages/opencode/src/provider/transform.ts) already
detects Kimi/Moonshot models correctly by provider ID, model ID, and
baseURL host — which means it already works for custom
@ai-sdk/openai-compatible provider blocks like the one in this report, not
just catalog entries. Per #34436's own note, Model.make, Model.update,
Model.input, and Route.model are where compatibility metadata is meant
to be threaded through — defaulting compatibility.toolSchema to
"moonshot" there when isKimiFamily(model) is true (and nothing more
specific was already set) would turn on the existing, already-correct
projection for every Kimi/Moonshot model without touching per-provider
request shaping.
I'm happy to put up a PR for this if a maintainer can confirm
Model.make/Route.model is the intended injection point — didn't want to
guess at the architecture for a change that touches request construction on
every protocol.
Environment
- OpenCode 1.18.25
- Custom provider:
@ai-sdk/openai-compatible,baseURL: https://api.moonshot.ai/v1, modelkimi-k3 - Reproduced with
opencode run, both with and without--pure, and with
zero other plugins/MCP servers active besides the one MCP server whose
schema is shown above
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.