What happened?
Replaying a session's history to Kimi (moonshotai-cn, openai-completions, kimi-k2/kimi-k3) fails with 400s that lenient providers never trigger:
Invalid request: messages with role 'tool' must be a response to a preceding message with 'tool_calls'
Invalid request: tokenization failed
Root causes, all in transformMessages:
- Errored/aborted assistant messages are skipped on replay, but their tool results are kept -> orphaned
tool message (error 1).
- Custom messages (e.g. background-task notifications) interleaved between an assistant tool call and its result serialize as
user messages -> tool result follows a user message (error 1).
- The synthetic "No result provided" inserted for an interrupted tool call remains next to the real result -> two tool messages with the same tool_call_id (error 2).
Steps to reproduce
- Use an OpenAI-compatible provider that validates message order, e.g. moonshotai-cn with kimi-k2 or kimi-k3.
- In a session, let the model call a tool, then interrupt the turn (abort/error mid tool-call), or use a background-task tool (subagent_wait) whose notification lands before the tool result.
- Send a follow-up message. pi replays the history and the API returns the 400 above.
Verified by replaying the exact failing 275-message history against the real Moonshot API: 400 before the fix, HTTP 200 after.
Expected behavior
Replayed history stays structurally valid for every provider: no tool message without a preceding assistant tool_calls message, no user message between tool_calls and its result, no duplicate tool_call_id.
Version
0.84.2
I have a fix and unit tests ready on my fork (wulong-t/pi, branch fix/tool-result-history-normalization); PR #8536 was auto-closed.
What happened?
Replaying a session's history to Kimi (moonshotai-cn, openai-completions, kimi-k2/kimi-k3) fails with 400s that lenient providers never trigger:
Invalid request: messages with role 'tool' must be a response to a preceding message with 'tool_calls'Invalid request: tokenization failedRoot causes, all in
transformMessages:toolmessage (error 1).usermessages -> tool result follows a user message (error 1).Steps to reproduce
Verified by replaying the exact failing 275-message history against the real Moonshot API: 400 before the fix, HTTP 200 after.
Expected behavior
Replayed history stays structurally valid for every provider: no tool message without a preceding assistant tool_calls message, no user message between tool_calls and its result, no duplicate tool_call_id.
Version
0.84.2
I have a fix and unit tests ready on my fork (
wulong-t/pi, branchfix/tool-result-history-normalization); PR #8536 was auto-closed.