TanStack AIは、TanStack QueryやTanStack Routerと同じTanStackが開発する、MITライセンスのTypeScript向けAI SDKです。OpenAI・Anthropic・Geminiなどのプロバイダーをアダプタで差し替えながら、ストリーミングチャット、ツール呼び出し、画像や音声の生成を同じ型で書けます。2026年10月23日(日本時間)に1.0.0が公開され、0.x系のリリースキャンディデート段階を抜けました。この記事では、2026年11月10日時点の最新版1.4.0をNode v26.5.0とTypeScript 7.0.2で動かし、最小構成、承認付きのツール呼び出し、0.xからの移行で出る型エラー、Vercel AI SDKとの選び方を確かめた結果をまとめます。
まとめ:TanStack AI 1.0の要点
- 現在地:1.0.0は2026年10月23日公開、最新は1.4.0。公式ブログは中核APIを「stable and ready for production」としていますが、エージェント用のハーネス(@tanstack/ai-harness 0.3.0)は実験的なまま0.x系に残っています。
- 最小構成:React向けはコア・Reactバインディング・プロバイダーの3パッケージ。サーバーは
chat()とtoServerSentEventsResponse()、クライアントはuseChat()です。1.0からthreadIdが必須になりました。 - ツールと承認:
toolDefinition()にスキーマを1回書き、.server()と.client()で実装先を分けます。needsApproval: trueのツールは、承認されるまでサーバー側の関数が実行されません。 - 0.xからの移行:codemodを1回流し、安定版の対象パッケージをまとめて1.xへ上げ、型チェックのエラーを直す手順です。実験的なハーネス系は0.xのまま別途管理します。
threadIdの付与はcodemodでは直りません。 - Vercel AI SDKとの差:既にAI SDKの通信形式(UI Message Stream)で組んだアプリならAI SDK 7のまま、新規でモデル単位の型やAG-UIを使いたいならTanStack AIが候補です。週間ダウンロード数ではAI SDKが約22倍です。
TanStack AIの現在地:1.0安定版とバージョン・利用規模
npmレジストリの公開日時で見ると、@tanstack/ai の初版0.0.1は2025年12月4日、1.0.0は2026年10月22日15時21分(UTC)で、日本時間では10月23日です。公式ブログのTanStack AI 1.0: Still Your AI, Still Your Wayも同じ10月23日付です。1.0以降も1.1.0(11月4日)、1.2.0(11月5日)、1.3.0と1.4.0(いずれも11月9日UTC)と、ほぼ週に2回のペースで更新されています。
| 項目 | 値(2026年11月10日に取得) |
|---|---|
| 最新版 | @tanstack/ai 1.4.0 |
| 1.0.0の公開 | 2026-10-22 UTC(日本時間10-23) |
| 公開バージョン数 | 109 |
| ライセンス | MIT |
| GitHubスター | 3,344 |
| リポジトリのpackages | 80ディレクトリ |
| 週間ダウンロード | 2,075,955(11月2日〜8日) |
| Vercel ai の同週 | 45,288,064 |
2026年11月2日〜8日の週間ダウンロード数では、Vercel AI SDKがTanStack AIの約22倍でした(npmのパッケージページとダウンロード統計APIで取得)。ダウンロード数は依存として間接的に入った分も含むため、本番利用の社数とは別物です。
1.0で安定したAPIと実験的なまま残る部分
公式ブログは1.0を「our formal commitment to the APIs you build on」と位置づけ、中核APIを安定版、新しいハーネスAPIを実験的と明記しています。0.xで非推奨だったAPIは1.0ですべて削除され、置き換え先は0.xの時点で出ていたため、非推奨警告に対応していた人には新しい概念が増えていません。
実験的な範囲は線を引いて扱ってください。長時間続く会話セッションやコーディングエージェントを作る @tanstack/ai-harness は0.3.0、端末で動かす @tanstack/ai-harness-cli も0.x系で、公式はマイナー更新でもAPIが変わりうると書いています。本番に組み込むなら、1.x系の chat()・ツール・各UIバインディングまでにとどめ、ハーネスは版を固定して試す位置づけが無難です。
インストールと最小構成のチャット実装
React向けのインストール対象は、コアの @tanstack/ai、フックの @tanstack/ai-react、プロバイダーの @tanstack/ai-openai の3つです。@tanstack/ai は @tanstack/ai-react の peerDependency なので省けません。ヘッドレスのクライアント @tanstack/ai-client は @tanstack/ai-react の依存として自動で入ります。
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openai
Vueなら @tanstack/ai-vue、Svelteなら @tanstack/ai-svelte というように、2つ目だけを差し替えます。公式の比較ドキュメントに載っているバインディングはReact・Vue・Solid・Svelte・Preact・Angular・Octaneと、フレームワークを使わない場合の @tanstack/ai-client です。
サーバー側:chatParamsFromRequest と chat()
サーバーでは、リクエスト本文を chatParamsFromRequest() で読み取り、chat() にアダプタと一緒に渡して、戻り値のストリームを toServerSentEventsResponse() でSSEの Response に変換します。Web標準の Response を返せるホストなら、TanStack StartでもNext.jsのRoute Handlerでも同じ形で書けます。
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)
}
アダプタは実行時に環境変数 OPENAI_API_KEY を読みます。リクエスト本文はAG-UIの RunAgentInput 形式で、threadId・runId・messages・tools・context・state・forwardedProps がそろっていないと、chatParamsFromRequest() はステータス400の Response を例外として投げます(本文は「Invalid AG-UI request body.」)。curlで手早く試すときに messages だけを送ると、この400で止まります。公式のQuick Startによると、投げられた Response をそのままクライアントへ返してくれるのはTanStack Start・SolidStart・Remix・React Router 7で、Next.jsのRoute Handler・SvelteKit・Hono・素のNodeでは try/catch で受けて返す処理を自分で書きます。
クライアント側:useChat と threadId
Reactでは useChat() に接続先と threadId を渡します。threadId は会話ごとに固定のIDで、ルートのパラメータやDBの会話IDを使います。1.0ではブラウザ側もサーバー側もIDを自動では作りません。
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
export function Chat({ chatId }: { chatId: string }) {
const { messages, sendMessage, isLoading, stop } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: chatId,
})
return (
<div>
{messages.map((m) =>
m.parts.map((p, i) =>
p.type === 'text' ? <p key={m.id + i}>{p.content}</p> : null,
),
)}
<button onClick={() => sendMessage('こんにちは')} disabled={isLoading}>送信</button>
{isLoading ? <button onClick={stop}>停止</button> : null}
</div>
)
}
1件のメッセージは文字列ではなく parts の配列で、テキスト・ツール呼び出し・思考過程がパートの種別で分かれて届きます。描画は part.type で出し分けます。0.xにあった完成済みのチャットUI部品(@tanstack/ai-react-ui など)は1.0では提供されず、npmの説明文も「Deprecated」となった0.9.8で止まっています。見た目は自分で組むか、@tanstack/ai-react の ui サブパスにある createChatHook を使います。
対応プロバイダーと活動別アダプタ
公式ブログによると、1.0時点のプロバイダーアダプタは23種類で、OpenAI互換のAPIなら openaiCompatible で接続できます。npmで最新版を取得した主なアダプタは次のとおりです。
| パッケージ | 最新版 | 主な用途 |
|---|---|---|
| @tanstack/ai-openai | 1.1.1 | チャット・画像・動画・音声 |
| @tanstack/ai-anthropic | 1.1.1 | Claudeのチャット・ツール |
| @tanstack/ai-gemini | 1.2.1 | Geminiのチャット・画像・音声 |
| @tanstack/ai-vertex | 1.0.1 | Vertex AI上のGemini |
| @tanstack/ai-bedrock | 1.2.1 | Amazon Bedrock |
| @tanstack/ai-ollama | 1.1.1 | ローカルLLM |
| @tanstack/ai-openrouter | 1.1.2 | OpenRouter経由の各社モデル |
| @tanstack/ai-vercel-gateway | 1.1.2 | Vercel AI Gateway |
| @tanstack/ai-cloudflare | 1.2.0 | Workers AIとAI Gateway |
| @tanstack/ai-grok / ai-groq | 1.1.1 / 1.0.2 | xAI Grok / Groq |
| @tanstack/ai-mistral / ai-cohere | 1.1.1 / 1.1.0 | Mistral / Cohere |
| @tanstack/ai-elevenlabs / ai-fal | 1.1.1 / 1.1.1 | 音声 / 画像・動画 |
アダプタはプロバイダー単位ではなく、チャット・画像・音声といった「活動」単位の関数に分かれています。@tanstack/ai-openai のexportを列挙すると、チャットの openaiText、画像の openaiImage、音声合成の openaiSpeech、文字起こしの openaiTranscription、動画の openaiVideo、埋め込みの openaiEmbedding などが並びます。使う関数だけがバンドルに入る設計です。コア側の関数名も活動ごとで、文字起こしは transcribe ではなく generateTranscription です(1.4.0のexport 206件を列挙して確認)。
ツール呼び出しと承認フロー:toolDefinition と needsApproval
このツール例はZodを使うため、先に npm i zod を実行してください。ツールは定義と実装を分けて書きます。toolDefinition() に名前・説明・入力と出力のスキーマを書いたファイルをサーバーとブラウザの両方からimportし、サーバーで .server()、ブラウザで .client() を付けます。返金や送信のように実行前に人の確認が要る操作は、needsApproval: true を付けます。
// tools.ts(サーバーとブラウザの両方でimportする)
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'
export const refundOrderDef = toolDefinition({
name: 'refundOrder',
description: '注文を返金する',
inputSchema: z.object({ orderId: z.string() }),
outputSchema: z.object({ refunded: z.boolean() }),
needsApproval: true,
})
承認の返事は2回目のリクエストで届きます。サーバーは parentRunId と resume を chat() に渡し、止まった呼び出しを再開できるようにします。公式のTool Approvalドキュメントと同じ構成です。
// api/chat.ts
import { chat, chatParamsFromRequest, toServerSentEventsResponse, maxIterations } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { refundOrderDef } from './tools.js'
const refundOrder = refundOrderDef.server(async ({ orderId }) => {
// 承認後にだけ実行される
return { refunded: true }
})
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
parentRunId: params.parentRunId,
...(params.resume ? { resume: params.resume } : {}),
tools: [refundOrder],
agentLoopStrategy: maxIterations(5),
})
return toServerSentEventsResponse(stream)
}
ブラウザ側は、useChat() の tools に同じ定義を渡し、interrupts のうち kind が 'tool-approval' のものに承認ボタンを出します。
const { messages, sendMessage, interrupts, resuming } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: chatId,
tools: [refundOrderDef],
})
// JSX内
{interrupts.map((it) =>
it.kind === 'tool-approval' ? (
<button key={it.id} disabled={!it.canResolve || resuming} onClick={() => it.resolveInterrupt(true)}>
注文 {it.originalArgs.orderId} の返金を承認
</button>
) : null,
)}
tools を渡し忘れると型で気づけます。tsc 7.0.2で試すと、useChat() に tools を渡さない場合の interrupts の kind は 'generic' | 'unbound' だけになり、'tool-approval' との比較がTS2367(比較が常に偽)になりました。上の3つのコード片を組み合わせたファイルは、tsc 7.0.2でエラー0件です。
APIキーなしで動作を確かめるテスト:fakeText
@tanstack/ai/testing の fakeText() は、あらかじめ用意した応答を順番に返すアダプタです。ネットワークもAPIキーも使わずに、chat()・ツール・ミドルウェアの動きを確かめられます。次のコードは、サーバー例で定義した refundOrder と同じスコープに置いて実行します。
import { chat } from '@tanstack/ai'
import { fakeText } from '@tanstack/ai/testing'
const fake = fakeText()
fake.setResponses([
{ toolCalls: [{ name: 'refundOrder', input: { orderId: 'A-100' } }] },
{ text: '返金しました' },
])
const result = await chat({
adapter: fake,
threadId: 'test-1',
stream: false,
messages: [{ role: 'user', content: 'A-100を返金して' }],
tools: [refundOrder],
})
console.log(result.chunks.map((c) => c.type))
実行すると、イベントは RUN_STARTED → TOOL_CALL_START → TOOL_CALL_ARGS → TOOL_CALL_END → MESSAGES_SNAPSHOT → RUN_FINISHED の順に出ました。最後の RUN_FINISHED の outcome は type: "interrupt" で、「Approval required to run refundOrder」という承認待ちが入っています。サーバー側の返金関数は0回、偽モデルの呼び出しは1回で止まっており、承認前に処理が走らないことを単体テストで固定できます。
承認の要らないツールで同じことをすると、ツール実行の結果を受けて2回目のモデル呼び出しが行われ、TOOL_CALL_RESULT と TEXT_MESSAGE_CONTENT まで進みます。SSEに変換した本文に data: [DONE] の行はなく、ランの終わりは RUN_FINISHED イベントで表されます。
0.xから1.0への移行手順
公式の移行ガイド「Upgrade to 1.0」は5段階です。codemodで名前の変更を自動で直し、安定版の @tanstack/ai* パッケージをまとめて1.xへ上げ(実験的なハーネス系は除外)、型チェックのエラーを直し、保存済みデータを移行してから、最後に確認します。1.0のクライアントは1.0のサーバーを必要とするため、フロントだけ先に上げる段階的な更新はできません。
npx jscodeshift \
--parser=tsx \
-t https://raw.githubusercontent.com/TanStack/ai/main/codemods/ag-ui-compliance/transform.ts \
"src/**/*.{ts,tsx}"
型チェックで出るエラーと書き換え先
旧APIを1.4.0にそのまま書いてtsc 7.0.2で型チェックした結果です。body と conversationId の改名はcodemodが直しますが、threadId の追加はcodemodにはできません(IDの出どころをアプリしか知らないため)。
| 0.xの書き方 | 1.0の書き方 | tscのエラー |
|---|---|---|
| useChat({ connection }) | threadId を追加 | TS2345 |
| chat({ adapter, messages }) | threadId を追加 | TS2345 |
| useChat({ body }) | forwardedProps | TS2353 |
| chat({ conversationId }) | threadId | TS2353 |
| addToolApprovalResponse() | interrupt の resolveInterrupt | TS2339 |
型チェックを通さずに実行した場合も、chat() は threadId が無いと「chat() needs a threadId. Pass a non-empty string, for example the id of the conversation.」という例外で止まります。JavaScriptのまま使っているプロジェクトでも、実行時に気づける作りです。
自前サーバーと保存データの注意
型チェックが通っても終わりではありません。chat() と toServerSentEventsResponse() を使わずに自前でストリームを返しているサーバーは、data: [DONE] の送信をやめて RUN_FINISHED で終える必要があります。移行ガイドによると、クライアントは [DONE] の行を解析できずエラーとして扱います。思考過程は REASONING_* イベントで送り、承認やクライアントツールはinterruptで表します。
会話履歴をブラウザに保存している場合は、getItem が { messages } の形で返す必要があり、0.xで保存した配列のままでは読み込まれません。移行ガイドは、配列なら { messages: saved } に包み直す変換を getItem に入れる方法を示しています。サーバー側の永続化やサンドボックスの実行記録も同様に、デプロイ前に処理中のランを終わらせるか消す手順が必要です。
Vercel AI SDK 7との違いと選び方
比較対象として最も名前が挙がるのはVercel AI SDKです。npmの ai パッケージは2026年11月9日公開の7.0.166が最新で、engines は node >=22、ライセンスはApache 2.0です。両者の主な違いを、TanStack側の比較ドキュメント(AI SDKは7.x、TanStack AIは1.0で比較)とnpmの値で並べます。
| 観点 | TanStack AI 1.4.0 | Vercel AI SDK 7.0.166 |
|---|---|---|
| ライセンス | MIT | Apache 2.0 |
| Node要件(engines) | 本体>=18(openai依存は>=22) | >=22 |
| 通信形式 | AG-UI(双方向) | 独自のUI Message Stream |
| 型の粒度 | モデル単位 | プロバイダー単位 |
| 一次プロバイダー | 23種+OpenAI互換 | 約38パッケージ |
| ツール定義 | toolDefinition+.server()/.client() | tool()+onToolCall |
| 他言語 | AG-UI準拠サーバーへ接続 | AI SDK for Python(ベータ) |
| 週間ダウンロード | 約208万 | 約4,529万 |
比較を読むときは、どちらの公式も自分に有利な結論を書いている点に注意してください。TanStackの比較ドキュメントは冒頭で「Pick TanStack AI for a new app」と勧め、Vercelのナレッジベース記事は、承認・タイムアウト付きの本番エージェントや再起動をまたぐ実行ではAI SDKを選ぶよう整理しています。しかもVercel側の記事は最終更新が2026年9月17日で、TanStack AIを「v0 RC」のまま扱っています。1.0以降の状況は反映されていません。
判断の分かれ目は既存資産です。UIが @ai-sdk/react やAI Elementsで組まれている、あるいは自分で管理していないバックエンドがUI Message Streamを返してくるなら、TanStack AIはその形式を読み書きしないため、乗り換えは書き直しになります。AI SDK for PythonがUI Message Streamを返す構成でも、AI SDKを継続すれば既存の通信処理を維持できます。Vercel AI Gateway自体はTanStack AIの専用アダプタからも利用できます。
新規に作るなら、TanStack AIを選ぶ理由ははっきりしています。モデルIDごとに渡せるオプションが型で絞られること、ツールの定義をサーバーとブラウザで共有できること、通信がオープンなAG-UIなので相手側のエージェントを差し替えられることです。Node要件の差は選ぶ理由になりません。@tanstack/ai 本体の engines は18以上ですが、Node 18〜21はすでにサポートが終了しており、OpenAIアダプタが依存する openai パッケージ(7.x)はNode 22以上を要求します。どちらを選んでもNode 22以上のLTSを前提にしてください。JavaScriptでLangChainのエージェント機能も含めて比べたい場合は、LangChain.jsも候補に入ります。社内の業務システムに生成AIのチャットや承認フローを組み込む設計から相談したい場合は、生成AI開発・AI受託開発でSDKの選定から支援しています。
TanStack AIの導入制約:モジュール形式・型検査・他言語接続
ESM専用パッケージとCommonJSからの読み込み
@tanstack/ai の package.json は "type": "module" で main を持たず、exports にも require の条件がありません。CommonJSから読み込むと次のエラーで止まります(1.4.0で実測)。
$ node -e "require('@tanstack/ai')"
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in .../@tanstack/ai/package.json
CommonJSのままのExpressサーバーに後から足すなら、ESMへ移行するか、動的 import() で読み込む形に書き換えるのが先です。
モデルIDの型検査と入力モダリティ検査の限界
モデルIDの型検査は機能します。openaiText('gpt-6.1-typo') のように存在しないモデル名を書くと、tsc 7.0.2はTS2345を返し、候補として55件のモデルIDのユニオン型を示しました。
一方、テキストしか受け付けないモデルへの画像入力は、正しい形の画像パートであれば型検査を通ります。アダプタのモデル定義で入力が ["text"] だけの gpt-3.5-turbo に { type: 'image', source: { type: 'url', value: ... } } を渡しても、tscのエラーは0件でした。入力を ['text'] に絞った fakeText でも型検査を通り、実行時も画像パートがそのままアダプタに渡りました。source を欠いた不完全な画像パートなら「’image’ is not assignable to type ‘text’」になるため、検査は形の誤りまでは拾っても、モデルが画像を読めるかまでは保証しません。画像を扱う画面では、送信前にモデルの対応入力を自分で確かめる処理を入れてください。
TypeScript以外の言語との関係
SDK本体はTypeScriptで書かれています。GitHubの言語統計(2026年11月10日取得)ではTypeScriptが約3,052万バイトで大半を占め、PythonやPHPは集計に現れません。PHPやPythonのアプリにそのまま組み込めるライブラリではありません。
接続先のサーバーは別の話です。公式サイトは「point the same client at a Python, Go, or PHP runtime and it keeps working」と書いており、AG-UIに準拠したサーバーなら実装言語を問わず、同じ useChat() から接続できます。@tanstack/ai 1.4.0は @ag-ui/core 1.0.0に依存しています。既にPythonでエージェントを動かしているなら、フロントだけTanStack AIにする構成も取れます。
よくある質問
TanStack AIは本番環境で使えますか?
中核APIは使えます。2026年10月23日の1.0で、公式は中核APIを本番利用可能な安定版と位置づけました。ただし @tanstack/ai-harness などハーネス系は0.x系の実験的な扱いで、マイナー更新でもAPIが変わりえます。1.0以降も週に2回前後のペースで更新されているので、バージョンを固定し、安定版パッケージの更新ではサーバーとクライアントの互換性を確認してください。実験的なハーネス系は別途バージョンを管理します。
TanStack Start以外のフレームワークでも使えますか?
使えます。サーバー側はWeb標準の Response を返せる環境ならよく、Next.jsのRoute HandlerやHonoでも同じ chat() のコードを使えます。この2つでは、不正なリクエストで投げられる400の Response を try/catch で受けて返す処理を足します。TanStack Startは同じTanStack製で、ドキュメントの例に多く使われています。UI側はReact・Vue・Solid・Svelte・Preact・Angular向けのパッケージがあります。
「TanStack Chat」というパッケージはありますか?
ありません。npmで @tanstack/chat と @tanstack/ai-chat を引くとどちらも404です(2026年11月10日に確認)。TanStackのチャット機能はTanStack AIの一部で、サーバー側は @tanstack/ai の chat()、画面側は @tanstack/ai-react などの useChat() が担います。
OpenAI以外のLLMやローカルLLMに切り替えられますか?
切り替えられます。chat() に渡す adapter を、@tanstack/ai-anthropic や @tanstack/ai-ollama の関数に替えるのが基本です。一覧にないプロバイダーは、OpenRouter、Vercel AI Gateway、openaiCompatible のいずれかを経由します。モデル固有のオプションは型で絞られているため、切り替え後に型エラーが出たら、その箇所が新しいモデルで使えない設定です。
Vercel AI SDKから移行できますか?
公式に「Migration from Vercel AI SDK」のガイドがあり、streamText() は chat()、tool() は toolDefinition() と .server()、stopWhen: stepCountIs(n) は agentLoopStrategy: maxIterations(n) に対応します。ただし通信形式がUI Message StreamからAG-UIに変わるため、サーバーとクライアントを同時に書き直す作業になります。