Skip to content

fix(gateway): preserve parallel tool calls in the streaming path#17

Merged
albanm merged 1 commit into
mainfrom
fix-parallel-tool-calls
Jun 9, 2026
Merged

fix(gateway): preserve parallel tool calls in the streaming path#17
albanm merged 1 commit into
mainfrom
fix-parallel-tool-calls

Conversation

@albanm

@albanm albanm commented Jun 9, 2026

Copy link
Copy Markdown
Member

Fix the gateway's streaming (SSE) path silently dropping all but the first
parallel tool call.

What changed: in the OpenAI streaming wire format, parallel tool calls are
distinguished only by their index. The gateway hardcoded index: 0 on every
tool-input-start/tool-input-delta chunk, so the openai-compatible client
collapsed concurrent calls into a single index-0 slot. Each call now gets a
stable incrementing index keyed by its id. The non-streaming generateText
path was already correct.

Also extends the mock model with a call tools <name> <name> … seam that emits
parallel calls, and adds a regression test driving two parallel calls through
streamText that asserts both survive.

Why: parallel tool calls were broken all along in the live gateway; the
trace feature surfaced it.

Regression risks:

  • Mock tool-call id changed from mock-tool-call-id to mock-tool-call-id-{idx}
    (test-only; no test depends on the old literal).
  • The tool-input-delta index lookup falls back to 0 only when no matching
    tool-input-start was seen — a safe guard, not a live path.

The streaming SSE path hardcoded index: 0 for every tool-call chunk in
the OpenAI wire format, where parallel calls are distinguished only by
that index. The openai-compatible client collapsed concurrent calls into
a single index-0 slot, silently dropping all but the first.

Assign a stable incrementing index per tool-call id so each call keeps a
distinct slot. The non-streaming generateText path was already correct.

Also extend the mock model with a "call tools <name> <name>" seam that
emits parallel tool calls, and add a regression test driving two parallel
calls through streamText that asserts both survive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@albanm albanm merged commit 3b14a2c into main Jun 9, 2026
3 checks passed
@github-actions github-actions Bot added the fix label Jun 9, 2026
@albanm albanm deleted the fix-parallel-tool-calls branch June 9, 2026 16:09
albanm added a commit that referenced this pull request Jun 9, 2026
Reconcile the trace-storage work with main's quota refactor (#16) and
streaming parallel-tool-call fix (#17):
- gateway/summary routers adopt resolveUsageIdentity/enforceQuotas from
  usage/enforce.ts while keeping trace recording and the streamed
  tool-call capture; merged the per-id toolCallIndex with streamedToolCalls
- defaultQuotas (now incl. untrusted) stays centralized in settings/service.ts;
  routers fall back to it, enforce.ts uses NonNullable<Settings['quotas']>
- settings.quotas/models remain optional (storeTraces form work)
- regenerated put-req validate.js and vjsf components from the merged schema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant