fix(kosong): recognize OpenAI-compatible tool_call_id 400 as a recoverable tool-exchange error - #1292
Merged
Conversation
…rable tool-exchange error Moonshot / Kimi (OpenAI-compatible) rejects a history whose tool message references a tool_call_id with no matching tool_calls entry in the preceding assistant message as `400 tool_call_id is not found`. The TOOL_EXCHANGE_ADJACENCY_MESSAGE_PATTERNS only covered Anthropic's tool_use/tool_result phrasing, so isRecoverableRequestStructureError returned false, the strict-resend fallback in executeLoopStep never fired, and the session stayed permanently stuck re-sending the same rejected history every turn (observed in the field after a manual compaction busted the prompt cache and forced full revalidation of a latently misordered prefix). Add the tool_call_id-anchored pattern so the whole recovery chain — strict projection (adjacency repair, orphan-result drop, synthetic results) plus the one-shot resend — now also covers the default provider. Covered by classifier unit tests and an e2e resend-and-recover case.
🦋 Changeset detectedLatest commit: 8d27965 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A field session (exported debug zip) was permanently stuck: every turn failed with
The history contained a
toolmessage whosetool_call_idhad no matchingtool_callsentry in the immediately preceding assistant message (a user message had been interleaved into the tool exchange). Moonshot validates this before generation and rejects the request deterministically — and since the same history is re-sent every turn, the session could never recover.Root cause
The recovery chain for exactly this situation already exists (#1241):
repairToolExchangeAdjacencyin the normal projection, plus a one-shot strict resend (strictMessages: adjacency repair + orphan-result drop + synthetic results) inexecuteLoopStepwhen the provider rejects the request structure.But the trigger,
isRecoverableRequestStructureError, only matched Anthropic's phrasing (tool_use/tool_result, roles must alternate, ...). The OpenAI-compatible phrasing used by Moonshot / Kimi —tool_call_id ... is not found— matched nothing, so the strict resend never fired for the default provider and the session stayed bricked. (In the field log:turn failedwith noresending with strict projectionline before it.)Fix
Add a
tool_call_id-anchored pattern toTOOL_EXCHANGE_ADJACENCY_MESSAGE_PATTERNS:/tool_call_id[\s\S]*not found/Anchored on
tool_call_idso an unrelated "not found" body (e.g. 404-style) cannot trip the recovery; still gated on status 400/422 and not-context-overflow, and the resend remains one-shot.Tests
kosong/test/errors.test.ts: classifier accepts the field-observed message (doubled space verbatim) for 400 and 422, on bothisToolExchangeAdjacencyErrorandisRecoverableRequestStructureError; negative case:400 resource not founddoes not match.agent-core/test/loop/tool-exchange-fallback.e2e.test.ts: end-to-end — Moonshot-phrased 400 triggers exactly one strict resend and the turn recovers.apps/kimi-code/test/cli/update/preflight.test.ts(verified pre-existing via stash on a clean tree).