CopilotKit 前端工具(Frontend Tools)实战:以 Claude Agent SDK (TypeScript) 集成的 change_background 为例
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
本篇文章以仓库中 Claude Agent SDK (TypeScript) 集成示例的前端工具(Frontend Tools)QA 文档为主线,完整讲解「Agent 调用浏览器端工具、在客户端执行并把结果回传给 Agent」这一能力的机制、验证步骤与底层原理。读完本文,你将掌握useFrontendTool的注册方式、前端工具如何经由 AG-UI 协议抵达 Claude 后端,以及如何通过手动 QA 与 Playwright 端到端测试双重验证「工具在客户端执行、Agent 感知结果」的完整链路。
前置条件:Demo 已部署、Agent 后端健康
QA 文档的第一部分是两条前置条件,它们是整个验证流程能否开始的基础:
- Demo 已部署并可访问:即 Next.js 应用正常运行,
/demos/frontend-tools页面可以被浏览器打开; - Agent 后端健康:Claude Agent SDK (TypeScript) 后端进程可被 CopilotKit Runtime 访问。
在仓库中,这两条前提有明确的代码支撑。CopilotKit Runtime 路由 src/app/api/copilotkit/route.ts 中定义了:
// The Claude agent backend runs as a separate TypeScript process on port 8000. // This runtime proxies CopilotKit requests to it via AG-UI protocol. const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000";后端默认监听8000端口,可通过AGENT_URL环境变量覆盖。route 的GET分支还提供健康探针:它请求${AGENT_URL}/health(3 秒超时),并把agent_status(reachable/error/unreachable)、ANTHROPIC_API_KEY是否已设置等信息以 JSON 返回。因此在开始手动验证前,可以先访问/api/copilotkit检查后端连通性与密钥配置,这正是 QA「Agent backend is healthy」的自动化化表达。
核心机制:useFrontendTool 如何把浏览器变成 Agent 的工具箱
理解change_background之前,先看它背后的架构。示例后端是「pass-through(透传)」模式——route.ts 的注释明确指出:
The Claude Agent SDK (TypeScript) backend is a pass-through: it forwards whatever tools the AG-UI client provides (frontend-registered via useFrontendTool / useRenderTool ...) to Claude. So distinct agent behaviour across demos comes from the frontend, not a per-demo backend graph.
也就是说:工具的「所有权」在前端。前端通过useFrontendTool注册工具,工具定义随 AG-UI run 输入一起发给 Runtime;Runtime 转发给后端进程;后端进程把 AG-UI 工具定义转换为 Anthropic Messages API 的toolsschema 后交给 Claude;Claude 决定何时调用该工具,调用请求再沿原路返回前端,由handler在浏览器中执行。
这个「AG-UI 工具定义 → Anthropic 工具 schema」的转换实现在 src/agent_server.ts 的buildTools函数(@region[frontend-tools-setup]):
function buildTools(tools: RunAgentInput["tools"]): Anthropic.Tool[] { if (!tools || tools.length === 0) return []; return tools.map((tool) => { let inputSchema: Anthropic.Tool.InputSchema = { type: "object", properties: {} }; if (tool.parameters) { try { const parsed = typeof tool.parameters === "string" ? JSON.parse(tool.parameters) : tool.parameters; inputSchema = parsed as Anthropic.Tool.InputSchema; } catch (parseErr) { // Don't silently swap in an empty schema ... console.warn(`[agent_server] failed to parse tool.parameters for ${tool.name}; using empty schema. error=${message}`); } } return { name: tool.name, description: tool.description ?? "", input_schema: inputSchema }; }); }两个值得注意的实现细节:
- schema 解析容错:
parameters既可能是字符串(JSON)也可能是对象,统一解析为 Anthropic 的input_schema;解析失败时不静默降级为空 schema(那会让 Claude 接受任意输入形状),而是打console.warn大声告警; - 空 schema 兜底:当工具没有声明参数时,使用
{ type: "object", properties: {} },保证请求在 Messages API 层面始终合法。
在 agentic loop 中(同文件 L1818-1822),运行时工具(前端注册)与 demo 自带后端工具会被合并,且运行时工具优先级更高。对于前端工具这类「非后端工具」,loop 只在收到 Claude 的 tool_use 后把调用请求透传给客户端执行,而不会在服务端自行执行(见 L2196-2197 的注释)。
一步步验证:QA 文档五步测试的逐条拆解
QA 文档给出了 5 个测试步骤,下面结合源码逐条展开,说明「测什么、为什么这么测、底层对应什么」。
步骤 1:导航到 /demos/frontend-tools
页面入口是 src/app/demos/frontend-tools/page.tsx,顶层结构为:
export default function FrontendToolsDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent="frontend_tools"> <Chat /> </CopilotKit> ); }runtimeUrl="/api/copilotkit"指向上一节介绍的 Runtime 路由,agent="frontend_tools"指定 agent id。这个 id 在 route.ts 的agentNames注册表中("frontend_tools"出现在 "newly ported demos" 分组),并被映射到同一个 pass-through 后端——再次印证「各 demo 的行为差异来自前端,而非各自的 backend graph」。
步骤 2:发送 "Change the background to a sunset gradient"
用户通过CopilotSidebar(agentId="frontend_tools"、defaultOpen)输入这条自然语言指令。为了让用户一键触发,demo 还通过 src/app/demos/frontend-tools/suggestions.ts 的useConfigureSuggestions预置了三个建议 pill:
suggestions: [ { title: "Sunset theme", message: "Make the background a sunset gradient." }, { title: "Forest theme", message: "Switch to a deep green forest gradient." }, { title: "Cosmic theme", message: "Make it a navy → magenta cosmic gradient." }, ], available: "always",其中 "Sunset theme" 的消息正是 QA 文档要验证的「sunset gradient」场景。
步骤 3:验证 change_background 前端工具执行、页面背景发生改变
这一步是核心。工具注册代码在 page.tsx:
const [background, setBackground] = useState<string>(DEFAULT_BACKGROUND); useFrontendTool({ name: "change_background", description: "Change the page background. Accepts any valid CSS background value — colors, linear or radial gradients, etc.", parameters: z.object({ background: z.string().describe("The CSS background value. Prefer gradients."), }), handler: async ({ background }) => { setBackground(background); return { status: "success" }; }, });可以拆解出四个要素:
| 配置项 | 值 | 作用 |
|---|---|---|
name | change_background | 工具唯一标识,也是 Claude 发起 tool_use 时使用的名字 |
description | 接受任意合法 CSS 背景值(颜色、线性/径向渐变等) | 给 LLM 的工具说明,直接影响其调用准确度 |
parameters | Zod schema:{ background: string } | 声明入参类型,供 LLM 生成结构化参数,并用于前端校验 |
handler | async ({ background }) => setBackground(background) | 在浏览器端实际执行:把 CSS 值写入 React state |
handler返回{ status: "success" },这个返回值会经 AG-UI 协议回传给后端,最终作为tool_result让 Claude 感知执行结果——这正是 QA「expected results」里「Frontend tool executes on the client and the agent sees the result」的底层含义。
背景的呈现与默认值在 src/app/demos/frontend-tools/background.tsx:
export const DEFAULT_BACKGROUND = "#4f46e5"; // solid indigo // <div>test("background container starts with the solid indigo default", async ({ page }) => { const bg = page.locator('[data-testid="frontend-tools-background"]'); const initial = await bg.getAttribute("style"); expect(initial ?? "").toContain("#4f46e5"); }); test("Forest theme pill mutates the background inline style", async ({ page }) => { await page.getByRole("button", { name: /Forest theme/i }).click(); const bg = page.locator('[data-testid="frontend-tools-background"]'); await expect.poll(async () => { const s = (await bg.getAttribute("style")) ?? ""; return !s.includes("#4f46e5"); }, { timeout: 45000 }).toBe(true); }); test("Sunset theme pill triggers a gradient change", async ({ page }) => { await page.getByRole("button", { name: /Sunset theme/i }).click(); const bg = page.locator('[data-testid="frontend-tools-background"]'); await expect.poll(async () => { const s = (await bg.getAttribute("style")) ?? ""; return /linear-gradient|radial-gradient/.test(s); }, { timeout: 45000 }).toBe(true); });解读这三条测试的验证逻辑:
- 默认态:背景内联样式含
#4f46e5,确认初始画布干净; - Forest theme:点击 pill 后轮询内联样式,直到其不再包含默认值——说明
change_background已被调用且setBackground生效; - Sunset theme:轮询到
linear-gradient或radial-gradient出现——说明 LLM 理解了「sunset gradient」并生成了合法渐变 CSS 值。
expect.poll的 45 秒超时覆盖了「前端注册 → 后端转换 → Claude 推理 → 工具回传 → handler 执行」的整条链路延迟。测试文件还注释了验证环境的分工:aimock feature-parity fixture 覆盖 "sunset-themed gradient" 提示词,真实 LLM 在 Railway 上处理自由文本提示词。
延伸:异步前端工具 query_notes 与更多组合场景
同一集成下还有一个异步版本 QA 文档 qa/frontend-tools-async.md,用于验证带asynchandler 的前端工具。其测试步骤为:导航到/demos/frontend-tools-async→ 让 Agent 查询笔记(如 "Look up my note about project kickoff")→ 验证query_notes工具触发并解析 → 验证 Agent 用解析出的笔记内容回复 → 无 console 错误。预期结果是「异步工具解析被正确等待并呈现,无 UI 错误」。
实现见 src/app/demos/frontend-tools-async/page.tsx,它与同步版的关键差异有三点:
- async handler:
handler: async ({ keyword }) => { await sleep(500); ... },模拟 500ms 的客户端数据库往返,返回{ keyword, count, notes }; - 本地假数据库:
fake-notes-db.ts中的NOTES_DB提供笔记数据,query_notes在标题、摘要、标签上进行大小写不敏感的模糊搜索并截取前 5 条; - render 回调:额外的
render: ({ args, result, status }) => ...让前端工具在「执行中(loading)」与「完成」两个状态渲染出NotesCard卡片,把工具状态可视化。
对应的 e2e 测试 tests/e2e/frontend-tools-async.spec.ts 与query_notes一起,构成了「异步前端工具」的完整验证闭环。由此可以推断,同一套useFrontendTool机制既支持同步副作用(改背景),也支持异步数据查询(查笔记),还可结合useRenderTool实现更复杂的生成式 UI 渲染;在 route.ts 的注册表中,「gen-ui-interrupt」等 demo 还用带 async handler 的useFrontendTool模拟 LangGraph 的interrupt()语义,说明这一机制足以承载「中断/恢复」类交互。
故障排查指引
当 QA 步骤失败时,可以按链路顺序排查:
| 现象 | 可能原因 | 排查入口 |
|---|---|---|
| 页面打不开 | Demo 未部署 / 路由错误 | 确认/demos/frontend-tools可访问,检查 Next.js 部署状态 |
| 发送消息无响应 | Agent 后端不健康 | 访问/api/copilotkit的 GET 探针,查看agent_status与ANTHROPIC_API_KEY是否 set |
| 工具不触发 | 描述/schema 不佳 | 检查change_background的description与 Zodparameters是否清晰 |
| 背景无变化 | handler 未执行或结果未回流 | 观察浏览器 console;后端启用SHOWCASE_ROUTE_DEBUG=1查看每请求日志 |
| Claude 收到空 schema | parametersJSON 解析失败 | 查看后端console.warn(buildTools的失败告警) |
其中SHOWCASE_ROUTE_DEBUG开关在 route.ts 中定义(默认关闭,设置为1或true开启逐请求日志),而设置AGENT_URL可把代理指向任意后端实例(如本地调试端口)。
总结
从这篇 QA 文档出发,我们完整走通了 CopilotKit 前端工具(Frontend Tools)在 Claude Agent SDK (TypeScript) 集成中的全链路:前端用useFrontendTool注册工具与 handler(page.tsx),工具定义随 AG-UI run 输入经透传 Runtime(route.ts)到达后端,由buildTools转换为 Anthropic Messages API schema(agent_server.ts),Claude 决策调用后在浏览器端执行并把tool_result回流,最终由 Agent 总结确认。手动 QA 文档与 Playwright e2e 测试(frontend-tools.spec.ts)互为镜像,分别覆盖「人工体验」与「自动化回归」两种验证维度——这种「QA 文档 → 源码 → e2e 测试」三位一体的组织方式,本身就是前端工具类特性落地的最佳实践样板。
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考