Skip to content

fix(ai): prevent duplicate tool parts when model calls unavailable tool#12556

Open
gdaybrice wants to merge 1 commit intovercel:mainfrom
gdaybrice:fix/duplicate-tool-parts-unavailable-tool
Open

fix(ai): prevent duplicate tool parts when model calls unavailable tool#12556
gdaybrice wants to merge 1 commit intovercel:mainfrom
gdaybrice:fix/duplicate-tool-parts-unavailable-tool

Conversation

@gdaybrice
Copy link
Contributor

Summary

  • Fixes a bug where calling an unavailable tool creates two UI parts with the same toolCallId — a static tool-{name} part and a dynamic-tool part
  • The duplicate causes Bedrock (and potentially other providers) to reject the message with "duplicate Ids" when the message is sent back in conversation history
  • Root cause: tool-input-start arrives without a dynamic flag (creating a static part), then tool-input-error arrives with dynamic: true (from parseToolCall's NoSuchToolError handling) and creates a new dynamic part instead of updating the existing one
  • Fix: check for an existing part before deciding which update function to call, preserving the original part type

Reproduction

  1. Configure an agent with a set of tools
  2. Have the model call a tool name that isn't in the available tools (e.g., model hallucinates a tool name)
  3. The resulting UI message will contain two parts with the same toolCallId — one tool-{name} and one dynamic-tool
  4. On the next turn, convertToModelMessages converts both into tool-call content parts
  5. The Bedrock provider's groupIntoBlocks merges them into one message, and Bedrock rejects it:
    The toolUse blocks at messages.54.content contain duplicate Ids: tooluse_Uel6zAsKe7Lve5LiXPGiyH
    

@gdaybrice gdaybrice force-pushed the fix/duplicate-tool-parts-unavailable-tool branch 3 times, most recently from 45186a0 to d0eaf3a Compare February 13, 2026 07:16
When a model calls a tool that isn't in the available tools list, the
streaming pipeline can create two UI parts with the same toolCallId:

1. tool-input-start arrives without a dynamic flag, creating a static
   tool-{name} part
2. tool-input-error arrives with dynamic: true (set by parseToolCall
   for NoSuchToolError), calling updateDynamicToolPart which doesn't
   find the existing static part and creates a new dynamic-tool part

This causes issues when the message is later sent back to providers
like Amazon Bedrock that validate toolUse ID uniqueness within a
single message.

The fix checks for an existing part before deciding which update
function to call, ensuring the original part type is preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gdaybrice gdaybrice force-pushed the fix/duplicate-tool-parts-unavailable-tool branch from d0eaf3a to 8c550fc Compare February 13, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant