TanStack
TanStack

AI

beta

The headless agent framework. Bring your own stack.

TanStack AI runs the agent loop as typed TypeScript primitives you compose yourself: tool calls, reasoning, human-in-the-loop interrupts, sandboxed code execution, memory, and streaming state. Eleven provider adapters, seven UI framework bindings on top of a framework-free core, native AG-UI over the wire. No hosted gateway, no proprietary stream format, no platform to buy into.

Docs
A client graph shows eight UI adapters converging on the TanStack AI Client over AG-UI, then reaching an agent runtime in TypeScript, Python, Go, or PHP, and interchangeable model providers.
client graph
chat runtime

Two files

An agent on your own server, end to end.

One route on the server, one hook in the client, and the transport between them is yours. Nothing here is a wrapper around a service we run.

server · routes/api.chat.ts

import { chat, toServerSentEventsResponse } from '@tanstack/ai'

import { openRouterText } from '@tanstack/ai-openrouter'

import { createFileRoute } from '@tanstack/react-router'

 

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],

})

 

// your route, your auth, your deploy target

return toServerSentEventsResponse(stream)

},

},

},

})

client · chat.tsx

import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'

 

export function Chat() {

const { messages, sendMessage, interrupts } = useChat({

connection: fetchServerSentEvents('/api/chat'),

})

 

// typed state and events. no components, no styles.

return (

<>

{messages.map((message) => (

<Bubble key={message.id} {...message} />

))}

 

{/* the loop paused. you decide when it continues. */}

{interrupts.map((interrupt) => (

<button key={interrupt.id}

onClick={() => interrupt.resolveInterrupt(true)}>

Approve {interrupt.toolName}

</button>

))}

</>

)

}

Swap ai-react for ai-vue, ai-solid, ai-svelte, ai-preact, ai-angular, or the framework-free ai-client. The server route never changes.

The agent loop

An agent loop you can read, and stop where you want.

chat() runs the cycle: the model calls a tool, the result goes back, it keeps reasoning. You decide the boundary. Client tools touch local UI state, server tools use your credentials, isomorphic tools share one definition. Stop conditions are plain (state) => boolean functions you compose. Mark a tool needsApproval and the run ends as an interrupt your UI resolves, then resumes exactly where it stopped, on a stateless server, no database required.

tool contract

const lookupInvoice = toolDefinition({

  name: 'lookup_invoice',

  inputSchema: z.object({ id: z.string() }),

  outputSchema: invoiceSchema,

  needsApproval: true,

})

lookupInvoice.server(readPrivateLedger)

Runs behind your server boundary with private credentials and data. The model never sees them.

provider capability types

selected model

any of 300+ models

textreasoningtoolsimagemedia

Types narrow to this exact model: its options, its capabilities, its input modalities. Pass an image to a text-only model and it fails at compile time, not in production.

Provider types

Swap the model. Keep the agent.

OpenRouter, OpenAI, Anthropic, Gemini, Bedrock, Mistral, Groq, Grok, Ollama, ElevenLabs, and fal.ai ship as official adapters, and openaiCompatible covers any endpoint that speaks the same shape, including a model on your own hardware. Switching is a line of config, not a migration. And no adapter pretends every model is identical: write openaiText('gpt-5.5') and TypeScript narrows to that model's real options, capabilities, and input modalities.

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.

UI

headless client

AG-UI

request + events

Agent loop

your server

Provider

typed adapter

The rest of the agent stack

Sandboxes, code mode, MCP, memory. Shipped, not planned.

An agent framework is more than a loop around a model. Each of these is a separate package you opt into, running on infrastructure you already own. Each ships an Agent Skill so your coding assistant wires it up correctly.

Code Mode

@tanstack/ai-code-mode

The model writes one TypeScript program that calls your tools with loops and Promise.all, instead of a round trip per call. It runs in a V8 isolate, QuickJS WASM, or a Cloudflare Worker, with no host filesystem, network, or process.

01

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.

02

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.

03

Memory + persistence

@tanstack/ai-memory · -persistence

memoryMiddleware recalls across sessions through Redis, mem0, Honcho, or Hindsight adapters. Persistence keeps an authoritative server thread, resumes a stream through a dropped connection, and survives a reload.

04

Beyond chat

Not a chatbot library. Every modality, one runtime.

Text and structured output sit beside image, video, speech, transcription, music, and realtime voice. One hook per activity, each a separate tree-shakeable import, none of it wrapped in a chat UI you have to accept. Middleware, devtools, and OpenTelemetry observe every run at the activity level.

Text, objects, reasoning

chat · outputSchema · summarize

Structured output streams as a typed message part beside tool calls and is preserved per turn in history, not a separate one-shot call.

01

Speech, transcription, music

generateSpeech · generateTranscription · generateAudio

Six speech formats with speed control, transcription with word timestamps and diarization, plus music and sound effects.

02

Realtime voice

openaiRealtimeToken · RealtimeClient

OpenAI, Grok, and ElevenLabs with VAD modes and tool calling inside a live session.

03

Images + video

generateImage · generateVideo

Per-model typed options across OpenAI, Gemini, Grok, OpenRouter, and fal.ai, with an async job lifecycle for video.

04

Devtools

Watch the loop run instead of guessing.

Agent bugs live between the turns: which tool ran, what came back, what memory injected, where the run stopped. The TanStack Devtools panel finds every AI hook on the page and gives each one a turn-by-turn timeline with tool inputs and outputs, state snapshots, and errors. You can even replay a tool from a saved fixture instead of prompting your way back to the same state.

tanstack devtools · ai

hooks

Support Chat

useChat · 12 msgs

Image Studio

useGenerateImage

Invoice Extract

useObject

Call Notes

useTranscription

run timeline

thread_7f2 · run_3

user turn"refund the duplicate charge"
memory recall3 facts injected · 214 tokens
tool calllookupInvoice { id: "inv_8841" }
tool result{ total: 4200, status: "paid" }
interruptchargeCard · awaiting approval
finish reasoninterrupt · run resumable

Partners

Gold
CodeRabbit
Lovable
Cloudflare
Railway
Netlify
Silver
SerpApi
AG Grid
Clerk
OpenRouter
WorkOS
Bronze
Electric
Prisma
Unkey
Sentry
OSS Sponsors

Sponsors get special perks like private discord channels, priority issue requests, and direct support!