Skip to content

After #18382, streamed tool calls that omit or blank the id still kill the turn #18440

Closed

Description

@Astro-Han

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

  1. ai-sdk-factory commented on Aug 5, 2026

    @ai-sdk-factory
    Contributor

    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

    • Current provider-utils behavior throws when streamed tool calls omit id or use a blank id, aborting the entire turn.
    • Repeated IDs incorrectly merge distinct tool calls, producing invalid combined input and dropping a call.
    • Empty names and IDs are emitted unchanged, and unattributable deltas may be appended to the wrong call.
    • The failures were reproduced with specific current package versions after the related fix in fix: handle streamed tool calls with irregular or missing indexes #18382.
  2. ai-sdk-factory commented on Aug 5, 2026

    @ai-sdk-factory
    Contributor

    Bug Reproduction - AI SDK 7

    Bug was reproduced.

    Branch: main
    PR: successful reproduction pull request

    Details

    • The checked-out manifests match the reported versions: packages/provider-utils/package.json 5.0.21, packages/openai-compatible/package.json 3.0.23, and packages/openai/package.json 4.0.30, ruling out a partial-upgrade mismatch.
    • examples/ai-functions/src/reproduction/issue-18440-streamed-tool-call-id.ts reproduced A1 and A2 through both provider stacks: omitted IDs threw Expected 'id' to be a string., whitespace continuation IDs threw Expected 'function.name' to be a string., and zero final tool calls were emitted instead of the expected calls.
    • A complete call preceding an omitted-ID failure was also dropped, confirming that the transform error prevents turn finalization.
    • B1-B3 matched the report through both providers: repeated IDs merged calls into invalid concatenated input, empty names were emitted, and empty tool-call IDs passed through unchanged.
    • OpenAI-compatible B4 appended an unattributable delta to the latest call, and out-of-order indices were emitted in insertion order rather than index order.

    Relevant Documentation

  3. ai-sdk-factory commented on Aug 5, 2026

    @ai-sdk-factory
    Contributor

    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.

  4. ai-sdk-factory commented on Aug 5, 2026

    @ai-sdk-factory
    Contributor

    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.

  5. ai-sdk-factory commented on Aug 11, 2026

    @ai-sdk-factory
    Contributor

    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.

  6. ai-sdk-factory commented on Aug 12, 2026

    @ai-sdk-factory
    Contributor

    Bug Reproduction - AI SDK 5

    Bug was reproduced.

    Branch: release-v5.0
    PR: successful reproduction pull request

    Details

    • examples/ai-functions/src/reproduction/issue-18440-streamed-tool-call-id.ts, supported by examples/ai-functions/package.json, expected three distinct usable tool calls but observed both provider stacks throw Expected 'id' to be a string. and emit zero calls when IDs were omitted.
    • The target manifests contain the aligned release-v5 packages @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.
    • A blank continuation ID did not reproduce A2 on release-v5: both providers associated it by index and emitted one complete read_file call.
    • A complete call preceding an omitted-ID failure was emitted before the error, so release-v5 did not reproduce the reported loss of every earlier complete call.
    • Repeated IDs did not merge calls on release-v5; both calls retained their own names and inputs but were emitted with the same colliding toolCallId.
    • Both providers reproduced B2 and B3 by emitting an empty tool name and an empty tool-call ID unchanged.
    • The OpenAI-compatible provider rejected B4's missing index as a stream validation error instead of appending the unattributable arguments to another call.
    • Out-of-order complete calls were emitted in arrival order, reproducing the reported reversed ordering.

    Relevant Documentation

  7. ai-sdk-factory commented on Aug 12, 2026

    @ai-sdk-factory
    Contributor

    Bug Reproduction - AI SDK 6

    Bug was reproduced.

    Branch: release-v6.0
    PR: successful reproduction pull request

    Details

    • Added 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.
    • On @ai-sdk/[email protected], @ai-sdk/[email protected], and @ai-sdk/[email protected], A1 throws Expected '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.
    • A2 does not reproduce on this branch: a whitespace continuation ID is associated by index and produces one complete read_file call.
    • B1 preserves both calls and their inputs but emits the duplicate wire ID for both, rather than merging them as reported; B2 and B3 reproduce exactly by emitting an empty tool name and empty tool-call ID.
    • B4 throws 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

  8. 7 remaining items

  9. added a commit that references this issue on Sep 28, 2026
    e3605f6
  10. ai-sdk-factory commented on Sep 28, 2026

    @ai-sdk-factory
    Contributor

    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.

  11. ai-sdk-factory commented on Sep 28, 2026

    @ai-sdk-factory
    Contributor

    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.

  12. added a commit that references this issue on Sep 28, 2026
    4fed402
  13. ai-sdk-factory commented on Sep 28, 2026

    @ai-sdk-factory
    Contributor

    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.

  14. added a commit that references this issue on Sep 28, 2026
    29dc427
  15. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    🚀 Published in:

    Package Version
    ai 7.0.119 github npm
    @ai-sdk/alibaba 2.0.57 github npm
    @ai-sdk/amazon-bedrock 5.0.98 github npm
    @ai-sdk/angular 3.0.119 github npm
    @ai-sdk/anthropic 4.0.66 github npm
    @ai-sdk/anthropic-aws 2.0.58 github npm
    @ai-sdk/assemblyai 3.0.50 github npm
    @ai-sdk/azure 4.0.83 github npm
    @ai-sdk/baseten 2.1.36 github npm
    @ai-sdk/black-forest-labs 2.0.51 github npm
    @ai-sdk/bytedance 2.0.53 github npm
    @ai-sdk/cartesia 3.0.45 github npm
    @ai-sdk/cerebras 3.0.58 github npm
    @ai-sdk/code-mode 1.0.76 github npm
    @ai-sdk/cohere 4.0.51 github npm
    @ai-sdk/deepgram 3.1.21 github npm
    @ai-sdk/deepinfra 3.0.58 github npm
    @ai-sdk/deepseek 3.0.55 github npm
    @ai-sdk/devtools 1.0.25 github npm
    @ai-sdk/elevenlabs 3.0.51 github npm
    @ai-sdk/fal 3.0.51 github npm
    @ai-sdk/fireworks 3.0.61 github npm
    @ai-sdk/fish-audio 3.0.28 github npm
    @ai-sdk/gateway 4.0.97 github npm
    @ai-sdk/gladia 3.0.50 github npm
    @ai-sdk/gmicloud 3.0.29 github npm
    @ai-sdk/google 4.0.83 github npm
    @ai-sdk/google-vertex 5.0.96 github npm
    @ai-sdk/groq 4.0.51 github npm
    @ai-sdk/harness 1.0.129 github npm
    @ai-sdk/harness-acp 1.0.67 github npm
    @ai-sdk/harness-claude-code 1.0.133 github npm
    @ai-sdk/harness-cline 1.0.56 github npm
    @ai-sdk/harness-codex 1.0.131 github npm
    @ai-sdk/harness-cursor 1.0.42 github npm
    @ai-sdk/harness-deepagents 1.0.129 github npm
    @ai-sdk/harness-fx 1.0.42 github npm
    @ai-sdk/harness-github-copilot 1.0.24 github npm
    @ai-sdk/harness-grok-build 1.0.66 github npm
    @ai-sdk/harness-opencode 1.0.131 github npm
    @ai-sdk/harness-pi 1.0.131 github npm
    @ai-sdk/huggingface 2.0.58 github npm
    @ai-sdk/hume 3.0.50 github npm
    @ai-sdk/klingai 4.0.52 github npm
    @ai-sdk/langchain 3.0.119 github npm
    @ai-sdk/llamaindex 3.0.119 github npm
    @ai-sdk/luma 3.0.51 github npm
    @ai-sdk/mcp 2.0.61 github npm
    @ai-sdk/minimax 3.0.43 github npm
    @ai-sdk/mistral 4.0.53 github npm
    @ai-sdk/moonshotai 3.0.59 github npm
    @ai-sdk/open-responses 2.0.55 github npm
    @ai-sdk/openai 4.0.79 github npm
    @ai-sdk/openai-compatible 3.0.58 github npm
    @ai-sdk/otel 1.0.119 github npm
    @ai-sdk/perplexity 5.0.2 github npm
    @ai-sdk/policy-opa 1.0.119 github npm
    @ai-sdk/prodia 2.0.51 github npm
    @ai-sdk/provider 4.0.19 github npm
    @ai-sdk/provider-utils 5.0.50 github npm
    @ai-sdk/quiverai 2.0.51 github npm
    @ai-sdk/react 4.0.122 github npm
    @ai-sdk/replicate 3.0.51 github npm
    @ai-sdk/revai 3.0.50 github npm
    @ai-sdk/rsc 3.0.119 github npm
    @ai-sdk/sandbox-just-bash 1.0.129 github npm
    @ai-sdk/sandbox-vercel 1.0.129 github npm
    @ai-sdk/svelte 5.0.119 github npm
    @ai-sdk/togetherai 3.0.59 github npm
    @ai-sdk/tui 1.0.120 github npm
    @ai-sdk/typesafe-ai 3.0.9 github npm
    @ai-sdk/valibot 3.0.50 github npm
    @ai-sdk/voyage 2.0.50 github npm
    @ai-sdk/vue 4.0.119 github npm
    @ai-sdk/workflow 2.0.50 github npm
    @ai-sdk/workflow-harness 1.0.129 github npm
    @ai-sdk/xai 5.0.11 github npm
    @ai-sdk/zai 3.0.20 github npm
  16. nazerim commented on Oct 1, 2026

    @nazerim

    Confirming PR #18445's fix covers the name:null-on-opening-fragment variant: grepping published tarballs, the throw string Expected '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:null on the opening fragment, name arriving in a later delta, args split across fragments) through [email protected]'s doStream does not throw — it buffers and emits a correct tool-call with 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

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions