AI
AI building blocks for TypeScript. We build the hard parts, you keep the stack.
TanStack AI gives you composable building blocks for everything you should not write yourself: the agent loop, provider adapters, durability, interrupts, sandboxes, and tools. It leaves you everything a one-size-fits-all framework gets wrong the moment you are past a prototype: your server, your database, your UI.
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'
export const lookupInvoice = toolDefinition({
name: 'lookup_invoice',
description: 'Find an invoice by id',
inputSchema: z.object({ id: z.string() }),
outputSchema: z.object({
total: z.number(),
status: z.enum(['draft', 'sent', 'paid']),
}),
})This file never changes. Everything on the right is a destination for it.
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createFileRoute } from '@tanstack/react-router'
import { lookupInvoice } from './tools'
export const Route = createFileRoute('/api/chat')({
server: {
handlers: {
POST: async ({ request }) => {
const { messages } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
tools: [lookupInvoice.server(findInvoice)],
})
return toServerSentEventsResponse(stream)
},
},
},
})Who owns what
One rule decides every API.
If it is hard to get right and identical in every app, we own it. If it stops fitting the day your app is no longer a prototype, you own it and we hand you typed helpers. Nothing here is a wrapper around a service we run.
TanStack AI handles
Agent loop
Tool calls, stop conditions, and every model round-trip. Easy to start, wrong in a hundred small ways.
Providers
Every major provider behind one call, each model typed down to its options and modalities.
Durability
A dropped socket, a reload, or a restart replays from a log. The model is never re-run.
Interrupts
A run pauses for a human, then resumes at the exact step with their edits applied.
Sandboxes
Coding agents and Code Mode run in an isolate or a container. Their activity is ordinary events.
Tools
One schema, typed on both ends, executed on the server or the client.
Hard to get right, the same in every app, and a bug in any of them costs you a user or a bill.
You own
Server
Any route, any runtime. Your auth check sits next to the call, not behind a config flag.
Persistence
Your database and your schema. Two store functions are the whole contract.
UI
Typed messages, parts, and states. You render them.
Deploy
Your requests, credentials, and data never pass through TanStack.
A framework managing these feels great in a prototype and becomes a wall the day you need one thing it did not anticipate.
You own the server
One call, one Response, any framework.
chat() takes messages and returns a stream. Turn it into a Response and return it from whatever route you already have. Auth, rate limits, and the deploy target stay in your code, where you can see them.
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openRouterText } from '@tanstack/ai-openrouter'
import { createFileRoute } from '@tanstack/react-router'
import { lookupInvoice } from './tools'
export const Route = createFileRoute('/api/chat')({
server: {
handlers: {
POST: async ({ request }) => {
const { messages } = await request.json()
const stream = chat({
adapter: openRouterText('anthropic/claude-sonnet-4.5'),
messages,
tools: [lookupInvoice],
})
return toServerSentEventsResponse(stream)
},
},
},
})Your UI
React, Vue, Solid, Svelte…
Your server
any route, any runtime
Provider or gateway
direct, or one you choose
That is the whole path. TanStack ships the library and does not sit in it, so your requests, credentials, and data never pass through us.
You own persistence
Your database. Your schema. Two functions.
A framework that owns your tables is great until you need soft delete, archiving, or a column it never imagined. So the core never sees your schema. Load a thread, save a thread, and the transcript, run status, and pending approvals land wherever you point them.
- Postgres
- MySQL
- SQLite
- MongoDB
- Cloudflare D1
- Redis
- Drizzle
- Prisma
- localStorage
- IndexedDB
import { defineAIPersistence, defineMessageStore } from '@tanstack/ai-persistence'
import { db } from './db'
// The whole contract. Your tables, your columns, your types.
export const persistence = defineAIPersistence({
stores: {
messages: defineMessageStore({
loadThread: (threadId) => db.threads.messages(threadId),
saveThread: (threadId, messages) => db.threads.save(threadId, messages),
}),
},
})
// chat({ ..., middleware: [withPersistence(persistence)] })Add a runs store to rejoin a run after a reload and an interrupts store to hold an approval for days. Start with memoryPersistence() on the server or localStoragePersistence() in the browser, and swap it out without touching the route.
Durability you can move
A stream survives the reload. The log is yours.
Every chunk is written to a log before it is delivered. Drop the socket, refresh the page, open a second tab, and the client replays from its last offset instead of paying for the model again. Start in memory, move to a hosted log, or write five methods against the store you already run.
import { memoryStream, toServerSentEventsResponse } from '@tanstack/ai'
// Development and single-process apps. Zero setup.
export async function POST(request: Request) {
const stream = chat({ /* ... */ })
return toServerSentEventsResponse(stream, {
durability: { adapter: memoryStream(request) },
})
}- thinkingChecking the invoice before answering.complete
- tool-calllookup_invoice({ id: "inv_2231" })complete
- tool-result{ total: 1240, status: "paid" }complete
- textInvoice 2231 was paid in full onstreaming
tool-call lifecycle
- awaiting-input
- input-streaming
- input-complete
- approval-requested
- approval-responded
- complete
- error
You own the UI
Typed parts, honest states, no components to fight.
A message is a list of parts, and every part carries its own lifecycle. Text streams, a tool call moves through input, approval, and result, and an error is a state rather than an exception you missed. Render them yourself or register one component per part type.
We handle tools
Define a tool once. Run it on either side.
One schema gives you the input and output types on the server and the client. The loop calls the tool, pauses for approval when you ask it to, applies the user's edits, and feeds the result back to the model.
const lookupInvoice = toolDefinition({
name: 'lookup_invoice',
inputSchema: z.object({ id: z.string() }),
outputSchema: invoiceSchema,
needsApproval: true,
})
lookupInvoice.server(async ({ id }) => {
return db.invoices.update({
where: { id },
data: { lastViewedAt: new Date() },
})
})
The server implementation uses the same typed id to update a row in your database. The model never sees your credentials.
import { openaiText } from '@tanstack/ai-openai'
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{
role: 'user',
content: [
{ type: 'text', content: 'What is on this receipt?' },
{ type: 'image', source: { type: 'url', value: receiptUrl } },
],
}],
})
✓ no errors. gpt-5.5 accepts image input.
We handle providers
The types know which model you picked.
Select a model and TypeScript narrows its options, capabilities, and input modalities. Pass an image to a text-only model and it fails in the editor, not in production. Connect directly to the provider or through the gateway you choose.
Open protocol
AG-UI compliant, in both directions.
The client sends AG-UI requests and consumes AG-UI events, with no proprietary stream format and no translation layer in between. That is what makes the agent on the other end replaceable: point the same client at a Python, Go, or PHP AG-UI runtime and it keeps working. The transport is yours too, whether that is SSE, HTTP streams, XHR, RPC, a raw async iterable, or a fetcher you wrote. Nothing to sign up for, no key to hand over, no traffic through us.
AG-UI sits between your web app and your AI endpoint, with traffic in both directions. The server then talks to a provider such as OpenAI or Anthropic.
CLIENT
your web app
AG-UI
communication protocol
Server
your ai endpoint
Provider
openai, anthropic
We handle the hard parts
Sandboxes, Code Mode, MCP, memory, compaction.
Each one is a separate package with the same shape as the core. Reach for it when the task needs it, and leave it out of the bundle when it does not.
Code Mode
@tanstack/ai-code-mode
You provide a special tool to the LLM provider that allows it to chain tools (functions) into a single executable script and call it in a local or remote isolate, producing results that it further processes. It writes code and calls it.
Coding-agent harnesses
@tanstack/ai-sandbox
Run Claude Code, Codex, OpenCode, Grok Build, or any ACP agent as a chat backend, inside a local process, Docker, Daytona, Vercel, Sprites, or Cloudflare sandbox. Their tool activity streams back as AG-UI events your UI already renders.
MCP + MCP Apps
@tanstack/ai-mcp
A host-side MCP client with a type-generating CLI, provider-routed mcpTool(), and interactive ui:// widgets rendered from tool results across multiple servers.
Memory + compaction
@tanstack/ai-memory · @tanstack/ai-compaction
memoryMiddleware recalls across sessions through Redis, mem0, Honcho, or Hindsight adapters. Compaction keeps long threads inside the model window so the agent does not lose the thread as context grows.
Durability + persistence
@tanstack/ai-persistence · @tanstack/ai-durable-stream
Persistence keeps an authoritative server thread, resumes a stream through a dropped connection, and survives a reload. Durability lets a run continue after a process restart.
Beyond chat
Images, video, speech, and realtime voice.
The same adapters and the same persistence cover every modality, with progress updates and cost tracking built in.
Text, objects, reasoning
chat · outputSchema · summarize
Generate an output from an AI that matches your validation schema exactly using structured output.
Speech, transcription, music
generateSpeech · generateTranscription · generateAudio
Six speech formats with speed control, transcription with word timestamps and diarization, plus music and sound effects.
Realtime voice
openaiRealtimeToken · RealtimeClient
OpenAI, Grok, and ElevenLabs with VAD modes and tool calling inside a live session.
Images + video
generateImage · generateVideo
Generate images and videos, edit existing generations and show progress updates to your users with ease.
Devtools
See every action on both sides.
Every tool call, interrupt, memory recall, and finish reason, on the server and in the client, in one timeline.
hooks
Support Chat
useChat · 12 msgs
Image Studio
useGenerateImage
Invoice Extract
useObject
Call Notes
useTranscription
run timeline
thread_7f2 · run_3
Start here
Pick the page that matches your next hour.
Each one is a short guide with copyable code, not a tour.
Partners
Sponsors get special perks like private discord channels, priority issue requests, and direct support!