Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
Migration from Vercel AI SDK
Move a Vercel AI SDK app (ai and @ai-sdk/*) to TanStack AI by swapping the package names, the server call, the client hook, and the tool helpers.
- streamText becomes chat().
- useChat from @ai-sdk/react becomes useChat from @tanstack/ai-react.
- tool() becomes toolDefinition().
Do the steps below in order. The "Before" samples are AI SDK v5 and v6 code. Where v4 is different, the page says so. For every other API, see Vercel AI SDK: full API mapping.
Port a chat app#
1. Swap the packages#
| Vercel AI SDK | TanStack AI |
|---|---|
| ai | @tanstack/ai |
| @ai-sdk/openai | @tanstack/ai-openai |
| @ai-sdk/anthropic | @tanstack/ai-anthropic |
| @ai-sdk/google | @tanstack/ai-gemini |
| @ai-sdk/google-vertex | @tanstack/ai-vertex (Gemini), @tanstack/ai-anthropic/vertex (Claude), @tanstack/ai-grok/vertex (Grok), or @tanstack/ai-mistral/vertex (Mistral) |
| @ai-sdk/react | @tanstack/ai-react |
| @ai-sdk/vue | @tanstack/ai-vue |
| @ai-sdk/solid | @tanstack/ai-solid |
| @ai-sdk/svelte | @tanstack/ai-svelte |
Note
In AI SDK v4, the framework hooks came from subpaths such as ai/react. Since v5, they come from packages such as @ai-sdk/react.
Remove the ai and @ai-sdk/* packages from your package.json. Then install TanStack AI with the adapters that you need:
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicpnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicyarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropicbun add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @tanstack/ai-anthropic2. Port the server route#
Before (Vercel AI SDK):
import { streamText, convertToModelMessages } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = streamText({
model: openai('gpt-4o'),
messages: await convertToModelMessages(messages),
})
return result.toUIMessageStreamResponse()
// (v4: result.toDataStreamResponse())
}After (TanStack AI):
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId,
})
return toServerSentEventsResponse(stream)
}What changed:
- model becomes adapter. The model id goes into an adapter function for one activity: openai('gpt-4o') becomes openaiText('gpt-4o').
- No convertToModelMessages(). chat() takes the UI messages from the client as they are. The adapter converts them.
- The response is a separate helper. result.toUIMessageStreamResponse() becomes toServerSentEventsResponse(stream).
If you set system, pass systemPrompts: ['You are a helpful assistant.']. If you set temperature or maxOutputTokens, move them into modelOptions. See Model options.
3. Port the client#
Before (Vercel AI SDK v5+):
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'
export function Chat() {
const [input, setInput] = useState('')
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (input.trim() && status !== 'streaming') {
sendMessage({ text: input })
setInput('')
}
}
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.role}:{' '}
{m.parts.map((p, i) => (p.type === 'text' ? <span key={i}>{p.text}</span> : null))}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Send</button>
</form>
</div>
)
}After (TanStack AI):
import { useState } from 'react'
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
export function Chat() {
const [input, setInput] = useState('')
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-chat',
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (input.trim() && !isLoading) {
sendMessage(input)
setInput('')
}
}
return (
<div>
{messages.map((message) => (
<div key={message.id}>
{message.role}:{' '}
{message.parts.map((part, idx) =>
part.type === 'text' ? <span key={idx}>{part.content}</span> : null
)}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Send</button>
</form>
</div>
)
}The hook is headless, the same as AI SDK v5 and later. You keep your own input state. The rest maps like this:
| Vercel AI SDK (v5+) | TanStack AI |
|---|---|
| transport: new DefaultChatTransport({ api: '/api/chat' }) | connection: fetchServerSentEvents('/api/chat') |
| sendMessage({ text }) | sendMessage(text). It takes a string or multimodal content. To add a full UIMessage, use append(). |
| status | status, with the same four values ('submitted', 'streaming', 'ready', 'error'). For a boolean, read isLoading. |
| regenerate() | reload() |
| stop() | stop() |
| setMessages(messages) | setMessages(messages) |
| addToolOutput({ tool, toolCallId, output }) (v6; addToolResult in v5) | addToolResult({ tool, toolCallId, output }) |
| addToolApprovalResponse({ id, approved }) (v6) | interrupt.resolveInterrupt(approved) on a tool-approval item from interrupts |
| p.text on a text part | part.content on a text part |
4. Port your tools#
You define a tool once with toolDefinition(). Then you add a .server() or a .client() implementation.
Before (Vercel AI SDK v5+):
import { streamText, tool } from 'ai'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
import { fetchWeather } from './weather'
const messages = [{ role: 'user' as const, content: 'Hello' }]
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: tool({
description: 'Get weather for a location',
inputSchema: z.object({ // renamed from `parameters` in v5
location: z.string(),
}),
execute: async ({ location }) => {
const weather = await fetchWeather(location)
return weather
},
}),
},
})After (TanStack AI):
import { chat, toolDefinition, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { z } from 'zod'
import { fetchWeather } from './weather'
// 1. Define the tool
const getWeatherDef = toolDefinition({
name: 'getWeather',
description: 'Get weather for a location',
inputSchema: z.object({
location: z.string(),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
})
// 2. Add the server implementation
const getWeather = getWeatherDef.server(async ({ location }) => {
const weather = await fetchWeather(location)
return weather
})
// 3. Use it in chat
export async function POST(request: Request) {
const { messages, threadId } = await request.json()
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId,
tools: [getWeather],
})
return toServerSentEventsResponse(stream)
}| Vercel AI SDK | TanStack AI |
|---|---|
| parameters (v4) / inputSchema (v5+) | inputSchema |
| No equivalent | outputSchema (optional, types the result from end to end) |
| execute on the server | .server() or .client() on the definition |
| An object with tool names as keys | An array of tools |
Do you have client tools (onToolCall with addToolOutput) or approvals (needsApproval)? See Client tools and Tool approval.
5. Run it#
Start your app, and send a message. The reply streams in, and the model calls your tools.
Find the rest of your code#
| Vercel AI SDK | TanStack AI | Details |
|---|---|---|
| generateText() | chat({ stream: false }), which resolves to { text, chunks } | Non-streaming generation |
| generateObject(), streamObject(), Output.object() | chat({ outputSchema }) | Structured output |
| stopWhen, prepareStep | agentLoopStrategy, middleware | Agent loop control |
| wrapLanguageModel(), experimental_transform | middleware | Middleware |
| providerOptions: { openai: {...} } | modelOptions: {...} | Model options |
| embed(), embedMany() | embed() | Embeddings |
| Each streamText option and result member | streamText options |
Done#
Your server route streams with chat(), your client renders message.parts, and your tools run. No file imports ai or @ai-sdk/*.