feat: add smoothStream transform for word-by-word text delivery
@jherr is already working on this.
Since Sep 17, 2026.
- Dominant language
- TypeScript
- Stars
- 3.1k
- Forks
- 331
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 160
Description
Problem
Some LLM providers send large text deltas per SSE event instead of token-by-token streaming. Anthropic's thinking-enabled models (e.g. Claude Opus 4.6) batch ~250 characters per content_block_delta event — this is documented server-side behavior, not a client bug. The result is a chunky, jarring streaming UX where large blocks of text appear at once instead of the smooth word-by-word flow users expect.
This affects any consumer of TanStack AI that uses providers with batched deltas.
Precedent
The Vercel AI SDK solved this with smoothStream — a composable TransformStream that buffers incoming text deltas and re-emits them word-by-word with a configurable delay (exposed via experimental_transform option on streamText). It's a ~40-line transform that:
- Buffers
text-deltachunks and extracts words via regex (/\S+\s+/m) - Emits each word as a separate
text-deltachunk with a 10ms delay - Passes non-text chunks through immediately (flushing any buffered text first)
- Supports multiple chunking modes:
"word"(default),"line",RegExp,Intl.Segmenter
Proposal
Add a smoothStream utility and a transform option to TanStack AI's stream processing pipeline, operating on StreamChunk before chunks reach consumers. This would allow any app using useChat or the lower-level stream APIs to opt into smooth text delivery without implementing their own transform.
Suggested API
import { smoothStream } from '@tanstack/ai'
const stream = chat({
adapter,
messages,
transform: smoothStream({ chunking: 'word', delayInMs: 10 }),
})
The transform option accepts a function that wraps the internal StreamChunk iterable, keeping it generic enough for other transforms in the future (e.g. token counting, logging, rate limiting).
smoothStream() with no arguments would use sensible defaults (chunking: 'word', delayInMs: 10).
Scope
- Default chunking mode:
"word"via/\S+\s+/mregex - Default delay: 10ms between word emissions
- Non-text chunks (
TOOL_CALL_START,RUN_FINISHED, etc.) flush the buffer and pass through immediately - Provider-agnostic — benefits any provider that batches text deltas
- Optional
Intl.Segmentersupport for CJK languages (where whitespace-based splitting doesn't work)
Reference
I implemented a local version in OpenWaggle operating on our domain-typed AgentStreamChunk, modeled directly after Vercel's implementation. Happy to contribute a PR adapting it to StreamChunk if this direction is accepted.
Environment
@tanstack/aiversion: latest- Affected providers: Anthropic (Opus 4.6 with thinking), potentially others with batched SSE
- Not affected: OpenAI, smaller Claude models (Sonnet, Haiku) which send token-sized deltas
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.