Skip to content

Conversation

@9j
Copy link

@9j 9j commented Nov 21, 2025

close #1279

Problem

Some providers (e.g. Gemini) don't generate tool call IDs, resulting in empty callID being stored. When switching to providers that require tool call IDs (e.g. Anthropic, OpenAI), this causes API errors with "id": "" in tool_use blocks.

Solution

  • Generate fallback ulid() when value.id is empty from stream events
  • Make toModelMessage async to fix empty callID in existing stored data
  • Update storage with generated ID when empty callID is found

Test plan

  • Use Gemini model to create tool calls in a session
  • Switch to Anthropic/OpenAI model and continue the session
  • Verify no API errors about empty tool call IDs

return false
}),
),
...modelMessages,
Copy link
Collaborator

Choose a reason for hiding this comment

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

what was the reason for this?

Copy link
Author

Choose a reason for hiding this comment

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

@rekram1-node extracted it for readability since the streamtext call was getting too long, but yeah could use ...(await ...) too. should i revert to inline style?

return false
}),
),
...modelMessages,
Copy link
Collaborator

Choose a reason for hiding this comment

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

what's the reason for this change?

@rekram1-node
Copy link
Collaborator

@9j are you sure this fixes the issue? I can't get google to not generate ids

@rekram1-node
Copy link
Collaborator

ahh I see ur message it was specifically with the invalid tool

@rekram1-node
Copy link
Collaborator

async experimental_repairToolCall(input) {
            const lower = input.toolCall.toolName.toLowerCase()
            if (lower !== input.toolCall.toolName && tools[lower]) {
              log.info("repairing tool call", {
                tool: input.toolCall.toolName,
                repaired: lower,
              })
              return {
                ...input.toolCall,
                toolName: lower,
              }
            }
            return {
              ...input.toolCall,
              input: JSON.stringify({
                tool: input.toolCall.toolName,
                error: input.error.message,
              }),
              toolName: "invalid",
            }
          },

I believe, the correct fix is here, all the other changes you made can be undone, just make sure that the id is set here

@github-actions github-actions bot force-pushed the dev branch 2 times, most recently from 917250b to f4593c6 Compare November 22, 2025 05:02
@9j
Copy link
Author

9j commented Nov 22, 2025

@9j are you sure this fixes the issue? I can't get google to not generate ids

#1279 (comment)

There are cases like this. And when I built and tested with the version of the code I wrote, this problem was resolved.

async experimental_repairToolCall(input) {
            const lower = input.toolCall.toolName.toLowerCase()
            if (lower !== input.toolCall.toolName && tools[lower]) {
              log.info("repairing tool call", {
                tool: input.toolCall.toolName,
                repaired: lower,
              })
              return {
                ...input.toolCall,
                toolName: lower,
              }
            }
            return {
              ...input.toolCall,
              input: JSON.stringify({
                tool: input.toolCall.toolName,
                error: input.error.message,
              }),
              toolName: "invalid",
            }
          },

I believe, the correct fix is here, all the other changes you made can be undone, just make sure that the id is set here

  1. experimental_repairToolCall is only invoked when a tool call fails.

    • An empty ID is still problematic for normal tool calls.
    • Even a normal tool call from providers like Gemini can have an empty ID.
  2. Issue with already saved data

    • Parts that have previously been saved with an empty callID are still problematic.

@rekram1-node
Copy link
Collaborator

rekram1-node commented Nov 22, 2025

Yeah can you show me a tool call from gemini that has no id? Your example didnt have that but it did have the invalid tool missing an id

Yeah we can do the other thing repair the session too but I think this is an edge case when the model calls a tool that doesnt exist

But maybe it happens more often with gemini can u show an example?

@9j
Copy link
Author

9j commented Nov 22, 2025

Yeah can you show me a tool call from gemini that has no id? Your example didnt have that but it did have the invalid tool missing an id

Yeah we can do the other thing repair the session too but I think this is an edge case when the model calls a tool that doesnt exist

But maybe it happens more often with gemini can u show an example?

I experienced this issue when switching from Gemini 3 Pro to Claude. Here's what I found:

Evidence from my storage:

// Message using zenmux/google/gemini-3-pro-preview
{
  "id": "prt_aa63f171d0016pLk11oWQ8QG8D",
  "callID": "",  // ← Empty callID from Gemini
  "tool": "list"  // ← Normal tool call, not invalid
}

You're right that the invalid tool case is when the model calls a non-existent tool. But I also found multiple cases where Gemini 3 Pro generates empty callIDs for normal tool
calls (list, edit, bash, etc.).

Not sure if this is model-specific or provider-specific, but all empty callIDs in my storage came from the same session where I used Gemini 3 Pro and then switched to Claude.
The empty callIDs caused API errors when continuing the conversation with Claude.

@rekram1-node
Copy link
Collaborator

ah okay makes sense, just wanted to avoid code if we could help it, I guess if u can resolve the conflicts this is basically good to go

@9j 9j force-pushed the fix/empty-tool-call-id branch from bc8d99c to 7ec9c1b Compare November 23, 2025 05:14
@9j
Copy link
Author

9j commented Nov 23, 2025

@rekram1-node done!

Some providers (e.g. Gemini) don't generate tool call IDs, resulting in
empty callID being stored. This causes errors when switching to providers
that require tool call IDs (e.g. Anthropic, OpenAI).

- Generate fallback ulid() when value.id is empty from stream events
- Make toModelMessage async to fix empty callID in existing stored data
- Update storage with generated ID when empty callID is found
@9j 9j force-pushed the fix/empty-tool-call-id branch from 7ec9c1b to e0feae2 Compare November 25, 2025 12:27
@9j
Copy link
Author

9j commented Nov 25, 2025

@rekram1-node done again

@9j 9j requested a review from rekram1-node November 25, 2025 12:28
@rekram1-node
Copy link
Collaborator

ill merge in the morning thanks for your work on this

@9j
Copy link
Author

9j commented Nov 29, 2025

@rekram1-node Just a friendly bump! 👋 Let me know if there's anything else needed from my side before merging.

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.

AI_APICallError: messages.1.content.2: tool_use ids must be unique

2 participants