Skip to content

fix(ai): make chat({ stream: false }) actually non-streaming on the wire#561

Draft
tombeckenham wants to merge 1 commit into
mainfrom
557-chat-stream-false-still-streams-on-the-wire-uses-runstreamingtext-+-streamtotext
Draft

fix(ai): make chat({ stream: false }) actually non-streaming on the wire#561
tombeckenham wants to merge 1 commit into
mainfrom
557-chat-stream-false-still-streams-on-the-wire-uses-runstreamingtext-+-streamtotext

Conversation

@tombeckenham
Copy link
Copy Markdown
Contributor

Closes #557.

Summary

  • Adds optional TextAdapter.chatNonStreaming() returning Promise<NonStreamingChatResult> ({ content, reasoning?, toolCalls?, finishReason?, usage? }). When implemented, chat({ stream: false }) routes through it for a true wire-level non-streaming request; when omitted, the legacy stream-then-concatenate fallback applies — out-of-tree adapters keep working unchanged.
  • Implements chatNonStreaming() on all in-tree adapters: openai-base chat-completions + responses (covers openai, grok, groq), openrouter (chat-completions + responses), anthropic, gemini, ollama.
  • Rewires runNonStreamingText as a wire-level non-streaming agent loop: call adapter → execute returned tool calls server-side → append results → repeat until finishReason !== 'tool_calls' or agentLoopStrategy halts. Approval-required and pure client-side tools throw an explicit error directing callers to streaming, since they can't round-trip through Promise<string>.

Why

Previously chat({ stream: false }) invoked runStreamingText() and concatenated the SSE response via streamToText. The wire request still carried Accept: text/event-stream and "stream": true in the body — only the SDK return type changed (Promise<string> instead of AsyncIterable<StreamChunk>). On reasoning models with long pre-content thinking phases (Grok 4.3 via OpenRouter was the original repro), proxies with sub-30s socket-idle timeouts could cut off the stream mid-flight even though a real non-streaming POST would complete fine.

Test plan

  • Unit: 5 new tests covering wire-level dispatch, multi-turn tool loop, agentLoopStrategy cap, approval-tool error, and out-of-tree fallback. Existing 757 tests still pass (762 total).
  • E2E: rewired api.chat.ts so feature === 'one-shot-text' actually calls chat({ stream: false }). The existing one-shot-text.spec.ts now runs end-to-end against the new path across all 8 providers (openai, anthropic, gemini, ollama, groq, grok, openrouter, openrouter-responses). Updated the aimock fixture to include systemFingerprint: null so OpenRouter SDK's strict zod validation accepts the response. Full e2e suite (190 tests) passes.
  • pnpm test:lib, pnpm test:types, pnpm test:eslint, pnpm build: all clean.

Notes

  • Marked draft per author's request — wants more review before merging.
  • Middleware is intentionally not invoked on the non-streaming path, mirroring the silence of the existing adapter.structuredOutput call inside runAgenticStructuredOutput.
  • The Promise<string> return value remains content-only — reasoning is captured on the internal NonStreamingChatResult for future surfaces but is not part of the public contract.

🤖 Generated with Claude Code

Adds an optional `chatNonStreaming()` method to the TextAdapter
interface and a wire-level non-streaming agent loop in
`runNonStreamingText`. All in-tree adapters (openai-base
chat-completions + responses, openrouter, anthropic, gemini, ollama)
implement it; out-of-tree adapters fall back to the legacy
stream-then-concatenate behaviour. Closes #557.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@tombeckenham tombeckenham force-pushed the 557-chat-stream-false-still-streams-on-the-wire-uses-runstreamingtext-+-streamtotext branch from 8d1c292 to 02e4765 Compare May 15, 2026 00:11
@github-actions
Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

9 package(s) bumped directly, 25 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-anthropic 0.8.7 → 1.0.0 Changeset
@tanstack/ai-gemini 0.10.4 → 1.0.0 Changeset
@tanstack/ai-grok 0.8.0 → 1.0.0 Changeset
@tanstack/ai-groq 0.2.0 → 1.0.0 Changeset
@tanstack/ai-ollama 0.6.14 → 1.0.0 Changeset
@tanstack/ai-openai 0.9.0 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.9.0 → 1.0.0 Changeset
@tanstack/openai-base 0.3.0 → 1.0.0 Changeset
@tanstack/ai-code-mode 0.1.11 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.1.11 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.4 → 1.0.0 Dependent
@tanstack/ai-event-client 0.3.1 → 1.0.0 Dependent
@tanstack/ai-fal 0.7.4 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.11 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.11 → 1.0.0 Dependent
@tanstack/ai-preact 0.6.23 → 1.0.0 Dependent
@tanstack/ai-react 0.9.0 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.6.4 → 1.0.0 Dependent
@tanstack/ai-solid 0.8.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.6.4 → 1.0.0 Dependent
@tanstack/ai-svelte 0.8.0 → 1.0.0 Dependent
@tanstack/ai-vue 0.8.0 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.17.0 → 0.18.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-client 0.9.2 → 0.9.3 Dependent
@tanstack/ai-code-mode-models-eval 0.0.16 → 0.0.17 Dependent
@tanstack/ai-devtools-core 0.3.28 → 0.3.29 Dependent
@tanstack/ai-isolate-cloudflare 0.2.2 → 0.2.3 Dependent
@tanstack/ai-vue-ui 0.1.34 → 0.1.35 Dependent
@tanstack/preact-ai-devtools 0.1.32 → 0.1.33 Dependent
@tanstack/react-ai-devtools 0.2.32 → 0.2.33 Dependent
@tanstack/solid-ai-devtools 0.2.32 → 0.2.33 Dependent
ts-svelte-chat 0.1.42 → 0.1.43 Dependent
ts-vue-chat 0.1.42 → 0.1.43 Dependent
vanilla-chat 0.0.38 → 0.0.39 Dependent

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 15, 2026

View your CI Pipeline Execution ↗ for commit 02e4765

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 1m 52s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-15 00:13:45 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 15, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@561

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@561

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@561

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@561

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@561

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@561

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@561

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@561

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@561

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@561

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@561

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@561

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@561

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@561

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@561

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@561

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@561

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@561

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@561

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@561

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@561

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@561

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@561

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@561

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@561

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@561

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@561

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@561

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@561

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@561

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@561

commit: 02e4765

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.

chat({ stream: false }) still streams on the wire (uses runStreamingText + streamToText)

1 participant