Repository navigation
[Provider] kimi: no requiresAdjacentResponsesToolResults → 400 when hook-injected developer context splits a tool call from its result #4726
Description
Client or integration
Codex App (desktop). Also reproduced with a direct HTTP client against the local proxy, so this is not client-specific.
Provider or upstream service
kimi — Kimi Code Plan (OAuth, authMode: "oauth"), used over the Responses wire (adapter: "openai-responses", baseUrl: https://api.kimi.com/coding/v1).
OpenCodex version
2.56.0 (npm @bitkyc08/opencodex@2.56.0), macOS (arm64), default provider kimi. Behaviour is identical on main as of this report: the only requiresAdjacentResponsesToolResults: true in the registry is DeepSeek's.
Endpoint or capability
/v1/responses → Kimi https://api.kimi.com/coding/v1/responses, tool calls (tool result adjacency).
Current behaviour
Kimi's Responses endpoint requires a tool result to follow its call immediately. When any other item sits in between — most commonly hook-injected developer context such as LSP diagnostics — Kimi rejects the whole request with HTTP 400:
Invalid request: an assistant message with 'tool_calls' must be followed by tool
messages responding to each 'tool_call_id'. The following tool_call_ids did not
have response messages: exec:0
OpenCodex already implements exactly this repair — normalizeResponsesToolResultAdjacency() in src/adapters/openai-responses/tool-output-recovery.ts — but it is gated per provider in src/adapters/openai-responses/passthrough.ts:
if (provider.requiresAdjacentResponsesToolResults === true) {
outBody = normalizeResponsesToolResultAdjacency(outBody);
}That flag is seeded only by the DeepSeek registry entry (src/providers/registry/entries-core.ts, the requiresAdjacentResponsesToolResults: true line whose comment cites "DeepSeek rejects a valid Codex continuation when hook-provided developer context splits a call from its result (#1292)" — the identical failure shape).
No Kimi entry carries it:
entries-core.ts→id: "kimi"(adapter: "openai-chat") — no adjacency flag;entries-extended.ts→id: "kimi-code"(adapter: "openai-chat") — no adjacency flag;- there is no Kimi entry on the Responses wire at all, so a user who switches Kimi to
adapter: "openai-responses"(the same hand-configuration pattern the registry comment atentries-extended.tsacknowledges for other providers) inherits no adjacency normalization and every affected session 400s permanently, becausestatelessResponses: truereplays the broken history on every turn.
Workaround currently applied locally: "requiresAdjacentResponsesToolResults": true in the kimi provider block of config.json — this resolves it (see verification below).
Expected behaviour
The kimi provider should get the same adjacency normalization DeepSeek gets, i.e. the registry entry should set:
requiresAdjacentResponsesToolResults: true,The flag is inert for chat-wire bodies (it is only read in the openai-responses adapter path), so it is safe on the current openai-chat entries and would also be correct if a Kimi Responses-wire entry is added later.
Minimal redacted request or reproduction
Sent to the local proxy (loopback bind, no token required). Tool declared as the client declares Codex code mode (custom/exec), which the routed path lowers to a function.
{
"model": "kimi/k3-256k",
"stream": false,
"max_output_tokens": 16,
"tools": [{
"type": "custom",
"name": "exec",
"description": "code mode exec",
"format": { "type": "grammar", "syntax": "lark", "definition": "start: /[\\s\\S]+/" }
}],
"input": [
{ "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "reply with the single word ok" }] },
{ "type": "custom_tool_call", "call_id": "exec_900_ocxtest", "name": "exec", "input": "text('hi')" },
{ "type": "message", "role": "developer", "content": [{ "type": "input_text", "text": "[hook] LSP diagnostics: none" }] },
{ "type": "custom_tool_call_output", "call_id": "exec_900_ocxtest", "output": [{ "type": "input_text", "text": "hi" }] }
]
}curl -s -o out.json -w '%{http_code}' -X POST http://127.0.0.1:10100/v1/responses \
-H 'content-type: application/json' --data-binary @body.jsonDeterministic contrast, same body except the two lines noted:
| input shape | before fix | after flag |
|---|---|---|
| call → developer message → result | 400 | 200 |
| call → result (adjacent, no intervening item) | 200 | 200 |
| call → developer message (no result item at all) | 200 | 200 |
So the variable is purely the intervening item.
Actual response or error
HTTP/1.1 400
{"error":{"code":"invalid_request_error","message":"Invalid request: an assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: exec:0", "type":"invalid_request_error"}}
Note on the id: the request above uses call_id: "exec_900_ocxtest", yet Kimi reports exec:0. The <tool-name>:<ordinal> form is Kimi's own label for the call, not something OpenCodex rewrote. Real sessions show the same form (exec:17, collaboration__spawn_agent:0), which may be worth knowing when triaging similar reports.
Upstream documentation
No public specification documents this adjacency requirement for the Kimi Responses endpoint — Kimi Code Plan's documented surface is the Chat endpoint (https://platform.kimi.com/docs/api/chat, as cited in the registry entry). The requirement is therefore evidenced by the reproduction above (the 400 comes from https://api.kimi.com/coding/v1/responses), and by the fact that the same shape already required a workaround for DeepSeek in this repo.
Suggested mapping or implementation notes
- Minimal: add
requiresAdjacentResponsesToolResults: trueto both Kimi registry entries (kimi,kimi-code). - Optional hardening: consider deriving the flag from the wire/capability rather than a hand-seeded entry, since providers whose Responses implementation is OpenAI-compatible-but-stricter will keep hitting this (the same class already produced [Provider compatibility] DeepSeek V4 Flash returns 400 when developer message is interleaved between function_call and function_call_output #1292-style workarounds).
Additional context and attachments
Real-world trigger in Codex App: the desktop LSP hook inserts a developer message ("LSP diagnostics after editing …") between a code-mode exec call and its result. The session then 400s on every subsequent turn while the model is Kimi; switching the same thread to DeepSeek (which has the flag) resumes normally — consistent with the flag being the only difference.
Local verification after applying the flag to the kimi provider config on a stock 2.56.0 install (installed src/ is byte-identical to the published 2.56.0 tarball): the repro body above returns 200, with the tool result reordered adjacent to its call. Restart + /healthz clean.
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
coderabbitai commentedon Sep 15, 2026
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
lidge-jun commentedon Sep 15, 2026
리뷰 · 우선순위 75 / 80
이슈 #4726은 Kimi를 Responses 와이어(
adapter: "openai-responses")로 붙였을 때, 훅이 넣은 developer 메시지가 tool call과 tool result 사이에 끼어들면 업스트림이 HTTP 400을 내는 문제를 보고한다. 에러 문구는an assistant message with 'tool_calls' must be followed by tool messages...이고, 재현 본문은custom_tool_call→ developer →custom_tool_call_output순서다. 인접(call→result)이면 200, 사이 메시지가 있으면 400으로, 원인은 끼어든 항목뿐이라는 표까지 있다.지금
devHEAD(45cfb04e9, package 2.57.0)를 보면, 이미 같은 수리 함수가 있다.normalizeResponsesToolResultAdjacency()는src/adapters/openai-responses/tool-output-recovery.ts약 421행에 있고, call/result를 한 배치로 모은 뒤 끼어든 non-tool 항목을 배치 뒤로 보낸다. 게이트는src/adapters/openai-responses/passthrough.ts약 272–273행이다.provider.requiresAdjacentResponsesToolResults === true일 때만 그 함수를 탄다.그 플래그를 켜 둔 레지스트리 항목은 지금 DeepSeek뿐이다.
src/providers/registry/entries-core.ts약 1063–1066행 주석이 바로#1292(훅 developer가 call/result를 갈라 DeepSeek가 거절)를 가리킨다. 반면id: "kimi"(같은 파일 약 411–441행,adapter: "openai-chat")와id: "kimi-code"(src/providers/registry/entries-extended.ts약 888–905행)에는 이 필드가 없다. 레지스트리에 Responses 전용 Kimi 엔트리도 없다. 그래서 사용자가 Kimi를 Responses로 손설정하면 인접 정규화가 한 번도 안 돌고, 깨진 히스토리를 매 턴 다시 보내는 환경에서는 세션이 계속 400에 막힌다.플래그 자체는 chat 와이어 본문에서는 읽히지 않는다(Responses 패스스루에서만 검사). 그래서 지금 chat 기본 엔트리에
requiresAdjacentResponsesToolResults: true를 심어도 chat 경로 동작은 그대로이고, 나중에 Responses 엔트리를 추가하거나 손설정으로 와이어를 바꾸면 그때부터 수리가 켜진다. 제보자가config.json의 kimi 블록에 같은 플래그를 넣어 재현 본문이 400→200으로 바뀐 것도 현재 코드와 맞다.derive.ts/router.ts는 레지스트리 시드를 저장 설정이 비어 있을 때 채우는 경로가 이미 있다.CodeRabbit이
#1292중복 후보로 붙였지만, #1292는 DeepSeek용 수리·플래그의 원조이고 이번 이슈는 그 수리를 Kimi에도 시드하라는 좁은 provider-compatibility 요청이다. 타입/config 분할 캠페인에 무효화될 경로가 아니다. 재현·우회·제안이 모두 분명해서 작은 레지스트리 PR 하나로 닫기 좋다.라인 411–441 (
src/providers/registry/entries-core.tsid: "kimi") -requiresAdjacentResponsesToolResults가 없어 Responses 손설정 시 인접 정규화가 꺼진 채로 남는다라인 888–905 (
src/providers/registry/entries-extended.tsid: "kimi-code") - API-key 형제 항목에도 같은 플래그가 없다라인 1063–1066 (
entries-core.tsDeepSeek) - #1292와 같은 실패 모양인데 Kimi만 시드에서 빠져 있다라인 272–273 (
src/adapters/openai-responses/passthrough.ts) - 수리 자체는 이미 있고, 프로바이더 플래그로만 막혀 있다normalizeResponsesToolResultAdjacency(tool-output-recovery.ts~421) - call/result 쌍·병렬 배치 규칙이 이미 있으므로 Kimi도 같은 게이트를 쓰면 된다메인테이너의 판단이 필요한 지점
kimi/kimi-code둘 다에 플래그를 심을지, OAuthkimi만 먼저 할지너의 추천
이슈는 유효하다.
kimi와kimi-code레지스트리 엔트리에requiresAdjacentResponsesToolResults: true를 추가하고(주석에 이 이슈 번호와 #1292 동형임을 적기), 훅이 call/result를 가르는 Responses 픽스처로 회귀 테스트를 잠근 뒤 CI 그린이면 머지하면 된다. 임시 우회는 제보자처럼config.jsonkimi 블록에 같은 플래그를 넣는 것. 중복으로 닫을 필요는 없고(#1292는 DeepSeek 원조), close-don't-rebase 해당 없음.이 댓글은 grok-bot이 작성했습니다
Merge pull request #4770 from lidge-jun/codex/pw3-kimi-tool-adjacency
lidge-jun commentedon Sep 16, 2026
Landed via #4770 at aa31ed4
fix(providers): give Kimi the Responses tool-result adjacency repair (l…
fix(responses): separate tool-result pairing from adjacency
fix(providers): give Kimi the Responses tool-result adjacency repair (l…
Merge pull request lidge-jun#4770 from lidge-jun/codex/pw3-kimi-tool-…
fix(xai): seed Responses tool-result adjacency for interrupted Codex …