文档
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
类引用
函数引用
接口引用
类型别名引用
变量引用
API

@tanstack/ai-client

与框架无关的无头客户端,用于管理聊天状态和流式传输。

安装

sh
npm install @tanstack/ai-client

ChatClient

用于管理聊天状态的主要客户端类。

typescript
import { ChatClient, fetchServerSentEvents } from "@tanstack/ai-client";

const client = new ChatClient({
  connection: fetchServerSentEvents("/api/chat"),
  initialMessages: [],
  onMessagesChange: (messages) => {
    console.log("Messages updated:", messages);
  },
  onToolCall: async ({ toolName, input }) => {
    // Handle client tool execution
    return { result: "..." };
  },
});

构造函数选项

  • connection - 流式传输的连接适配器
  • initialMessages? - 初始消息数组
  • id? - 此聊天实例的唯一标识符
  • body? - 要发送的附加 body 参数
  • onResponse? - 接收到响应时的回调
  • onChunk? - 接收到流片段时的回调
  • onFinish? - 响应完成时的回调
  • onError? - 发生错误时的回调
  • onMessagesChange? - 消息更改时的回调
  • onLoadingChange? - 加载状态更改时的回调
  • onErrorChange? - 错误状态更改时的回调
  • onToolCall? - 客户端工具执行的回调
  • streamProcessor? - 流处理配置

方法

sendMessage(content: string)

发送用户消息并获取响应。

typescript
await client.sendMessage("Hello!");

append(message: ModelMessage | UIMessage)

将消息附加到对话中。

typescript
await client.append({
  role: "user",
  content: "Additional context",
});

reload()

重新加载上一条助手消息。

typescript
await client.reload();

stop()

停止当前的响应生成。

typescript
client.stop();

clear()

清除所有消息。

typescript
client.clear();

setMessagesManually(messages: UIMessage[])

手动设置消息数组。

typescript
client.setMessagesManually([...newMessages]);

addToolResult(result)

添加客户端工具执行的结果。

typescript
await client.addToolResult({
  toolCallId: "call_123",
  tool: "toolName",
  output: { result: "..." },
  state: "output-available",
});

addToolApprovalResponse(response)

响应工具审批请求。

typescript
await client.addToolApprovalResponse({
  id: "approval_123",
  approved: true,
});

属性

  • messages: UIMessage[] - 当前消息
  • isLoading: boolean - 是否正在生成响应
  • error: Error | undefined - 当前错误(如果有)

连接适配器

fetchServerSentEvents(url, options?)

创建 SSE 连接适配器。

typescript
import { fetchServerSentEvents } from "@tanstack/ai-client";

const adapter = fetchServerSentEvents("/api/chat", {
  headers: {
    Authorization: "Bearer token",
  },
});

fetchHttpStream(url, options?)

创建 HTTP 流连接适配器。

typescript
import { fetchHttpStream } from "@tanstack/ai-client";

const adapter = fetchHttpStream("/api/chat");

stream(connectFn)

创建自定义连接适配器。

typescript
import { stream } from "@tanstack/ai-client";

const adapter = stream(async (messages, data, signal) => {
  // Custom implementation
  const response = await fetch("/api/chat", {
    method: "POST",
    body: JSON.stringify({ messages, ...data }),
    signal,
  });
  return processStream(response);
});

辅助函数

clientTools(...tools)

创建带有适当类型推断的客户端工具的类型化数组。这消除了在定义工具数组时使用 as const 的需要,并能够进行适当的区分联合类型缩小。

typescript
import { clientTools } from "@tanstack/ai-client";
import { myTool1, myTool2 } from "./tools";

// Create client implementations
const tool1Client = myTool1.client((input) => {
  // Implementation
  return { result: "..." };
});

const tool2Client = myTool2.client((input) => {
  // Implementation
  return { result: "..." };
});

// Create typed tools array (no 'as const' needed!)
const tools = clientTools(tool1Client, tool2Client);

// Now when you use these tools in chat options:
const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools, // Fully typed with literal tool names
});

// In your component:
messages.forEach((message) => {
  message.parts.forEach((part) => {
    if (part.type === "tool-call" && part.name === "myTool1") {
      // ✅ TypeScript knows part.name is literally "myTool1"
      // ✅ part.input is typed from myTool1's input schema
      // ✅ part.output is typed from myTool1's output schema
    }
  });
});

createChatClientOptions(options)

辅助函数,用于使用适当的类型推断创建类型化的聊天客户端选项。

typescript
import { createChatClientOptions, clientTools } from "@tanstack/ai-client";

const tools = clientTools(tool1, tool2);

const chatOptions = createChatClientOptions({
  connection: fetchServerSentEvents("/api/chat"),
  tools,
});

// Use InferChatMessages to extract message types
type ChatMessages = InferChatMessages<typeof chatOptions>;

类型

UIMessage

typescript
interface UIMessage {
  id: string;
  role: "user" | "assistant";
  parts: MessagePart[];
  createdAt?: Date;
}

MessagePart

typescript
type MessagePart = TextPart | ThinkingPart | ToolCallPart | ToolResultPart;

TextPart

typescript
interface TextPart {
  type: "text";
  content: string;
}

ThinkingPart

typescript
interface ThinkingPart {
  type: "thinking";
  content: string;
}

思考部分代表模型的内部推理过程。它们通常以可折叠格式显示,并在出现响应文本时自动折叠。思考部分仅供 UI 使用,不会在后续请求中发送回模型。

注意:思考部分仅在使用支持推理/思考的模型时可用(例如,启用思考的 Anthropic Claude,启用推理的 OpenAI GPT-5)。

ToolCallPart

typescript
interface ToolCallPart {
  type: "tool-call";
  id: string;
  name: string;
  arguments: string; // JSON string (may be incomplete during streaming)
  input?: any; // Parsed tool input (typed from tool's inputSchema)
  state: ToolCallState;
  approval?: ApprovalRequest;
  output?: any; // Tool execution output (typed from tool's outputSchema)
}

在使用带有 clientTools()createChatClientOptions() 的类型化工具时,inputoutput 字段将根据您的工具的 Zod 模式自动类型化,并且 name 成为一个区分联合,从而实现类型缩小。

ToolResultPart

typescript
interface ToolResultPart {
  type: "tool-result";
  id: string;
  toolCallId: string;
  tool: string;
  output: any;
  state: ToolResultState;
  errorText?: string;
}

ToolCallState

typescript
type ToolCallState =
  | "pending"
  | "approval-requested"
  | "executing"
  | "output-available"
  | "output-error"
  | "cancelled";

ToolResultState

typescript
type ToolResultState =
  | "pending"
  | "executing"
  | "output-available"
  | "output-error";

流处理

使用块策略配置流处理

typescript
import { ImmediateStrategy, fetchServerSentEvents } from "@tanstack/ai-client";

const client = new ChatClient({
  connection: fetchServerSentEvents("/api/chat"),
  streamProcessor: {
    chunkStrategy: new ImmediateStrategy(), // Emit every chunk
  },
});

下一步