跳转到内容

前端工具注册模式:Agent 操作浏览器的安全通道

前端工具注册模式

学习目标

理解 CopilotKit 的前端工具注册与执行模式:

  • 工具如何在前端注册、发现和调用
  • 工具执行结果如何回传给 Agent
  • 工具的生命周期管理(注册 → 执行 → 注销)
  • 前端工具与后端工具的安全边界

前置知识

本章涉及工具调用协议的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 CopilotKit 的前端工具实现。

项目实践

工具注册表

CopilotKitCore 内部维护一个工具注册表,管理所有前端可用的工具:

操作说明
registerTool()注册一个新工具
getTool(name)按名称查找工具
runTool(name, args)执行工具并返回结果

每个工具包含以下字段:

工具结构(伪代码):
{
name: "工具名称",
description: "工具描述(Agent 用此理解工具用途)",
parameters: [参数定义],
handler: (args) => Promise<result>, // 浏览器端执行函数
render: (args, status) => ReactNode, // 可选:工具调用时的 UI 展示
available: "enabled" | "disabled" // 可选:动态可用性
}

React Hook 封装

前端工具通过 React Hook 声明式注册:

// 基础前端工具
useFrontendTool({
name: "getCurrentPage",
description: "获取当前页面的 URL 和标题",
parameters: [],
handler: () => ({ url: window.location.href, title: document.title })
});
// 带 UI 渲染的交互工具
useCopilotAction({
name: "confirmBooking",
description: "确认预订",
parameters: [
{ name: "date", type: "string" },
{ name: "room", type: "string" }
],
renderAndWaitForResponse: ({ args, respond, status }) => {
return (
<BookingDialog
date={args.date}
room={args.room}
onConfirm={() => respond("已确认")}
onCancel={() => respond("已取消")}
/>
);
}
});

工具执行的完整流程

工具生命周期管理

React Hook 的 useEffect 自动处理工具的注册和注销:

  1. 挂载:组件 mount → 工具注册到 Core
  2. 更新:组件 update → 更新工具定义(通过 useRef 保持 handler 最新)
  3. 卸载:组件 unmount → 工具自动注销

关键设计:handler 通过 useRef 保持引用稳定,避免因 React 重新渲染导致 handler 过期。

关键代码模式(伪代码):
const handlerRef = useRef(handler);
useEffect(() => { handlerRef.current = handler; }, [handler]);
// 注册时使用稳定的 ref,执行时调用最新的 handler

工具状态机

工具调用经历三个状态:

状态触发事件UI 表现
InProgressTOOL_CALL_START显示”正在执行…”
Executinghandler 开始执行显示参数和自定义 render UI
Completehandler 返回结果显示结果

Catch-All 通配符

name: "*" 注册一个捕获所有未定义工具的 handler:

useCopilotAction({
name: "*",
render: ({ name, args, status }) => {
return <div>未知工具: {name}({JSON.stringify(args)})</div>;
}
});

用途:

  • 调试:查看 Agent 尝试调用哪些未注册的工具
  • 降级:为未知工具提供默认 UI 而非静默忽略

类型安全参数

工具参数通过 Zod Schema 定义,自动映射为 TypeScript 类型:

Parameter[] → Zod Schema → TypeScript 类型推断
{ name: "url", type: "string" } → z.string() → { url: string }
{ name: "count", type: "number" } → z.number() → { count: number }
{ name: "tag", type: "string", enum: ["a", "b"] } → z.enum(["a", "b"])

问题与规避

问题规避策略
工具执行时间过长导致 Agent 超时前端设置合理的超时(如 30s);handler 内部实现 AbortSignal 支持
工具执行失败时的错误传递handler 抛出的异常被 Core 捕获,错误消息通过 TOOL_CALL_RESULT 回传
工具可用性动态切换时 Agent 感知延迟available: "disabled" 标记后,需等待 Agent 下次运行才知道工具已不可用
React 组件卸载导致 handler 丢失使用 useRef 保持 handler 引用;Core 中的执行通过 ref 调用最新 handler
工具名称冲突注册表按名称索引,后注册的覆盖先注册的;建议统一命名前缀

设计取舍

前端工具 vs 后端工具

维度前端工具后端工具
执行位置浏览器服务器
适合场景DOM 操作、用户交互、浏览器 API数据库查询、外部 API 调用、文件操作
安全性用户本地执行,不涉及服务器资源服务器端执行,需要沙箱隔离
延迟即时(无网络往返)需要网络往返到后端
Agent 视角透明(两种工具通过同一协议调用)透明

useFrontendTool vs useCopilotAction

  • useFrontendTool:轻量级,仅有 handler,适合纯逻辑操作
  • useCopilotAction:重量级,支持 render / renderAndWaitForResponse,适合需要 UI 交互的工具

选择策略:不需要 UI 的用 useFrontendTool,需要用户确认/输入的用 useCopilotAction

参考来源

  • packages/react-core/src/hooks/use-frontend-tool.ts
  • packages/react-core/src/hooks/use-copilot-action.ts
  • packages/core/src/core/core.ts(工具注册表实现)