惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
IT之家
IT之家
B
Blog RSS Feed
罗磊的独立博客
GbyAI
GbyAI
博客园 - Franky
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
博客园 - 聂微东
N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
人人都是产品经理
人人都是产品经理
U
Unit 42
博客园 - 叶小钗
Jina AI
Jina AI
MyScale Blog
MyScale Blog
雷峰网
雷峰网
B
Blog
Hugging Face - Blog
Hugging Face - Blog
Blog — PlanetScale
Blog — PlanetScale
Recent Announcements
Recent Announcements
腾讯CDC
酷 壳 – CoolShell
酷 壳 – CoolShell

Chat SDK Documentation

History | Chat SDK History | Chat SDK List a vendor-official adapter | Chat SDK Approvals | Chat SDK Vercel Connect | Chat SDK Teams Low-Level APIs | Chat SDK CLI | Chat SDK Platform Adapters | Chat SDK State Adapters | Chat SDK Cards | Chat SDK Getting Started | Chat SDK Introduction | Chat SDK Modals | Chat SDK Slack Low-Level APIs | Chat SDK Streaming | Chat SDK Testing | Chat SDK Overview | Chat SDK toAiMessages | Chat SDK Cards | Chat SDK Overview | Chat SDK Markdown | Chat SDK Modals | Chat SDK AI SDK Tools | Chat SDK Types | Chat SDK Message Subject | Chat SDK Conversation History | Chat SDK Transcripts | Chat SDK Slack bot with Next.js and Redis Actions | Chat SDK Direct Messages | Chat SDK
TanStack AI | Chat SDK
Vercel · 2026-09-17 · via Chat SDK Documentation

Feed thread history into TanStack AI's chat() and give it Chat SDK tools, with no runtime dependency on @tanstack/ai.

The chat/ai/tanstack subpath is the TanStack AI counterpart to the AI SDK helpers in chat/ai. It converts Chat SDK Message[] into the ModelMessage[] shape that chat() from @tanstack/ai expects, and it exposes the same Chat SDK tools, presets, approval flags, and scope guard as createChatTools, shaped for chat({ tools }). The subpath declares its own message and tool types, so it has no runtime dependency on @tanstack/ai.

import { createTanStackTools, toTanStackMessages } from "chat/ai/tanstack";

Add @tanstack/ai and a model adapter. The examples below use @tanstack/ai-vercel-gateway, which routes requests through Vercel AI Gateway so you can switch models by changing the model id. See TanStack AI with AI Gateway for the full setup.

Create an AI Gateway API key and expose it as AI_GATEWAY_API_KEY. The adapter reads it automatically, and falls back to VERCEL_OIDC_TOKEN when deployed on Vercel.

createTanStackTools needs zod 4.2 or newer. TanStack AI converts tool input schemas to JSON Schema through the Standard JSON Schema interface, which zod added in 4.2; on an older version createTanStackTools throws with a message that says so. toTanStackMessages has no zod requirement.

Converting history

Fetch recent messages the same way you would for toAiMessages, convert them, and hand the array to chat(). The stream chat() returns can be posted straight back to the thread, as described in Streaming.

import { chat } from "@tanstack/ai";
import { vercelGatewayText } from "@tanstack/ai-vercel-gateway";
import { toTanStackMessages } from "chat/ai/tanstack";

bot.onNewMention(async (thread) => {
  const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });
  const messages = await toTanStackMessages(result.messages);

  const stream = chat({
    adapter: vercelGatewayText("anthropic/claude-opus-5"),
    systemPrompts: ["You are a helpful assistant in a team chat workspace."],
    messages,
  });
  await thread.post(stream);
});

What gets converted

  • Roles: messages authored by the bot (author.isMe === true) become assistant, everything else becomes user. Messages are sorted oldest first by metadata.dateSent.
  • Text: message.text becomes string content. With includeNames: true, user messages are prefixed with [username]: .
  • Images: image attachments with a working fetchData() become { type: "image", source: { type: "data", value, mimeType } } parts, where value is base64 with no data: prefix. A user message with images gets array content, with a leading { type: "text", content } part when there is text.
  • Text files: TanStack AI has no file content part, so text-like attachments (text/*, JSON, XML, YAML, TOML, JavaScript, TypeScript) are inlined into the message text as [File: <name> (<mime>)] followed by the file contents.
  • Links: link metadata is appended to the text inside the same untrusted-content fence that toAiMessages uses, with third-party titles and descriptions normalized and length-limited.
  • Video and audio: skipped, with onUnsupportedAttachment called for each. Other file types (PDF, for example) are skipped silently.
  • Empty messages: a message with no text and nothing the converter can include is dropped.
  • System prompt: ModelMessage has no system role. Pass system text through chat({ systemPrompts: [...] }) instead.

Options

function toTanStackMessages(
  messages: Message[],
  options?: ToTanStackMessagesOptions
): Promise<TanStackMessage[]>
OptionTypeDescription
includeNamesbooleanPrefix user messages with [username]: so the model can tell speakers apart. Defaults to false.
transformMessage(message: TanStackMessage, source: Message) => TanStackMessage | null | Promise<TanStackMessage | null>Runs after default processing for each message. Return the message (modified or as-is) to keep it, or null to drop it.
onUnsupportedAttachment(attachment: Attachment, message: Message) => voidCalled for video and audio attachments. Defaults to console.warn.

createTanStackTools returns an array of plain tool objects (name, description, inputSchema, needsApproval, execute) that chat({ tools }) accepts as-is. TanStack runs the tool loop itself, so server tools execute during the same chat() call and their results feed back into the model before the stream finishes.

import { chat } from "@tanstack/ai";
import { vercelGatewayText } from "@tanstack/ai-vercel-gateway";
import { createTanStackTools, toTanStackMessages } from "chat/ai/tanstack";

bot.onNewMention(async (thread) => {
  const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });

  const stream = chat({
    adapter: vercelGatewayText("anthropic/claude-opus-5"),
    systemPrompts: ["You operate inside a chat workspace via Chat SDK tools."],
    messages: await toTanStackMessages(result.messages),
    tools: createTanStackTools({
      chat: bot,
      preset: "messenger",
      requireApproval: false,
    }),
  });
  await thread.post(stream);
});

Options

type TanStackChatToolsOptions = {
  chat: Chat;
  preset?: ChatToolPreset | ChatToolPreset[];
  requireApproval?: boolean | Partial<Record<ChatApprovalToolName, boolean>>;
  scope?: ReadScope | false;
  strictScope?: boolean;
  overrides?: Partial<Record<ChatToolName, TanStackToolOverrides>>;
};
OptionDescription
chatThe Chat instance the tools dispatch operations against. Required.
presetPreset (or array of presets) restricting which tools are returned. Omit to get every tool.
requireApprovaltrue (default), false, or per-tool overrides. Applies to every write tool and getUser. See Approval before leaving this on.
scopeConversation the tools are confined to. Defaults to the conversation being handled. Pass a Thread, Channel, raw id, or false for workspace-wide access.
strictScopefalse (default). Set true to tighten a thread scope to that thread alone.
overridesPer-tool customization keyed by tool name. Only description, needsApproval (boolean), metadata, and lazy can be set; name, inputSchema, outputSchema, and execute are ignored so tool semantics stay stable.

Presets

The reader, messenger, and moderator presets select the same tools as they do for createChatTools, and they compose the same way (preset: ["reader", "messenger"]). The AI SDK Tools page lists the tools in each preset and describes every available tool.

Approval

Write tools and getUser default to needsApproval: true, matching createChatTools. In TanStack AI that flag means something specific: when the model calls an approval-gated tool, chat() stops the run with a tool-approval interrupt instead of executing it. Resuming is your job. You call chat() again with the message history plus a resume array carrying the decision and the parentRunId of the paused run, as described in TanStack's tool approval docs.

A bot that posts the stream straight to a thread has no UI to collect that decision, so an approval-gated tool call ends the run without the tool running. Unless you implement the resume flow, pass requireApproval: false and gate risky tools yourself, for example with a smaller preset, a tighter scope, or an approval step before chat() is called.

Scope

scope and strictScope behave exactly as they do for createChatTools: tools built inside a handler are confined to that conversation, calls that resolve outside it are rejected before the platform is called, and getUser and sendDirectMessage are exempt because they target user ids rather than conversations. Read Limiting what the agent can reach for the full model, including what scope does not check.

chat/aichat/ai/tanstack
Tools return typeObject keyed by tool name, spread into toolsArray of tool objects, passed as tools
Tool overridesThe ToolOverrides keys: description, title, needsApproval, metadata, providerOptions, strict, inputExamples, toModelOutput, and the onInput* hooksdescription, needsApproval, metadata, lazy only
Text file attachmentsfile parts with base64 dataInlined into the message text as [File: name (mime)] blocks
Image attachmentsimage parts with image and mediaTypeimage parts with source: { type: "data", value, mimeType }
System promptA system message or the call's system optionchat({ systemPrompts }) only; there is no system role
zod requirementAny version the AI SDK accepts4.2 or newer for createTanStackTools
Approval flowThe AI SDK surfaces an approval request your app confirmschat() pauses with an interrupt you resume via resume and parentRunId

Everything below is exported from chat/ai/tanstack. The tool option types (ChatToolName, ChatToolPreset, ChatWriteToolName, ChatApprovalToolName, ApprovalConfig, ReadScope, ChatBinding) are the same types documented on the Types page, re-exported for convenience.

type TanStackMessage = TanStackUserMessage | TanStackAssistantMessage;

interface TanStackUserMessage {
  role: "user";
  content: string | TanStackContentPart[];
}

interface TanStackAssistantMessage {
  role: "assistant";
  content: string;
}

type TanStackContentPart = TanStackTextPart | TanStackImagePart;

interface TanStackTextPart {
  type: "text";
  content: string;
}

interface TanStackImagePart {
  type: "image";
  source: { type: "data"; value: string; mimeType: string };
}

interface TanStackTool<TInput = unknown, TOutput = unknown> {
  name: string;
  description: string;
  inputSchema: ZodType<TInput>;
  execute(args: TInput, context?: unknown): Promise<TOutput>;
  needsApproval?: boolean;
  metadata?: Record<string, unknown>;
  lazy?: boolean;
}

type TanStackToolOverrides = Partial<
  Pick<TanStackTool, "description" | "lazy" | "metadata" | "needsApproval">
>;

TanStackMessage[] is structurally assignable to TanStack AI's ModelMessage[], and TanStackTool[] to the tools option of chat(), without importing anything from @tanstack/ai.