fix(ai-sdk): emit tool-call events for OpenRouter compatibility#11420
fix(ai-sdk): emit tool-call events for OpenRouter compatibility#114200xMink wants to merge 3 commits intoRooCodeInc:mainfrom
Conversation
processAiSdkStreamPart() silently ignored AI SDK tool-call events, relying entirely on tool-input-start/delta/end for tool delivery. @openrouter/ai-sdk-provider v2.1.1 does not emit tool-input-end for incrementally streamed tool calls, and emits only tool-call (no tool-input-* events) for flush-path tool calls. This caused tool calls to be silently dropped, triggering false "no tools used" errors for users proxying through OpenRouter. - Emit tool-call as tool_call chunk instead of ignoring it - String input passthrough to avoid double-encoding when upstream JSON parse fails - Deduplicate in Task.ts tool_call handler: skip already-finalized tools, replace in-flight partial tools, push for flush-only path - 8 new tests covering emission, serialization, and dedup ordering
All previous issues resolved. Test updates in 90a3ee2 correctly align provider specs with the new tool-call emission behavior. No new issues found. LGTM.
Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues. |
| const alreadyPresent = this.assistantMessageContent.some( | ||
| (block) => | ||
| block.type === "tool_use" && | ||
| !block.partial && | ||
| (block as any).id === chunk.id, | ||
| ) |
There was a problem hiding this comment.
The dedup check only matches block.type === "tool_use", but NativeToolCallParser.finalizeStreamingToolCall() returns McpToolUse (type "mcp_tool_use") for dynamic MCP tools (mcp--serverName--toolName). When such a tool is already finalized via the streaming path, the block has type: "mcp_tool_use" and streamingToolCallIndices has been cleaned up, so both guards miss it. parseToolCall then returns a second McpToolUse which gets pushed as a duplicate. This only triggers when MCP tools are proxied through OpenRouter and both streaming events and tool-call are emitted for the same call.
| const alreadyPresent = this.assistantMessageContent.some( | |
| (block) => | |
| block.type === "tool_use" && | |
| !block.partial && | |
| (block as any).id === chunk.id, | |
| ) | |
| const alreadyPresent = this.assistantMessageContent.some( | |
| (block) => | |
| (block.type === "tool_use" || block.type === "mcp_tool_use") && | |
| !block.partial && | |
| (block as any).id === chunk.id, | |
| ) |
Fix it with Roo Code or mention @roomote and request a fix.
…lush-only tools Address review feedback: - Extend dedup predicate to match mcp_tool_use blocks, preventing duplicate MCP tool entries when both streaming and tool-call events arrive for the same call - Finalize preceding partial text block in flush-only path to prevent stalling tool presentation until stream-end cleanup - Add regression tests for both scenarios
|
Acknowledged — both issues addressed in 381faee:
Regression tests added for both scenarios (17/17 passing in duplicate-tool-use-ids suite, 128/128+4 skipped across ai-sdk and Task suites). |
Four provider test suites (deepseek, mistral, moonshot, openrouter) still asserted that tool-call events are ignored. Update them to assert the new behavior: tool-call emits a tool_call chunk, with deduplication handled by Task.ts.
|
Pushed CI fix 90a3ee2: updated provider test suites (DeepSeek, Mistral, Moonshot, OpenRouter) to expect tool_call chunk emission. Local: 107/107 passing across those suites. |
|
CI green. All review feedback addressed across 3 commits. Broad Unicode/control-char scan clean (no bidi, zero-width, BOM, soft hyphen, or formatting chars). Ready for re-review. |
Closes #11419
Summary
processAiSdkStreamPart()silently ignored AI SDKtool-callevents, relying entirely ontool-input-start/delta/endlifecycle events for tool delivery.@openrouter/ai-sdk-providerv2.1.1 does not emittool-input-endfor incrementally streamed tool calls, and emits onlytool-callwith notool-input-*events for flush-path calls. This caused tool calls to be silently dropped for all OpenRouter-proxied models.tool-callas atool_callchunk with string passthrough (avoids double-encoding when upstream JSON parse fails) and?? {}fallback for undefined input.Task.tstool_callhandler: skips tools already finalized via streaming path, replaces in-flight partial tools, pushes new blocks for flush-only path. All three OpenRouter streaming paths are now handled correctly.Test plan