@chat-adapter/web (英语)
npm 软件包 :
@chat-adapter/web
网络适配器 聊天SDK。让一个聊天-sdk bot与Slack, Teams, Discord 等同时为浏览器聊天UI服务——相同 bot.onDirectMessage(...) 每个平台的操作员起火.
适配器说 AI SDK UI 消息流协议 默认情况下,因此React,Vue,和Svelte AI SDK客户端对服务器端点工作. 设定 protocol: "ag-ui" 说话 乌干达 相反,因为 谭斯塔克AI 和其他AG-UI客户端。 服务器对两个客户端SDK都没有运行时间依赖.
联合国 聊天-sdk.dev/适应者/官方/网络 指南: vercel.com/kb/chat-sdk (英语)
安装
pnpm add @chat-adapter/web
然后安装匹配您的用户界面的客户端软件包 :
| 客户端 | 软件包 | 从 | protocol |
|---|---|---|---|
| AI SDK + React / Next.js (英语) | ai, (中文(简体) ), @ai-sdk/react | @chat-adapter/web/react | "ai-sdk" (默认) |
| AI SDK + Vue / 努克特 | ai, (中文(简体) ), @ai-sdk/vue | @chat-adapter/web/vue | "ai-sdk" (默认) |
| AI SDK + Svelte / Svelte Kit 软件 | ai, (中文(简体) ), @ai-sdk/svelte | @chat-adapter/web/svelte | "ai-sdk" (默认) |
| 谭斯塔克AI | @tanstack/ai-react (或另一个 TanStack AI 框架包) | @tanstack/ai-react | "ag-ui" |
带CLI的脚手架
要使用Web适配器预选的新浏览器聊天程序 :
npx create-chat-sdk@latest my-bot --adapter web memory
访问 适配器目录 见其他官方和供应商官方适配器.
快开始
服务器
// lib/bot.ts
import { Chat } from "chat";
import { createWebAdapter } from "@chat-adapter/web";
import { createMemoryState } from "@chat-adapter/state-memory";
export const bot = new Chat({
userName: "mybot",
adapters: {
web: createWebAdapter({
userName: "mybot",
getUser: (req) => ({ id: getUserIdFromCookie(req) }),
}),
},
state: createMemoryState(),
});
bot.onDirectMessage(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});
// app/api/chat/route.ts
import { after } from "next/server";
import { bot } from "@/lib/bot";
export async function POST(request: Request): Promise<Response> {
return bot.webhooks.web(request, {
waitUntil: (task) => after(() => task),
});
}
客户端
反应
// app/chat/page.tsx
"use client";
import { useChat } from "@chat-adapter/web/react";
export default function ChatPage() {
const { messages, sendMessage, status, stop } = useChat();
// Render with `ai-elements` (<Conversation>, <Message>, <PromptInput>)
// or your own components — `messages`, `sendMessage`, `status` are the
// standard `@ai-sdk/react` API.
}
阴道
<!-- components/Chat.vue -->
<script setup lang="ts">
import { useChat } from "@chat-adapter/web/vue";
const chat = useChat({ api: "/api/chat" });
</script>
<template>
<div v-for="msg in chat.messages" :key="msg.id">
<template
v-for="(part, index) in msg.parts"
:key="`${msg.id}-${part.type}-${index}`"
>
<p v-if="part.type === 'text'">{{ part.text }}</p>
</template>
</div>
</template>
丝绒
<!-- Chat.svelte -->
<script lang="ts">
import { useChat } from "@chat-adapter/web/svelte";
const chat = useChat({ api: "/api/chat" });
</script>
{#each chat.messages as msg (msg.id)}
{#each msg.parts as part, index (`${msg.id}-${part.type}-${index}`)}
{#if part.type === "text"}<p>{part.text}</p>{/if}
{/each}
{/each}
谭斯塔克AI
设定 protocol: "ag-ui" 在适配器上,然后点 TanStack AI的 useChat 在路线上 fetchServerSentEvents数字 :
createWebAdapter({
userName: "mybot",
getUser: (req) => ({ id: getUserIdFromCookie(req) }),
protocol: "ag-ui",
});
// app/chat/page.tsx
"use client";
import { fetchServerSentEvents, useChat } from "@tanstack/ai-react";
export default function ChatPage() {
const { messages, sendMessage, isLoading, stop } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-1",
});
// Text parts carry their text in `part.content`.
}
threadId 成为聊天- sdk 线条 id 中的对话 id 。 每个 thread.post 调用作为自己的助理消息到达,处理器错误以一个 RUN_ERROR 活动.
认证
getUser 这是 安全边界 用于网络适配器。 与Slack/Teams不同的是,平台在每一个webhook上签名,web请求直接来自浏览器——您必须亲自识别呼叫者. 正在返回 null 导致适配器以 HTTP 401 响应,没有处理器运行.
插入您的应用程序已经使用的内容 :
// NextAuth
createWebAdapter({
userName: "mybot",
getUser: async (req) => {
const session = await getServerSession(authOptions);
if (!session?.user) return null;
return { id: session.user.id, name: session.user.name };
},
});
// Clerk
createWebAdapter({
userName: "mybot",
getUser: async (req) => {
const { userId, sessionClaims } = await auth();
if (!userId) return null;
return { id: userId, name: sessionClaims?.name as string | undefined };
},
});
// Custom session cookie
createWebAdapter({
userName: "mybot",
getUser: async (req) => {
const sessionId = req.headers.get("cookie")?.match(/session=([^;]+)/)?.[1];
if (!sessionId) return null;
const user = await db.users.findBySession(sessionId);
return user ? { id: user.id, name: user.name } : null;
},
});
若为 getUser 扔出,适配器返回401并记录出错。 在错误消息中不要包括敏感数据——它没有被浮出客户端,而是被登录.
决心
user.id嵌入到聊天- sdk 线索 id (参见 线索 页:1 包含的用户代号:与 HTTP 400 一同被拒绝,因为他们会破坏往返decodeThreadId。如果您的认证提供者以冒号(例如.provider:sub要求)getUser——以基数64-编码为例.
线索
默认情况下,每个 useChat 一个聊天- sdk 线索的对话地图 :
web:{user.id}:{conversationId}
conversationId 这是 id 字段 AI SDK 的使用 Chat 在其请求正文中发送,或 threadId 字段为 protocol: "ag-ui"。如果客户提供(useChat({ id: "support-chat" })),它通过再装入来再使用;否则每个请求都会生成新鲜的ID. 一个不是字符串的对话ID,或者包含 :,用HTTP 400拒绝.
channel.messages 财务报告和审定财务报表 thread.messages 在网络上是等同的——频道 id 是线索 id. 这样可以避免交会出血 persistMessageHistory 被启用,而同一用户有多个使用Chat 对话框打开.
覆盖( 例如, 每个用户一个线程, 不分对话) :
createWebAdapter({
userName: "mybot",
getUser: (req) => /* ... */,
threadIdFor: ({ user }) => `web:${user.id}:default`,
});
编码/解码辅助器在适配器上被曝光:
adapter.encodeThreadId({ userId: "u1", conversationId: "abc" });
// → "web:u1:abc"
adapter.decodeThreadId("web:u1:abc");
// → { userId: "u1", conversationId: "abc" }
流线
thread.post 接受一个 AsyncIterable<string | StreamChunk> 并直接将 Dertas 泵入SSE 响应机体——没有编辑回路,没有限制速率. Handlers可以使用任何模型库. 例如,用AI SDK的 streamText数字 :
import { streamText } from "ai";
// Bring your own model from any AI SDK provider
// (@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google, ...).
bot.onDirectMessage(async (thread, message) => {
const result = streamText({
model: myModel,
prompt: message.text,
});
await thread.post(result.textStream);
});
适配者的荣誉 request.signal,所以打电话 stop() 从客户端的短路中取出服务器上的转发器. task_update 财务报告和审定财务报表 plan_update StreamChunk电线上没有原生的v1代表,被悄悄地抛出.
信件持续性
persistMessageHistory 默认为 true。Web没有平台侧历史 API,所以是聊天 sdk 处理器通过前一个回合查看的唯一方法 thread.messages 编号 : channel.messages 是通过配置的状态适配器消息历史缓存。 准备吧 false 只有在您的操作者不需要前轮 :
createWebAdapter({
userName: "mybot",
getUser: (req) => /* ... */,
persistMessageHistory: false,
});
客户端在每个请求上重新发送整个对话,但适配器不从中读取历史. 浏览器控制请求主体 messages[] 数组,因此适配器只消耗最新的用户消息,并从中去除工具部件. 最后一个条目必须是用户消息;一个以助手或工具消息结尾的正文,如客户端工具结果被HTTP 400拒绝. 再次发送相同的用户消息( S)regenerate() 或重新装入)再次运行处理器,而不是作为重复被丢弃,历史保存了更早的回覆并存储了重复的信件第二次.
message.raw 输入 WebMessage部分字段 type 为: unknown与 "ai-sdk" 形状匹配 AI SDK UIMessage这样你就可以投出 message.raw as UIMessage。 。 。 .
框架帮助者
@chat-adapter/web/react 输出薄薄的包装 @ai-sdk/react是因为 useChat 预配置为 DefaultChatTransport数字 :
import { useChat } from "@chat-adapter/web/react";
const { messages, sendMessage, status, stop, regenerate } = useChat({
api: "/api/chat", // default
threadId: "support-1", // becomes useChat's `id` and the request body's `id`
});
| 选项 | 说明 |
|---|---|
api | Web适配器路由的API端点. 默认为 /api/chat。 。 。 . |
threadId | 聊天- sdk 线程 id — 请求体的表面 id 这样服务器就可以获得聊天-sdk线程 id. 大力推荐. 倒计时 id 从 ChatInit。 。 。 . |
experimental_throttle | Throttle在ms中等待聊天消息和数据更新. |
resume | 是否恢复当前聊天生成流 . |
| 退缩 | 所有其他选项都通过 @ai-sdk/react是因为 useChat。 。 。 . |
用于高级配置(海关运输、响应拦截器等) @ai-sdk/react是因为 useChat 直接——包装上没有什么神奇.
@chat-adapter/web/vue 出口a useChat 返回 Vue 反应的工厂 Chat 实例 @ai-sdk/vue数字 :
<script setup lang="ts">
import { useChat } from "@chat-adapter/web/vue";
const chat = useChat({ api: "/api/chat", threadId: "support-1" });
</script>
<template>
<div v-for="msg in chat.messages" :key="msg.id">
<template
v-for="(part, index) in msg.parts"
:key="`${msg.id}-${part.type}-${index}`"
>
<p v-if="part.type === 'text'">{{ part.text }}</p>
</template>
</div>
</template>
@chat-adapter/web/svelte 出口a useChat 返回 Svelte 反应的工厂 Chat 实例 @ai-sdk/svelte数字 :
<script lang="ts">
import { useChat } from "@chat-adapter/web/svelte";
const chat = useChat({ api: "/api/chat", threadId: "support-1" });
</script>
{#each chat.messages as msg (msg.id)}
{#each msg.parts as part, index (`${msg.id}-${part.type}-${index}`)}
{#if part.type === "text"}<p>{part.text}</p>{/if}
{/each}
{/each}
与反应助手不同,Vue和Svelte返回 Chat 直接出现。 访问 chat.messages, (中文(简体) ), chat.sendMessage(), (中文(简体) ), chat.status,以及 chat.stop() 而不是去结构化.
配置
| 选项 | 需求 | 说明 |
|---|---|---|
userName | 对 | Bot用户名. 聊天 sdk 要求的提及检测( S)@username)并种子助理消息的机器人身份. |
getUser | 对 | (request: Request) => WebUser | null | Promise<WebUser | null>。从输入的 HTTP 请求中解析用户。 正在返回 null 生产出 HTTP 401. |
protocol | 没有 | "ai-sdk" (违约)或 "ag-ui". 请求和流式答复的无线电协议。 使用 "ag-ui" 给TanStack AI和其他AG-UI客户端. |
persistMessageHistory | 没有 | 在配置的状态适配器中坚持发送消息历史 。 默认 : true。 。 。 . |
threadIdFor | 没有 | 从已解析的用户和使用Chat 对话 id 中获取聊天- sdk 线索 id 。 默认 : web:{user.id}:{conversationId}。 。 。 . |
logger | 没有 | 锁定实例( 默认为 ConsoleLogger("info")) (中文(简体) ). |
特征
通讯
| 特性 | 支助 |
|---|---|
| 邮件 | 对 |
| 编辑信件 | 没有(每个助理转弯都是新鲜的回音) |
| 删除信件 | 没有 |
| 文件上传 | 否(推迟到v2) |
| 流线 | 原生( SSE: AI SDK UI 消息流或 AG- UI) |
| 已排定的消息 | 没有 |
丰富内容
| 特性 | 支助 |
|---|---|
| 卡片格式 | 只在v1(卡/JSX被推迟到v2)中下标 |
| 按钮 | 没有 |
| 表格 | 是(全球增长机制降标) |
| 模块 | 没有 |
对话
| 特性 | 支助 |
|---|---|
| 提及 | N/A(每个网络消息路由作为管理器) |
| 添加反应 | 没有 |
| 删除反应 | 没有 |
| 打字指标 | 不适用(使用Chat生成a) status (来自SSE反应本身) |
| 模式 | 对.. isDM: true 每个线程 |
信件历史
| 特性 | 支助 |
|---|---|
| 获取信件 | Via 状态适配器缓存( 无平台 API) |
| 获取单个消息 | 没有 |
| 获取线索信息 | 是(合成) |
| 获取频道消息 | 通过状态适配器缓存 |
| 列表线索 | 没有 |
| 后频道消息 | 没有 |
v1 范围
在: 文本 + 标记下, 本地流线, DM 风格的路由, 持续的信息历史, 中止通过 request.signal。 。 。 .
退出( 推迟至 v2 ): 卡/JSX渲染,反应,模式,文件上传,编辑/删除,多塔布主动推.
解决问题
每个请求返回401
getUser正在返回null或掷出. 在其中添加一个日志以确认请求实际包含您期望的会话 .- 饼干没有被转发——检查一下
useChat被挂载在与/api/chat(或您的运输通过证书).
每个请求返回 400 “无效用户 ID ”
- 身份证归来
getUser包含一个:字符,将损坏线条编号的回转。 将内部的标识规范化getUser(例如,id.replace(/:/g, "_")或基础64-编码它.
useChat 在每次渲染时重现状态
- 别走
id: undefined改为useChat- 内部的包裹守卫 - 但是如果你打电话@ai-sdk/react是因为useChat直接删除id而不是通过undefined。 。 。 .
thread.messages 为空
persistMessageHistory实值false没有任何平台-侧面的历史可以倒退。 准备吧true(默认). 适配器从不通过请求体messages[]因此,状态适配器是先前转弯的唯一来源.
请求退回 400 “信件的最后一项必须是用户信件”
- 适配器只处理新用户转动. 客户端工具结果和自动重新提交(例如,
useChat是因为sendAutomaticallyWhen)发送一个以助手或工具消息结尾的正文,不支持.
AI 编码代理
如果您使用OpenAI Codex, Claude Code, 或 Cursor 等AI编码代理, 在写入代码前安装 Chat SDK 技能, 使其了解 SDK API, 适配器模式, 以及项目常规 .
npx skills add vercel/chat
技术参考文献 node_modules/chat/docs,加上已发布的软件包中的适配器指南和启动模板.
您也可以安装 Vercel 插件 包括Chat SDK技术与专家代理、代理斜线指令
npx plugins add vercel/vercel-plugin
插件是可选的;单是技能就足以用Chat SDK来构建.
关于可代理阅读的文件,见 聊天- sdk.dev/llms.txt (页数索引)或 聊天- sdk.dev/llms- full.txt (全文).
许可证
麻省理工学院