Skip to content

Conversation

@0xMink
Copy link
Contributor

@0xMink 0xMink commented Feb 11, 2026

Closes #11419

Summary

  • processAiSdkStreamPart() silently ignored AI SDK tool-call events, relying entirely on tool-input-start/delta/end lifecycle events 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 with no tool-input-* events for flush-path calls. This caused tool calls to be silently dropped for all OpenRouter-proxied models.
  • Now emits tool-call as a tool_call chunk with string passthrough (avoids double-encoding when upstream JSON parse fails) and ?? {} fallback for undefined input.
  • Added deduplication in Task.ts tool_call handler: 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

  • Existing: 87 ai-sdk tests, 10 duplicate-tool-use-ids tests, 38 Task tests, 12 NativeToolCallParser tests -- all passing
  • New: 3 tests for tool-call emission (object input, undefined input, string passthrough)
  • New: 5 tests for tool_call dedup ordering (skip finalized, replace partial, flush-only push, tool_call-before-end ordering, mixed paths)
  • 155 total tests passing, 4 skipped (pre-existing)

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
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Feb 11, 2026
@roomote
Copy link
Contributor

roomote bot commented Feb 11, 2026

Rooviewer Clock   See task

All previous issues resolved. Test updates in 90a3ee2 correctly align provider specs with the new tool-call emission behavior. No new issues found. LGTM.

  • Dedup check in tool_call handler only matches tool_use blocks, missing mcp_tool_use -- can produce duplicate MCP tool blocks when both streaming and tool-call events arrive
  • Missing text block finalization before pushing tool_use in flush-only path -- delays tool presentation until stream-end cleanup
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment on lines 3107 to 3112
const alreadyPresent = this.assistantMessageContent.some(
(block) =>
block.type === "tool_use" &&
!block.partial &&
(block as any).id === chunk.id,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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
@0xMink
Copy link
Contributor Author

0xMink commented Feb 12, 2026

Acknowledged — both issues addressed in 381faee:

  1. Extended the alreadyPresent guard to include mcp_tool_use blocks, preventing duplicates when MCP tools finalize via the streaming path
  2. Added partial text block finalization in the flush-only tool_call path (mirroring tool_call_start behavior) to avoid delaying tool presentation

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.
@0xMink
Copy link
Contributor Author

0xMink commented Feb 12, 2026

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.

@0xMink
Copy link
Contributor Author

0xMink commented Feb 12, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenRouter proxy: tool calls silently dropped after v3.41.0 (tool-call event ignored)

1 participant