fix(core): enforce tool result adjacency#4638
Conversation
| // Filter out tool calls that don't have corresponding responses | ||
| const validToolCalls = message.tool_calls.filter( | ||
| (toolCall) => toolCall.id && toolResponseIds.has(toolCall.id), | ||
| (toolCall) => |
There was a problem hiding this comment.
[Suggestion] When adjacency enforcement orphans ALL tool calls on an assistant message that has content: '' and reasoning_content: 'thinking...', the fallback check at line 1453 (message.content.trim()) evaluates to falsy and the entire message — including reasoning_content — is silently dropped.
processContent produces content: '' for thinking models (line 677: assistantTextContent || (reasoningParts.length > 0 ? '' : null)). Providers like DeepSeek, Qwen3, and Mimo all consume reasoning_content from the outgoing history. Losing it means the model loses its own chain-of-thought from prior turns.
This is more likely to trigger now than before the PR, because the old global-match approach would have preserved the message.
| (toolCall) => | |
| } else if ( | |
| (typeof message.content === 'string' && message.content.trim()) || | |
| ('reasoning_content' in message && | |
| typeof (message as OpenAI.Chat.ChatCompletionAssistantMessageParam & { reasoning_content?: string }).reasoning_content === 'string' && | |
| ((message as OpenAI.Chat.ChatCompletionAssistantMessageParam & { reasoning_content?: string }).reasoning_content?.length ?? 0) > 0) | |
| ) { |
— qwen3.7-max via Qwen Code /review
Summary
Fixes #4619.
cleanOrphanedToolCalls()was checking tool call IDs globally, so it could keep a tool result even when another user turn had broken the required assistant-tool adjacency. Anthropic-compatible APIs reject that shape because eachtool_resultmust answer the immediately previous assistanttool_useturn.This changes the cleanup pass to keep only tool responses that directly follow the assistant message that declared their tool call. It also removes the matching assistant tool call when its response is no longer adjacent.
Tests