当浏览器拥有模型必须编排的工具时使用 @cloudflare/codemode/browser。例如这些工具可能读取页面状态、访问浏览器 API 或更新应用持有的数据。
当模型必须用循环、条件或中间结果调用多个客户端工具时 Code Mode 有用。单个浏览器操作请使用标准客户端工具。
Code Mode 将这些工具作为带类型的函数呈现给模型。模型编写一个 JavaScript async 箭头函数,可调用多个工具、组合结果并应用控制流。IframeSandboxExecutor 在页面沙箱 iframe 中运行生成的代码。
此集成不会让 agent 控制 remote browser。要检查网站、截图或通过 Chrome DevTools Protocol (CDP) 自动化页面,请参阅 Browser tools。
在 client 应用中安装包:
npm i @cloudflare/codemodeyarn add @cloudflare/codemodepnpm add @cloudflare/codemodebun add @cloudflare/codemode@cloudflare/codemode/browser 入口使用 JSON Schema 与 browser API。不需要 @cloudflare/codemode/ai 使用的 AI SDK 或 Zod peer dependency。
Browser 创建 Code Mode tool 并注册为动态 client tool。Agent 接收 tool schema,但 tool 实现保留在 browser。
-
用 JSON Schema 与
execute函数定义 browser 自有 tool。src/browser-tools.jsjs export const browserTools = { getPageInfo: { description: "Get information about the current browser page", inputSchema: { type: "object", properties: {}, required: [], }, execute: async () => ({ title: document.title, url: window.location.href, }), }, getSelectionText: { description: "Get the user's current text selection", inputSchema: { type: "object", properties: {}, required: [], }, execute: async () => ({ text: window.getSelection()?.toString() ?? "", }), }, };src/browser-tools.tsts import type { JsonSchemaExecutableToolDescriptors } from "@cloudflare/codemode/browser"; export const browserTools: JsonSchemaExecutableToolDescriptors = { getPageInfo: { description: "Get information about the current browser page", inputSchema: { type: "object", properties: {}, required: [] }, execute: async () => ({ title: document.title, url: window.location.href }) }, getSelectionText: { description: "Get the user's current text selection", inputSchema: { type: "object", properties: {}, required: [] }, execute: async () => ({ text: window.getSelection()?.toString() ?? "" }) } };JSON Schema 提供展示给模型的类型。
createBrowserCodeTool()不在 runtime 用 schema 验证参数。需要时在每个execute函数内验证不可信输入。 -
用 iframe 执行器创建 Code Mode 描述符。
src/codemode-tool.jsjs import { IframeSandboxExecutor, createBrowserCodeTool, } from "@cloudflare/codemode/browser"; import { browserTools } from "./browser-tools"; export const codemodeTool = createBrowserCodeTool({ tools: browserTools, executor: new IframeSandboxExecutor(), });src/codemode-tool.tsts import { IframeSandboxExecutor, createBrowserCodeTool } from "@cloudflare/codemode/browser"; import { browserTools } from "./browser-tools"; export const codemodeTool = createBrowserCodeTool({ tools: browserTools, executor: new IframeSandboxExecutor() });createBrowserCodeTool()返回名为codemode的 plain descriptor。其描述包含 browser tool 的生成 TypeScript 定义。输入在code属性中包含模型生成的 JavaScript。executor选项可选。省略时createBrowserCodeTool()用默认设置创建IframeSandboxExecutor。 -
用
useAgentChat()注册 descriptor 并执行 client tool 调用。src/client.jsxjs import { useAgentChat } from "@cloudflare/ai-chat/react"; import { useAgent } from "agents/react"; import { useMemo } from "react"; import { codemodeTool } from "./codemode-tool"; function BrowserCodeModeChat() { const agent = useAgent({ agent: "browser-codemode" }); const tools = useMemo( () => ({ codemode: { description: codemodeTool.description, parameters: codemodeTool.inputSchema, execute: (input) => codemodeTool.execute(input), }, }), [], ); const { messages, sendMessage } = useAgentChat({ agent, tools, onToolCall: async ({ toolCall, addToolOutput }) => { const tool = tools[toolCall.toolName]; if (!tool?.execute) return; try { const output = await tool.execute(toolCall.input); addToolOutput({ toolCallId: toolCall.toolCallId, output, }); } catch (error) { addToolOutput({ toolCallId: toolCall.toolCallId, state: "output-error", errorText: error instanceof Error ? error.message : String(error), }); } }, }); // Render messages and call sendMessage() from your chat UI. }src/client.tsxts import { useAgentChat, type AITool } from "@cloudflare/ai-chat/react"; import { useAgent } from "agents/react"; import { useMemo } from "react"; import { codemodeTool } from "./codemode-tool"; function BrowserCodeModeChat() { const agent = useAgent({ agent: "browser-codemode" }); const tools = useMemo<Record<string, AITool>>( () => ({ codemode: { description: codemodeTool.description, parameters: codemodeTool.inputSchema, execute: (input) => codemodeTool.execute(input as { code: string }) } }), [] ); const { messages, sendMessage } = useAgentChat({ agent, tools, onToolCall: async ({ toolCall, addToolOutput }) => { const tool = tools[toolCall.toolName]; if (!tool?.execute) return; try { const output = await tool.execute(toolCall.input); addToolOutput({ toolCallId: toolCall.toolCallId, output }); } catch (error) { addToolOutput({ toolCallId: toolCall.toolCallId, state: "output-error", errorText: error instanceof Error ? error.message : String(error) }); } } }); // Render messages and call sendMessage() from your chat UI. }useAgentChat()将注册的 client tool schema 发送到 Agent。模型调用codemode时,onToolCall在 browser 执行 descriptor 并将其 output 加入对话。 -
在 Agent 上,将 client schema 转为 model tool。
src/server.jsjs import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; import { createWorkersAI } from "workers-ai-provider"; export class BrowserCodemode extends AIChatAgent { async onChatMessage(_onFinish, options) { const workersai = createWorkersAI({ binding: this.env.AI }); const result = streamText({ model: workersai("@cf/moonshotai/kimi-k2.7-code"), system: "Use the codemode tool to write JavaScript that calls browser-provided tools.", messages: await convertToModelMessages(this.messages), tools: createToolsFromClientSchemas(options?.clientTools), stopWhen: stepCountIs(10), }); return result.toUIMessageStreamResponse(); } }src/server.tsts import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; import { createWorkersAI } from "workers-ai-provider"; export class BrowserCodemode extends AIChatAgent<Env> { async onChatMessage( _onFinish?: unknown, options?: { clientTools?: Parameters<typeof createToolsFromClientSchemas>[0]; } ) { const workersai = createWorkersAI({ binding: this.env.AI }); const result = streamText({ model: workersai("@cf/moonshotai/kimi-k2.7-code"), system: "Use the codemode tool to write JavaScript that calls browser-provided tools.", messages: await convertToModelMessages(this.messages), tools: createToolsFromClientSchemas(options?.clientTools), stopWhen: stepCountIs(10) }); return result.toUIMessageStreamResponse(); } }Agent 向模型通告客户端提供的 schema。不运行生成的代码或浏览器工具实现。
若浏览器工具集在运行时变化,创建新 Code Mode 描述符并在客户端工具层注册更新的描述符。
IframeSandboxExecutor 为每次执行创建隐藏 iframe。Iframe 使用 sandbox="allow-scripts" 并通过 postMessage 接收生成的代码。工具调用返回父页面,运行匹配的浏览器自有 execute 函数。
消息限定到当前 iframe 与执行 nonce。执行器在完成、失败或超时后移除 iframe 与消息监听器。
Executor 接受这些选项:
| 选项 | 类型 | 默认 | 行为 |
|---|---|---|---|
timeout |
number |
30000 |
在指定毫秒后结束 execution。 |
csp |
string |
default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; |
设置 iframe 文档的内容安全策略(CSP)。 |
默认 CSP 阻止除执行生成代码所需 inline 与 evaluated script 外的资源。仅当生成的代码需要额外 iframe 能力时传递自定义策略。
放宽 connect-src、img-src 或 form-action 等 directive 可能让生成的 iframe 代码与外部系统通信。该代码可能暴露 browser tool 返回的值。保持出站 destination 窄,不要在 tool result 中放置 secret。Browser 自有 tool 在 parent 页面单独执行,保留其实现提供的能力。
createBrowserCodeTool() 排除 needsApproval 为 true 或函数的工具。Code Mode 不会暂停 iframe 执行以请求这些工具的审批。
将需审批门控的操作保持在 Code Mode 描述符外。将其注册为标准工具,改用 useAgentChat() 审批流程。