Skip to content

feat(ai-ui-message-stream): client tools, structured output and reconnect for AI SDK routes - #1891

Merged
tombeckenham merged 2 commits into
ag-ui-adapterfrom
ag-ui-adapter-client
Oct 23, 2026
Merged

tombeckenham merged 2 commits into
ag-ui-adapterfrom
ag-ui-adapter-client

Conversation

@tombeckenham

@tombeckenham tombeckenham commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

TanStack useChat now gets more from an AI SDK route than the AI SDK's own client: client tools that the route reads from the request, typed structured output, and reconnect backoff. Stacked on #1888.

🎯 Changes

  • Client-declared tools. fromChatTransport sends the client's tool declarations in the request body as clientTools. On the AI SDK route, clientToolsFromAISDKBody(body, serverTools) turns them into AI SDK tools without execute, so streamText stops at the call and the client runs it.
    • Server tools win a name clash.
    • A malformed clientTools throws.
    • Browser WebMCP tools reach the route the same way.
  • Typed structured output. With outputSchema on fromChatTransport (the same schema as useChat({ outputSchema })), an Output.object answer becomes a structured-output part, with partial objects while it streams and a checked final value. A schema failure ends the run with RUN_ERROR.
  • Reconnect backoff. A dropped stream now reconnects with createReconnectTracker, the backoff and limits of the TanStack HTTP connections, set with the new reconnect option. @tanstack/ai-client exports createReconnectTracker and ReconnectTracker for this, and becomes a required peer of the bridge.
  • Proofs over the bridge. Tests show that client persistence, the message queue, devtools events and loading saved AI SDK chats (initialMessages: fromAISDKMessages(saved)) all work.
  • Docs. The client guide lists what the TanStack client gives against an AI SDK route, and adds sections for client tools on the route, structured output, saved chats and reconnect. The compatibility table, README and migration skill are updated.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Testing

Commands run

  • In packages/ai-ui-message-stream: vitest run (48 passed), test:types, test:oxlint, knip.
  • In packages/ai-client: test:types, test:oxlint. The one lint warning is in existing code.
  • kiira check on the changed docs and skill, and pnpm test:docs.
  • Not run locally: pnpm test:pr and the E2E suite. CI runs them.

Manual test

  1. On an AI SDK route, use streamText({ tools: clientToolsFromAISDKBody(body, {}) }).
  2. On the client, use useChat({ tools: [myClientTool], connection: fromChatTransport(new DefaultChatTransport({ api })) }).
  3. Ask for something that needs the tool. The client runs it, and the route continues with its output.

How this PR makes testing easy

  • tests/client-tools.test.ts runs a real ChatClient, fromChatTransport and AI SDK streamText with a mock model.
  • tests/client-extras.test.ts covers structured output, the queue, devtools and saved chats.
  • tests/reconnect.test.ts covers backoff and the no-progress limit.

Risk / rollback

Low. Every option is new and off by default, except two changes:

  • Reconnect: it now retries with backoff up to the limit, instead of once.
  • Peer: @tanstack/ai-client is now a required peer of @tanstack/ai-ui-message-stream.

Revert the PR to undo both.

Public API change

Before: every client tool also had to be defined on the AI SDK route.

After

// AI SDK route
const body = await request.json()
const result = streamText({
  model,
  messages: await convertToModelMessages(body.messages),
  tools: clientToolsFromAISDKBody(body, { getWeather }),
})

// TanStack client
useChat({
  threadId,
  tools: [getLocation],
  outputSchema: recipe,
  connection: fromChatTransport(new DefaultChatTransport({ api: '/api/chat' }), {
    outputSchema: recipe,
    reconnect: { maxAttempts: 10 },
  }),
})
// @tanstack/ai-client
import { createReconnectTracker } from '@tanstack/ai-client'

…nect for AI SDK routes

TanStack useChat gets more from an AI SDK route:

- fromChatTransport sends the client's tool declarations in the request
  body. clientToolsFromAISDKBody(body, serverTools) turns them into AI SDK
  tools without execute on the route, so client and WebMCP tools need no
  copy there. Server tools win a name clash.
- With outputSchema, an Output.object answer becomes a typed
  structured-output part with partial objects and a checked final value.
- A dropped stream reconnects with createReconnectTracker, the backoff of
  the TanStack HTTP connections, set with the reconnect option.
  @tanstack/ai-client now exports createReconnectTracker.

Tests prove client persistence, the message queue, devtools and loading
saved AI SDK chats over the bridge.
@tombeckenham
tombeckenham added this pull request to stack #1892 October 23, 2026 05:24
@changeset-bot

changeset-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a576436

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@tanstack/ai-ui-message-stream Minor
@tanstack/ai-client Patch
ag-ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit a576436

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 25s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-23 05:28:11 UTC

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

✅ Coverage held across 10 compared package(s).

Each package is measured twice in this job — on this PR and on its merge-base with main — and compared. A metric fails only when it is at or above 60% on the merge-base and under 60% on this PR. Other drops are listed but do not fail. Packages your PR doesn't affect are not measured and not listed.

package statements branches functions lines
ai-angular 73.83% (0.00) 60.89% (0.00) 61.83% (0.00) 75.40% (0.00) ok
ai-client 87.81% (0.00) 79.97% (0.00) 87.01% (0.00) 90.13% (0.00) ok
ai-octane 91.19% (0.00) 68.26% (0.00) 87.44% (0.00) 91.47% (0.00) ok
ai-preact 86.50% (0.00) 71.00% (0.00) 87.50% (0.00) 87.98% (0.00) ok
ai-react 81.94% (0.00) 65.42% (0.00) 80.73% (0.00) 82.67% (0.00) ok
ai-remix 43.02% (0.00) 30.35% (0.00) 39.02% (0.00) 46.23% (0.00) ok
ai-solid 75.81% (0.00) 57.63% (0.00) 71.28% (0.00) 78.11% (0.00) ok
ai-svelte 74.05% (0.00) 50.99% (0.00) 70.05% (0.00) 78.64% (0.00) ok
ai-ui-message-stream 87.78% (+0.89) 80.29% (+1.08) 94.11% (+2.05) 89.55% (+0.92) ok
ai-vue 73.33% (0.00) 48.73% (0.00) 73.28% (0.00) 74.66% (0.00) ok

@pkg-pr-new

pkg-pr-new Bot commented Oct 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@1891

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@1891

@tanstack/ai-anthropic

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

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@1891

@tanstack/ai-byteplus

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-byteplus@1891

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@1891

@tanstack/ai-client

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

@tanstack/ai-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-cloudflare@1891

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-snippets@1891

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@1891

@tanstack/ai-cohere

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-cohere@1891

@tanstack/ai-compaction

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-compaction@1891

@tanstack/ai-computer-local

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-computer-local@1891

@tanstack/ai-computer-playwright

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-computer-playwright@1891

@tanstack/ai-computer-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-computer-sandbox@1891

@tanstack/ai-computer-use

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-computer-use@1891

@tanstack/ai-devtools-core

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

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@1891

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@1891

@tanstack/ai-groq

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

@tanstack/ai-harness

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-harness@1891

@tanstack/ai-harness-cli

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-harness-cli@1891

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-daytona@1891

@tanstack/ai-isolate-e2b

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-e2b@1891

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs-bun@1891

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-llmgateway@1891

@tanstack/ai-lovable

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-lovable@1891

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@1891

@tanstack/ai-memory

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-memory@1891

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@1891

@tanstack/ai-neon

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-neon@1891

@tanstack/ai-octane

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-octane@1891

@tanstack/ai-ollama

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

@tanstack/ai-ollaya

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollaya@1891

@tanstack/ai-openai

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

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@1891

@tanstack/ai-openrouter

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

@tanstack/ai-perplexity

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-perplexity@1891

@tanstack/ai-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence@1891

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-reactor

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-reactor@1891

@tanstack/ai-remix

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-remix@1891

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@1891

@tanstack/ai-sandbox-blaxel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-blaxel@1891

@tanstack/ai-sandbox-boxd

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-boxd@1891

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@1891

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@1891

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@1891

@tanstack/ai-sandbox-e2b

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-e2b@1891

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@1891

@tanstack/ai-sandbox-railway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-railway@1891

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@1891

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-upstash-box@1891

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@1891

@tanstack/ai-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-skills@1891

@tanstack/ai-solid

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

@tanstack/ai-svelte

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

@tanstack/ai-typesafe

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-typesafe@1891

@tanstack/ai-ui-message-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ui-message-stream@1891

@tanstack/ai-utils

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

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vercel-gateway@1891

@tanstack/ai-vertex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vertex@1891

@tanstack/ai-vue

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

@tanstack/ai-worldlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-worldlabs@1891

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@1891

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

@tanstack/svelte-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/svelte-ai-devtools@1891

commit: a576436

@tombeckenham
tombeckenham removed this pull request from stack #1892 October 23, 2026 07:51
@tombeckenham
tombeckenham merged commit a576436 into ag-ui-adapter Oct 23, 2026
15 checks passed
@tombeckenham
tombeckenham deleted the ag-ui-adapter-client branch October 23, 2026 07:51
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.

1 participant