Repository navigation
Description
Follow-up to #18333. #18382 fixed the crash when index is absent or non-zero, and I can confirm that shape is fixed in @ai-sdk/[email protected]. But the tracker now resolves identity from id first, and id is exactly as untrustworthy as index was — so the same class of turn-killing failure moved from one field to the other. Measured on 5.0.21 through both @ai-sdk/[email protected] and @ai-sdk/[email protected], unless noted.
A. Still fatal to the whole turn
A1 — a gateway that omits id. Ollama and several OpenAI-compatible gateways label every parallel call index: 0 and send no id:
{index: 0, type: 'function', function: {name: 'read_file', arguments: '{"path":"p0"}'}}
{index: 0, type: 'function', function: {name: 'write_file', arguments: '{"path":"p1"}'}}
{index: 0, type: 'function', function: {name: 'read_file', arguments: '{"path":"p2"}'}}
→ throws Expected 'id' to be a string. on the first delta. Expected: three calls. Note function.name distinguishes all three, but is never consulted.
A2 — a blank id on a continuation. A whitespace id is non-empty, so it misses the toolCallsById lookup and starts a new call; that delta carries only arguments, so the name check throws:
{index: 0, id: 'call_a', type: 'function', function: {name: 'read_file', arguments: '{"pa'}}
{index: 0, id: ' ', type: 'function', function: {arguments: 'th":"a"}'}}
→ throws Expected 'function.name' to be a string. Expected: one read_file call with input {"path":"a"}.
Both throws happen inside transform, so flush() never runs and every already-complete call in the turn is dropped with them.
B. Silently wrong
B1 — a repeated id merges two calls.
{index: 0, id: 'dup', type: 'function', function: {name: 'read_file', arguments: '{"path":"a"}'}}
{index: 1, id: 'dup', type: 'function', function: {name: 'write_file', arguments: '{"path":"b"}'}}
→ [{toolCallId: 'dup', toolName: 'read_file', input: '{"path":"a"}{"path":"b"}'}]. The write_file call is gone, the differing index is ignored because id wins outright, and the input is not valid JSON. Expected: two calls.
B2 — name: '' mints an unnamable call. The guard is name == null, so an empty string passes:
{index: 0, id: 'call_a', type: 'function', function: {name: '', arguments: '{"path":"a"}'}}
→ [{toolCallId: 'call_a', toolName: '', input: '{"path":"a"}'}]. No tool resolves that.
B3 — an empty wire id is passed through. {index: 0, id: '', ...} → [{toolCallId: '', ...}]. Consumers deriving an operation key from toolCallId get an empty key, and a duplicated one (B1) gets a colliding key — in our case the second call is rejected only after the first tool's side effects have already run.
B4 — latestToolCall absorbs an unattributable delta (openai-compatible only; openai's chunk schema requires index). With two calls open, a delta carrying neither id nor index appends its arguments to whichever call was touched last, with nothing backing the attribution.
Minor: a stream supplying unique out-of-order indices is emitted in insertion order, so {index: 1} before {index: 0} yields the calls reversed.
Expected
Every call the gateway sent is emitted exactly once, with its own name, its own arguments, and a usable unique id; and no shape of id kills the turn.
The common cause is that id and index are both association labels a gateway may omit, blank, or repeat, and neither is safe as sole authority — while function.name, which would separate A1 and B1, is never read at all.
Activity
ai-sdk-factory commentedon Aug 5, 2026
Identify issue type
Identified as Bug
Confidence: High
Rationale
The issue documents reproducible crashes and incorrect tool-call association in existing streamed tool-call handling, including lost, merged, and malformed calls. This is a concrete failure of current behavior rather than a request for new capability.
Evidence
idor use a blankid, aborting the entire turn.ai-sdk-factory commentedon Aug 5, 2026
Bug Reproduction - AI SDK 7
Bug was reproduced.
Branch:
mainPR: successful reproduction pull request
Details
packages/provider-utils/package.json5.0.21,packages/openai-compatible/package.json3.0.23, andpackages/openai/package.json4.0.30, ruling out a partial-upgrade mismatch.examples/ai-functions/src/reproduction/issue-18440-streamed-tool-call-id.tsreproduced A1 and A2 through both provider stacks: omitted IDs threwExpected 'id' to be a string., whitespace continuation IDs threwExpected 'function.name' to be a string., and zero final tool calls were emitted instead of the expected calls.Relevant Documentation
ai-sdk-factory commentedon Aug 5, 2026
Bugfix - AI SDK 7
Created bugfix and opened PR: #18445
Root Cause
The shared tracker treated non-empty IDs as authoritative identities, rejected absent IDs, and used the latest call for ambiguous deltas. The reproduction confirmed these paths across both OpenAI provider stacks.
Summary
The tracker now generates unique usable IDs, correlates deltas using ID, index, and function name, preserves distinct calls with repeated labels, rejects blank names, ignores ambiguous unattributable deltas, and finalizes calls in index order. A patch changeset was added.
Testing
Added tracker regressions for omitted, blank, and repeated IDs; reused indices; blank names; ambiguous deltas; unique generated IDs; and out-of-order indices.
ai-sdk-factory commentedon Aug 5, 2026
Bugfix updated - AI SDK 7
Updated PR: #18445
Root Cause
Correlation still allowed an ID/name match to override a distinct index, validation also canonicalized nonblank strings, and ID generation assumed repeated calls would eventually return a unique value.
Update
Distinct indices now separate repeated ID/name pairs, nonblank IDs and names are preserved exactly, and blank or duplicate generated IDs receive bounded unique fallbacks. The existing patch changeset remains sufficient.
ai-sdk-factory commentedon Aug 11, 2026
Bugfix updated - AI SDK 7
Updated PR: #18445
Root Cause
The matcher still selected an arbitrary call for duplicate-ID continuations, treated matching named ID-less continuations as new calls, and rejected index evidence when a continuation ID changed. Focused regressions reproduced all three paths.
Update
Updated correlation to ignore ambiguous duplicate-ID continuations, retain matching name/index continuations, and use index evidence for unexpected continuation IDs. Wire-ID aliases are retained, and generated-ID suffix searches now resume rather than rescan. The existing changeset remains sufficient.
ai-sdk-factory commentedon Aug 12, 2026
Bug Reproduction - AI SDK 5
Bug was reproduced.
Branch:
release-v5.0PR: successful reproduction pull request
Details
examples/ai-functions/src/reproduction/issue-18440-streamed-tool-call-id.ts, supported byexamples/ai-functions/package.json, expected three distinct usable tool calls but observed both provider stacks throwExpected 'id' to be a string.and emit zero calls when IDs were omitted.@ai-sdk/[email protected],@ai-sdk/[email protected], and@ai-sdk/[email protected]; the issue's AI SDK 7 versions and shared tracker are not present on this branch.read_filecall.toolCallId.Relevant Documentation
ai-sdk-factory commentedon Aug 12, 2026
Bug Reproduction - AI SDK 6
Bug was reproduced.
Branch:
release-v6.0PR: successful reproduction pull request
Details
examples/ai-functions/src/reproduction/issue-18440-streamed-tool-call-id.ts, covering A1-A2, B1-B4, dropped calls, and out-of-order indices through both applicable provider stacks.@ai-sdk/[email protected],@ai-sdk/[email protected], and@ai-sdk/[email protected], A1 throwsExpected 'id' to be a string.through both providers and emits zero final tool calls; a previously started call is also dropped. Expected behavior is three distinct calls with usable unique IDs and no lost calls.read_filecall.Expected 'id' to be a string.and drops both calls instead of absorbing the unattributable delta; out-of-order indices are emitted in correct index order.Relevant Documentation
7 remaining items
fix: prevent unreliable streamed tool-call labels from aborting, corr…
ai-sdk-factory commentedon Sep 28, 2026
Backport to v5.0
skipped: requires a release-specific fix.
v5.0 has the affected behavior, but implements tool-call tracking separately in V2 providers: it throws on missing IDs and merges calls by index. The cherry-pick adds an unexported V4-only tracker without integrating it, plus a conflicted test, so resolving conflicts would not fix v5.0; a native V2 custom fix is required.
ai-sdk-factory commentedon Sep 28, 2026
Backport to v6.0
skipped: requires a release-specific fix.
v6.0 remains affected: its seven providers use V3, index-keyed local tool-call state and still throw on missing IDs. The main fix targets a shared V4 tracker that does not exist or export on v6.0, so the cherry-pick adds unusable files without integrating production code; a release-specific implementation is required.
fix #18440
ai-sdk-factory commentedon Sep 28, 2026
Bugfix - AI SDK 6
Created bugfix and opened PR: #21574
Root Cause
The V3 stream parsers treated unreliable wire IDs and indices as authoritative identities. The original replay confirmed that omitted IDs threw an error and dropped all calls, while focused scenarios exposed duplicate IDs, reused indices, and ambiguous deltas.
Summary
Added a shared streaming tool-call tracker that combines ID, index, function name, completeness, and ambiguity evidence; generates unique usable IDs; and is now used by both OpenAI chat providers. Added a patch changeset for the affected packages.
Testing
Added 10 tracker regressions covering omitted, blank, changed, generated, and repeated IDs, reused and out-of-order indices, blank names, and ambiguous deltas.
[v6.0] fix: preserve streamed tool calls when gateway IDs or indices …
github-actions commentedon Sep 28, 2026
🚀 Published in:
ai@ai-sdk/alibaba@ai-sdk/amazon-bedrock@ai-sdk/angular@ai-sdk/anthropic@ai-sdk/anthropic-aws@ai-sdk/assemblyai@ai-sdk/azure@ai-sdk/baseten@ai-sdk/black-forest-labs@ai-sdk/bytedance@ai-sdk/cartesia@ai-sdk/cerebras@ai-sdk/code-mode@ai-sdk/cohere@ai-sdk/deepgram@ai-sdk/deepinfra@ai-sdk/deepseek@ai-sdk/devtools@ai-sdk/elevenlabs@ai-sdk/fal@ai-sdk/fireworks@ai-sdk/fish-audio@ai-sdk/gateway@ai-sdk/gladia@ai-sdk/gmicloud@ai-sdk/google@ai-sdk/google-vertex@ai-sdk/groq@ai-sdk/harness@ai-sdk/harness-acp@ai-sdk/harness-claude-code@ai-sdk/harness-cline@ai-sdk/harness-codex@ai-sdk/harness-cursor@ai-sdk/harness-deepagents@ai-sdk/harness-fx@ai-sdk/harness-github-copilot@ai-sdk/harness-grok-build@ai-sdk/harness-opencode@ai-sdk/harness-pi@ai-sdk/huggingface@ai-sdk/hume@ai-sdk/klingai@ai-sdk/langchain@ai-sdk/llamaindex@ai-sdk/luma@ai-sdk/mcp@ai-sdk/minimax@ai-sdk/mistral@ai-sdk/moonshotai@ai-sdk/open-responses@ai-sdk/openai@ai-sdk/openai-compatible@ai-sdk/otel@ai-sdk/perplexity@ai-sdk/policy-opa@ai-sdk/prodia@ai-sdk/provider@ai-sdk/provider-utils@ai-sdk/quiverai@ai-sdk/react@ai-sdk/replicate@ai-sdk/revai@ai-sdk/rsc@ai-sdk/sandbox-just-bash@ai-sdk/sandbox-vercel@ai-sdk/svelte@ai-sdk/togetherai@ai-sdk/tui@ai-sdk/typesafe-ai@ai-sdk/valibot@ai-sdk/voyage@ai-sdk/vue@ai-sdk/workflow@ai-sdk/workflow-harness@ai-sdk/xai@ai-sdk/zainazerim commentedon Oct 1, 2026
Confirming PR #18445's fix covers the
name:null-on-opening-fragment variant: grepping published tarballs, the throw stringExpected 'function.name'is present in @ai-sdk/openai-compatible 1.x/2.x (sampled 1.0.0, 2.0.0, 2.0.7) and absent from 3.0.0 onward (checked 3.0.0 + 3.0.62). Behaviorally verified today: feeding the exact malformed stream (id present,name:nullon the opening fragment, name arriving in a later delta, args split across fragments) through[email protected]'sdoStreamdoes not throw — it buffers and emits a correcttool-callwith merged args.Note for others hitting this error: if your client is opencode 1.x, upgrading the npm package does not help — the SDK is compiled into the binary as a pre-3.0.0 copy (filed with evidence + deterministic synthetic-SSE repro: anomalyco/opencode#52462). Interim client-side guard plugin: https://github.com/nazerim/toolcall-guard