Repository navigation
[Provider]: Kimi/Moonshot 400 — $ref with sibling keywords in tool schema $defs is not normalized (openai-chat adapter) #2673
Description
Client or integration
Codex App
Provider or upstream service
kimi (Moonshot / api.kimi.com coding endpoint)
OpenCodex version
2.32.1-preview.20260825
Endpoint or capability
/v1/chat/completions (tool calls)
Current behaviour
Requests routed to kimi/k3 / kimi/k3[1m] fail with HTTP 400 as soon as the Codex tool catalog contains a schema in which a $defs node carries $ref together with sibling validation keywords:
Provider error 400: tools.function.parameters is not a valid moonshot flavored json schema,
details: <At path '$defs.__schema20': when using $ref, type should be defined in the referenced
schema instead of the parent schema>
The offending node is generated by Codex itself. Captured verbatim from a local Codex Desktop session rollout (codex-cli 0.145.0, automation_update tool):
"__schema20": {
"type": "string",
"minLength": 1,
"format": "uuid",
"description": "Target thread UUID for heartbeat automations. ...",
"$ref": "#/$defs/__schema2"
}In that one tool schema, 8 of the $defs nodes have sibling keys next to $ref. Seven of them only carry description, which Moonshot tolerates; __schema20 also carries type, minLength, and format, and that is the node the API names in the error.
This is valid JSON Schema 2020-12 — $ref no longer replaces siblings there, it is an in-place applicator, so type/minLength/format are meant to be combined with the referenced schema. Moonshot enforces the older draft-07 reading where $ref must stand alone.
The proxy forwards the schema unchanged in this respect. In src/adapters/openai-chat.ts, toolsToChatFormat() only applies ensureRootObjectType() for non-xAI targets:
const parameters = stripResponsesOnlyEncryptedMarker(xaiTarget
? normalizeXaiToolParameters(t.parameters)
: ensureRootObjectType(t.parameters));ensureRootObjectType() (added for #228) only stamps type: "object" on the root. It never walks $defs/properties, so a sibling-$ref node deeper in the schema reaches Moonshot untouched. There is already a per-provider deep normalizer for xAI (resolveXaiSchemaRefs) and for Google (src/adapters/google-tool-schema.ts inlines local $defs refs), but nothing on the Kimi path.
Expected behaviour
For the Kimi/Moonshot target, function parameter schemas should be normalized so no node emits $ref alongside sibling keywords. Requests carrying the Codex automation_update catalog (or any zod-to-json-schema output with deduplicated $defs) should return HTTP 200 and be able to call tools.
Minimal redacted request or reproduction
# Provider: kimi (OAuth), baseUrl https://api.kimi.com/coding/v1
curl http://127.0.0.1:10100/v1/responses \
-H 'content-type: application/json' \
--data-binary '{
"model": "kimi/k3",
"input": "Reply exactly OK. Do not call tools.",
"tool_choice": "auto",
"stream": false,
"tools": [{
"type": "function",
"name": "schema_probe",
"description": "Schema compatibility probe.",
"parameters": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": { "threadId": { "$ref": "#/$defs/__schema20" } },
"required": ["threadId"],
"additionalProperties": false,
"$defs": {
"__schema2": { "type": "string", "minLength": 1 },
"__schema20": {
"type": "string",
"minLength": 1,
"format": "uuid",
"description": "Target thread UUID.",
"$ref": "#/$defs/__schema2"
}
}
}
}]
}'Removing the "$ref" line from __schema20 (or removing type/minLength/format and keeping only $ref) makes the same request succeed.
Note on scope: the identical payload sent through a Moonshot-compatible third-party endpoint (openai-chat adapter, non-api.kimi.com host serving Kimi K3) returns HTTP 200, so the rejection comes from Moonshot's own schema validator on the first-party endpoint, not from the model or the adapter's transport.
Actual response or error
HTTP 400
Provider error 400: tools.function.parameters is not a valid moonshot flavored json schema,
details: <At path '$defs.__schema20': when using $ref, type should be defined in the referenced
schema instead of the parent schema>Upstream documentation
- Moonshot / Kimi API reference: https://platform.moonshot.ai/docs/api/chat (also https://platform.kimi.com/docs/api/chat). The "moonshot flavored json schema" constraint is not published as a documented subset; the wording above is the API's own error text, so the requirement is only observable from the response.
- JSON Schema 2020-12 core,
$refas an in-place applicator (siblings are permitted and must be combined): https://json-schema.org/draft/2020-12/json-schema-core#name-direct-references-with-ref - Draft-07 behaviour Moonshot appears to implement (siblings ignored,
$refstands alone): https://json-schema.org/draft-07/json-schema-core#rfc.section.8.3
Suggested mapping or implementation notes
Two candidate shapes, in the order I would try them:
-
Inline the referenced schema (preferred). Add a Kimi-scoped normalizer next to
ensureRootObjectType()insrc/adapters/openai-chat.ts, gated on the Kimi host in the same style asisXaiSchemaTarget(). Walk the schema; whenever a node has$refplus other keys, resolve the local#/$defs/.../#/definitions/...pointer and merge the target under the node's own keywords (node keywords win, matching 2020-12 semantics), dropping$ref.src/adapters/google-tool-schema.tsalready does exactly this shape of local-ref inlining andresolveXaiSchemaRefs()already has the cycle guard, so the logic is proven twice in-tree — a shared helper would be better than a third private copy. Cyclic or unresolvable refs need a bail-out; dropping the sibling keywords instead of failing the whole tool is the safer fallback there. -
Strip the siblings. Simpler, but lossy:
format: "uuid"andminLength: 1disappear from the wire, so the model loses constraints it would otherwise honor. Only worth it if inlining proves to blow up schema size on catalogs with many shared$defs.
Whichever is chosen, the fix should sit behind the same host check as the other provider-scoped schema paths so no other openai-chat provider changes shape, and it should be covered by a focused test alongside tests/xai-tool-schema.test.ts / tests/google-tool-schema.test.ts — a fixture carrying a sibling-$ref $defs node, asserting no $ref-with-siblings survives in the emitted tools[].function.parameters.
Worth noting this is the second Moonshot schema-strictness 400 in the same code path after #228 (root type requirement). A single Kimi schema-conformance pass, rather than another point patch, would probably be the durable answer.
Additional context and attachments
- Related: [Bug]: Kimi returns 400 when a Codex tool schema has no root object type #228 (
tools.function.parameters.type is required and must be "object"— same adapter, same provider, different strictness rule; fixed byensureRootObjectType(), which does not walk nested nodes). - The trigger schema comes from Codex's own deferred
automation_updatetool, so any Kimi user in Codex Desktop hits this as soon as that tool loads. It is not caused by user configuration. - Environment: macOS 26.5.2 arm64, Codex Desktop,
codex-cli 0.145.0, opencodex2.32.1-preview.20260825.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
Ingwannu commentedon Aug 26, 2026
Confirmed against current
dev@779b6090c. The first-party Kimi/Moonshot chat path only appliesensureRootObjectType(); it does not traverse$defsor remove sibling validation keywords from a$refnode, so the reported schema reaches Moonshot unchanged.I will implement this as a provider-scoped schema compatibility layer:
api.kimi.com,api.moonshot.ai, andapi.moonshot.cnchat destinations; third-partyopenai-chatgateways remain byte-shape compatible;openai-chat.ts;#/$defs/...and#/definitions/...references when they have siblings, merging the referenced schema first and the node's own keywords last, then removing$ref;$refnodes; cyclic or unresolvable sibling refs degrade to a pure$refinstead of inventing constraints;automation_updateshape, pure refs, cycles/unresolvable refs, the first-party host gate, and a third-party preservation control.I will link the implementation PR here and keep the issue open until exact-head CI is green and the fix reaches
dev.Ingwannu commentedon Aug 26, 2026
Implemented in draft PR #2675 at exact head
c7916e72822909da269da6328e46ad426b079f9b.The fix is limited to first-party Kimi/Moonshot chat destinations. It expands resolvable local sibling refs, preserves pure refs, degrades cyclic/unresolvable sibling refs to a pure ref, bounds depth/node/ref expansion, and leaves third-party OpenAI-chat gateways unchanged. The reported nested
$defs.__schema20shape is covered directly.Focused validation is green (65/65 for the new normalizer plus existing OpenAI-chat hardening; 17/17 for xAI boundary, Moonshot cases, and dangling tool-call compatibility), with typecheck, privacy scan, and diff check passing. This issue remains open until exact-head CI is green and the PR reaches
dev.lidge-jun commentedon Aug 26, 2026
리뷰 · 우선순위 73 / 80
설명
이 이슈는 Codex 앱이
kimi/k3또는kimi/k3[1m]으로 도구를 실을 때, Moonshot 이 HTTP 400tools.function.parameters is not a valid moonshot flavored json schema를 내는 문제입니다. 원인은$defs노드가$ref와 함께type,minLength,format같은 형제 키를 가진 형태입니다. 작성자가 Codex Desktop (codex-cli 0.145.0) 의automation_update도구에서 그대로 잡은__schema20이 그 모양입니다. JSON Schema 2020-12 에서는$ref가 형제를 허용하는 제자리 적용자이지만, Moonshot 은 draft-07 처럼$ref가 혼자 있어야 한다고 봅니다.지금 current
devHEAD779b6090c의src/providers/registry.ts1243-1246줄kimi제공자는adapter: "openai-chat",baseUrl: "https://api.kimi.com/coding/v1"입니다. 채팅 도구 변환은src/adapters/openai-chat.ts1226줄toolsToChatFormat입니다. 1231-1234줄은isXaiSchemaTarget가 참일 때만normalizeXaiToolParameters를 쓰고, 나머지는ensureRootObjectType만 호출합니다.ensureRootObjectType은 954-961줄에서 루트에type: "object"만 찍습니다.$defs나properties안을 걷지 않으므로, 형제$ref노드는 Moonshot 까지 그대로 갑니다.이미 제공자별 깊은 정규화는 두 곳이 있습니다. 같은 파일 1002줄
resolveXaiSchemaRefs는 로컬#/포인터를 풀고, 형제가 있으면 대상과 나머지를 합칩니다.src/adapters/google-tool-schema.ts168-176줄sanitizeSchema는$ref를 로컬$defs/definitions로 풀어mergeRefTarget로 합친 뒤 다시 걷습니다. 925-929줄isXaiSchemaTarget은 호스트cli-chat-proxy.grok.com에만 묶입니다. HEAD 에는normalizeMoonshot,isMoonshotSchemaTarget,src/adapters/moonshot-tool-schema.ts가 없습니다. 이슈가 적은 세 번째 사본을 피하고 공유 헬퍼를 쓰라는 제안은, 아직 코드에 반영되지 않았습니다.재현 curl 은
/v1/responses로 들어가 채팅 어댑터가 도구 스키마를 그대로 내보내는 경로입니다. 같은 몸을 1차 호스트가 아닌 openai-chat 게이트웨이로 보내면 HTTP 200 이 나온다고 적혀 있으므로, 거절은 모델이 아니라 Moonshot 검증기입니다. 형제를 지우기만 하면format: "uuid"와minLength: 1이 사라져 모델 안내가 줄어듭니다. 로컬 참조를 인라인하는 쪽이 이슈가 선호한 길입니다. 순환이거나 못 푸는 참조는 도구 전체를 실패시키기보다 형제를 버리고 순수$ref로 남기는 편이 안전합니다.초안 PR #2675 가 이미 이 이슈를 닫겠다고 연결되어 있습니다. 그 PR 은 1차
api.kimi.com/api.moonshot.ai/api.moonshot.cn에만 묶인 전용 모듈을 추가합니다. 이슈는 exact-head CI 가 초록이고 고침이dev에 들어가기 전까지 열린 채로 두겠다고 본문에 적혀 있습니다. 이 댓글은 그 구현 PR 이 아니라, HEAD 에 구멍이 남아 있다는 이슈 자체에 대한 리뷰입니다.src/adapters/openai-chat.ts:1234- 1차 Kimi 경로는ensureRootObjectType만 써서$defs안의 형제$ref가 Moonshot 까지 갑니다src/adapters/openai-chat.ts:954-ensureRootObjectType은 루트type: "object"만 찍고 중첩 노드를 걷지 않습니다src/providers/registry.ts:1246- 기본 Kimi 코딩 끝점은https://api.kimi.com/coding/v1이라, 호스트 게이트가 이 이름을 봐야 합니다src/adapters/google-tool-schema.ts:168- Google 은 이미 로컬$ref를 인라인하므로, Kimi 만 그 층이 없습니다경로/심볼 - HEAD 에 Moonshot 전용 스키마 정규화가 없어 Codex
automation_update카탈로그가 400을 냅니다메인테이너의 판단이 필요한 지점
$ref를 인라인할지, 형제를 지워 순수$ref만 남길지 정해야 합니다$ref로 낮출지 정해야 합니다너의 추천
이 이슈는 열어 두고 구현을 진행하는 것을 추천합니다. 고침은
src/adapters/openai-chat.ts의toolsToChatFormat옆에,isXaiSchemaTarget과 같은 호스트 게이트로 좁게 넣으세요. 로컬#/$defs와#/definitions형제$ref만 인라인하고, 순수$ref와 3자 게이트웨이는 그대로 두세요. 초안 #2675 가 그 방향입니다. exact-head CI 가 초록이고 #2675 가dev에 들어가기 전에는 이슈를 닫지 마세요. 미리보기 배포는 계획에 없습니다.이 댓글은 grok-bot이 작성했습니다
Ingwannu commentedon Aug 26, 2026
Correction to draft PR #2675: review of the duplicate #2677 exposed that object-spread inlining could overwrite referenced assertions when the same keyword appeared in both schemas. I fixed the same weakness in #2675 at exact head
0c211d7c2384cd4886ca04a23c37b3da6c187c0e.Equal duplicate assertions and non-conflicting keys still inline for the reported Codex schema. Conflicting
required,properties,enum,minimum, or other duplicate assertions now degrade to the pure ref instead of weakening the referenced contract. Focused tests are 78/78 green; typecheck and privacy scan pass. The issue remains open pending exact-head CI and another maintainer review.lidge-jun commentedon Aug 26, 2026
Landed via #2677 at 8412fe1
fix(openai-chat): normalize $ref-with-siblings for Moonshot tool schemas
fix(openai-chat): normalize $ref-with-siblings for Moonshot tool schemas
fix(openai-chat): normalize $ref-with-siblings for Moonshot tool schemas