TanStack AI 1.0: Stable Core, 76 Packages, and an Experimental Agent Harness
TanStack AI 1.0 landed on npm on October 22, 2026, and the announcement post followed a day later. When we covered the beta in June, the pitch was the "Switzerland of AI tooling": any provider, any UI framework, the open AG-UI protocol on the wire, and no hosted platform attached. That pitch has not changed. What changed is the commitment behind it. The core APIs are now stable, 76 packages moved to 1.0 together, every API that 0.x had deprecated is gone, and a new agent harness ships beside the core as an explicitly experimental 0.x package. This post covers what "stable" includes, what landed between the August release candidate and 1.0, what an upgrade from 0.x costs, how the harness works, and what all of it means for Aliz projects.
Try itโ
npm install @tanstack/ai@1.0.0 @tanstack/ai-react@1.0.0 @tanstack/ai-openai@1.0.0
Swap the last package for your provider (@tanstack/ai-anthropic, @tanstack/ai-gemini, @tanstack/ai-openrouter, and 19 more) and the middle one for your UI framework. @tanstack/ai-client is the headless option when there is no framework. Pin exact versions: a 1.0 client needs a 1.0 server, and our npm security checklist applies as usual.
What "stable" coversโ
1.0 is a formal commitment to the APIs you build on. In practice it splits into three groups.
Stable at 1.0.0. We checked the package manifests in the repository at the 1.0 tag: 76 packages carry version 1.0.0. That is the core (@tanstack/ai), the headless client, the framework bindings, all 23 provider adapters, devtools, MCP, persistence, memory, compaction, resumable streams, the sandbox and isolate providers, computer use, Code Mode, skills, and ACP.
Experimental at 0.x. @tanstack/ai-harness and @tanstack/ai-harness-cli ship as 0.1.0, and so does the brand-new Neon AI Gateway adapter. The harness entry points inside stable packages, such as @tanstack/ai-skills/harness and @tanstack/ai-acp/agent, are experimental too. They can change in a minor release.
Gone. @tanstack/ai-react-ui, @tanstack/ai-solid-ui, and @tanstack/ai-vue-ui have no 1.0 release. Their ready-made Chat, ChatMessages, and ChatInput components are replaced by the createChatHook factory: you supply the components, and the factory types them from your chat options.
The team's own framing is that "1.0 has no new concepts". Everything deprecated in 0.x was removed, and every replacement already shipped during 0.x. If you kept up with the deprecation warnings, you are most of the way there.
Ten months from alpha to 1.0โ
- December 2025: the alpha shipped
chat(), four providers, isomorphic tools, and devtools. Alpha 2 followed two weeks later with every modality. - Winter: the team killed the
ai()function after eight days of work and split the adapters into small, tree-shakable packages. - Spring 2026: middleware, realtime voice, Code Mode, streaming structured output, and full AG-UI compliance.
- June 2026: host-side MCP, the beta we covered, AI Project of the Year at the JavaScript Open Source Awards, and coding agents in sandboxes.
- August 2026: the release candidate locked the architecture.
- October 2026: 1.0.
| Alpha (December 2025) | 1.0 (October 2026) | |
|---|---|---|
| Provider adapters | 4 | 23, plus openaiCompatible for any OpenAI-style API |
| UI frameworks | React, Solid, vanilla JS | React, Vue, Solid, Svelte, Preact, Angular, Octane, Remix, vanilla JS |
| Coding agents | None | Claude Code, Codex, OpenCode, Grok Build, and any ACP agent |
| Sandbox providers | None | 11 |
The day-to-day team is three maintainers, with 143 people having contributed code. The end-to-end suite runs on every pull request against recorded, deterministic model fixtures rather than live provider calls.
The shape of a 1.0 appโ
A chat has two halves. The server calls chat() with an adapter and the messages, and returns the stream as a Response. The client calls a hook such as useChat(), which posts to that route and renders typed message parts as they arrive. Tools run inside the same loop: when the model calls one, TanStack AI executes it, feeds the result back, and keeps streaming.
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat({ chatId }: { chatId: string }) {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading, stop } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: chatId,
});
return (
<>
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.content}</p> : null,
)}
</div>
))}
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
{isLoading ? (
<button type="button" onClick={stop}>
Stop
</button>
) : (
<button type="submit">Send</button>
)}
</form>
</>
);
}
Three details in that snippet matter more than they look:
threadIdis required on both sides, and neither side invents one. Use a stable id per conversation, such as a route param or a database id. A fresh random id on every render starts a new conversation.- Message parts are typed. Text, thinking, tool calls, tool results, subagent cards, and structured output arrive as discriminated parts, each with its own state. The UI is yours to render.
- The wire format is AG-UI.
toServerSentEventsResponseemits standard AG-UI events, which is why a Python or Go agent that speaks AG-UI can sit behind the sameuseChat. SSE is the default transport.fetchHttpStream,fetchJson, a resumablewebSockettransport, an XHR transport for React Native, and TanStack Start server functions are drop-in alternatives behind theconnectionoption.
What landed since the RCโ
The release candidate locked the architecture in August. The two months after it were not quiet.
Agents
- Subagents in
chat()anduseChat: hand turns to a named child agent with typed input and limits. The client shows the child as a card inside the assistant message. decide()for typedchoice,score, andbooleandecisions, with no stream to parse.- Portable Agent Skills: any tool-calling model can load
SKILL.mdskills when it needs them. - Compaction keeps long chats under the context limit, with native provider compaction where the provider has it.
- Computer use: the model drives a web page from screenshots, and the same code runs on OpenAI and Anthropic.
decide() is the one to try first if you run classification or triage prompts today. The option keys become the union type of the answer:
import { decide, choice, score, boolean } from "@tanstack/ai";
import { typesafeDecider } from "@tanstack/ai-typesafe";
const result = await decide({
adapter: typesafeDecider("jev-latest"),
state: ticket,
questions: {
queue: choice({
instructions: "Which team should handle this ticket?",
options: {
billing: "Payments, invoices, refunds",
tech: "Bugs, outages, integrations",
},
}),
urgency: score({
instructions: "How urgent is this ticket?",
levels: ["low", "medium", "high"],
}),
refund: boolean({ instructions: "Is the customer asking for a refund?" }),
},
});
// result.queue.value is typed as "billing" | "tech"
if (result.queue.value === "billing" && result.refund.value) {
routeToBilling(ticket, result.urgency.value);
}
Models
chat({ reasoning }): one reasoning option that works the same on every provider and is typed per model.- Automatic prompt caching, and tools or system prompts that change mid-conversation without losing the cache.
chat({ retry })for rate-limited model calls.- Conversation history that replays across models, so one thread can move to another model.
- Each provider's Files API behind one interface.
Keys and sign-in
- Bring your own key with
defineByokand keyed adapters, so a user can paste their own provider key in the browser. - Sign in with ChatGPT, GitHub Copilot, or OpenRouter, or use a Claude OAuth token, so a local app does not need a pasted API key.
MCP
createMCPServerfrom@tanstack/ai-mcp/serverserves your TanStack tools to Claude Desktop, Cursor, and any other MCP host.- Tool filters, approvals, and task-based tools for MCP clients.
- WebMCP in both directions: browser agents call your client tools, and your chat uses the tools a page exposes.
Providers, frameworks, and sandboxes
- New adapters: Cloudflare Workers AI and AI Gateway, Lovable AI Gateway, Azure OpenAI, World Labs Marble, TypeSafe, and Ollaya.
- Remix 3 support, and devtools for Svelte.
- Five new sandbox providers (Blaxel, Upstash Box, E2B, boxd, and Railway) bring the total to eleven, from a local process and Docker for development up to microVM providers for production.
Building and testing
- Typed, headless chat UI factories (
createChatHook), with custom adapters for any framework. fakeText()from@tanstack/ai/testingtestschat(), tools, and middleware with no network and no API key.- Agent Skills for your coding assistant, so Claude Code or Cursor writes TanStack AI code that matches the version you installed.
Upgrading from 0.xโ
For most apps the upgrade is one codemod and a type check.
npx jscodeshift \
--parser=tsx \
-t https://raw.githubusercontent.com/TanStack/ai/main/codemods/ag-ui-compliance/transform.ts \
"src/**/*.{ts,tsx}"
Add --dry --print to preview the changes. The codemod handles the mechanical renames:
| Before | After |
|---|---|
useChat({ body }), new ChatClient({ body }) | forwardedProps |
chat({ conversationId }) | chat({ threadId }) |
Svelte chat.updateBody(x) | chat.updateForwardedProps(x) |
The parts you do by hand:
threadIdis required everywhere. The codemod renames the key but cannot invent your ids. On the client, pass a stable id to every chat and generation hook. On the server, readparams.threadIdfromchatParamsFromRequest()and pass it tochat(),summarize(), and the generation functions.addToolApprovalResponse()is gone. Resolve thetool-approvalinterrupt instead:
- const { addToolApprovalResponse } = useChat({ connection, body: { userId } })
- addToolApprovalResponse({ id, approved: true })
+ const { interrupts } = useChat({
+ connection,
+ threadId: chatId,
+ forwardedProps: { userId },
+ })
+ const approval = interrupts.find((item) => item.kind === 'tool-approval')
+ if (approval?.kind === 'tool-approval') approval.resolveInterrupt(true)
- Client tools must match the server schema,
outputSchemaincluded. Otherwise the client does not run the tool and the pause shows as a generic interrupt. Define the tool once withtoolDefinition()and import it on both sides. - UI packages: replace
ai-react-ui,ai-solid-ui, andai-vue-uiwithcreateChatHookfrom@tanstack/ai-react/uior the Solid, Vue, and Remix equivalents. - Custom servers: if your own backend streams to a TanStack AI client, stop sending
data: [DONE]and end the run with aRUN_FINISHEDevent. - Saved data: passing the type check does not migrate stored conversations, runs, or files. Follow the saved-data steps in the upgrade guide before you deploy.
Upgrade every stable @tanstack/ai* package at the same time. A 1.0 client needs a 1.0 server, and an interrupt coming from a 0.x server shows up on a 1.0 client as kind: 'unbound', which cannot be resumed. The harness packages are the exception: they stay on 0.x.
The agent harness, experimental on purposeโ
chat() answers one request, which is right for a chat box. A coding agent, a support bot, or a media studio needs one conversation that stays open, accepts new messages while it works, runs other agents, and survives a deploy. That is what @tanstack/ai-harness is for. You define a harness once, with the same options as chat(), and open a session per conversation:
import { createHarnessHost, defineHarness } from "@tanstack/ai-harness";
import { openaiText } from "@tanstack/ai-openai";
import { memoryPersistence } from "@tanstack/ai-persistence";
const assistant = defineHarness({
name: "acme/assistant",
adapter: openaiText("gpt-6.1-sol"),
systemPrompts: ["You are a helpful travel agent."],
});
const host = createHarnessHost({ persistence: memoryPersistence() });
const session = await host.open(assistant, { threadId: "trip-1" });
const plan = session.send("Plan a three-day trip to Lisbon.");
plan.onEvent("TEXT_MESSAGE_CONTENT", ({ event }) => {
process.stdout.write(event.delta);
});
// The user types while the agent works. A steer joins the running turn.
await session.send("Keep it under 500 euros.", { mode: "steer" });
await plan;
A steer joins the running turn before its next model call, or starts the next turn if the current one has finished. A plain follow-up waits until the current work is done. The events are standard AG-UI events. A session gives you:
- Durable sessions. Every thread has a session log. With a store that survives restarts, a turn cut by a crash or a deploy continues on the next host. One host owns a thread at a time.
- Waits that survive restarts. An approval, a question, or a sign-in stops the run and waits in the log. Any server can take the answer later.
- Typed agents. Run agents from code, in parallel or in series, with budgets and timeouts, or let the model call them as tools.
- Plugins. Package tools, prompts, middleware, slash commands, and state, and load them into any harness. The first-party plugins cover permissions, workspace file tools, snapshots with
/undo, todos,/compact,/usage, and project instructions, which is enough to assemble a coding agent for your terminal. - Many users and background work. Access rules, shared threads, a provider key per user, and workers for schedules and tools that wait for hours.
One definition runs in several places. runCli gives you a terminal chat, -p runs one prompt for scripts and CI, --serve exposes the session protocol over HTTP, --acp turns it into an ACP agent for editors such as Zed, and --mcp makes it an MCP server for Claude Code and Cursor. In a web app, createHarnessHandler serves a session over HTTP and handleHarnessSocket over a WebSocket. The run route speaks AG-UI, so the same useChat works against a harness, including approvals and a page reload that rejoins the running turn.
The harness is 0.1.0 and explicitly experimental: a minor release can change its API, so pin the exact version. The documented recovery limits also matter for anything beyond a prototype. A harnessAgent child waits only in memory and does not resume on another host after a crash, and log snapshots grow with the thread unless you drop the old records.
Rewritten docs and skills for your coding agentโ
Every hand-written docs page was rewritten for 1.0. Each page opens with the problem and a copy-and-run setup, pushes the deep details to an Advanced section or a sibling page, and every adapter page follows the same order so providers can be compared side by side. The Quick Start gets to a streaming chat in a handful of steps.
The docs also ship as Agent Skills. Install them once, and Claude Code or Cursor writes TanStack AI code for the version you have installed:
npx skills add TanStack/ai -g --skill tanstack-ai tanstack-ai-migration
Inside Claude Code or Cursor, /plugin marketplace add TanStack/ai followed by /plugin install tanstack-ai does the same. Inside a project, npx @tanstack/intent@latest install wires the skills of the installed packages into AGENTS.md or CLAUDE.md for the whole team. Our Agent Skills page covers how that fits the rest of our setup.
Where it stands against Vercel AI SDKโ
TanStack's own comparison page is blunt: pick TanStack AI for a new app, and stay on the Vercel AI SDK if your app already speaks its UI Message Stream protocol. That is a vendor's framing, but the table behind it is specific. The Vercel AI SDK is on 7.x (7.0 shipped in June 2026) with roughly 38 first-party provider packages and its own stream protocol. TanStack AI has 23 adapters, native AG-UI in both directions, per-model types instead of per-provider types, one toolDefinition() with .server() and .client() halves, and persistence and resumable streams as built-in middleware rather than patterns you implement. Vercel AI Gateway is no longer an argument either way: TanStack AI ships a native adapter for it.
| TanStack AI 1.0 | Vercel AI SDK 7.x | |
|---|---|---|
| Wire protocol | Native AG-UI, in both directions | Own UI Message Stream; AG-UI through an external bridge |
| Type safety | Per model | Per provider |
| Tools | One toolDefinition() with .server() and .client() | tool() on the server, client tools through onToolCall |
| Persistence and resume | Built in: withPersistence middleware, durability adapters, useChat reconnects by itself | Patterns you write, plus the resumable-stream package |
| Framework packages | React, Solid, Vue, Svelte, Preact, Angular, Octane, Remix, React Native | React, Vue, Svelte, Angular; Solid is community-maintained |
| Hosting | Anywhere, no platform layer | Anywhere, optional Vercel platform parts |
| License | MIT | Apache 2.0 |
Our AI Assistant Chat cookbook still lists Vercel AI SDK plus AI Elements as the default and TanStack AI as the alternative. The "it is still beta" argument against TanStack AI is gone. The "the team already knows it" argument for the Vercel SDK is not.
What it means for Aliz projectsโ
- New chat features on a React SPA: the stable core is now a serious candidate next to our Vercel AI SDK default. Pick TanStack AI when you want AG-UI on the wire, persistence and reload-safe streaming without hand-rolled routes, or a backend that is not JavaScript.
- Existing Vercel AI SDK apps: stay. TanStack AI does not read or write the UI Message Stream, so moving means rewriting the chat layer. The upstream migration guide exists for when that is worth it.
- Teams already on 0.x: upgrade now, all packages together. Budget for the
threadIdplumbing and the interrupt rewrite, then run the saved-data migration before you deploy. - Agent products: prototype on the harness, but treat it as 0.x. Pin exact versions, read the known limits, and do not bet a production deadline on an API that can change in a minor release.
- Structured output and evaluation:
chat({ outputSchema })anddecide()are stable and fit the patterns from our Zod structured output post.
TanStack AI stays in our Triage section until an Aliz project ships on it. 1.0 removes the biggest reason it was there.
Further reading:
- TanStack AI 1.0: Still Your AI, Still Your Way โ the official announcement
- Upgrade to 1.0 โ the full upgrade guide, including the saved-data steps
- Build your first harness โ the harness documentation
- TanStack AI vs Vercel AI SDK โ the upstream comparison and API mapping
- TanStack AI triage page โ our refreshed evaluation summary
- TanStack AI Beta: The 'Switzerland of AI Tooling' Goes Multi-Modal โ our June post on the beta
