anomalyco / anomalyco/opencode

Model.compatibility.toolSchema is never set — the Moonshot/Kimi tool-schema projection exists but is dead code

Open
#46,861 1 comment 0 reactions 1 assignee View on GitHub

@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, model kimi-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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.