跳转到内容
搜索文档

浏览器集成

最后更新 查看 MarkdownAgent 设置

当浏览器拥有模型必须编排的工具时使用 @cloudflare/codemode/browser。例如这些工具可能读取页面状态、访问浏览器 API 或更新应用持有的数据。

当模型必须用循环、条件或中间结果调用多个客户端工具时 Code Mode 有用。单个浏览器操作请使用标准客户端工具。

Code Mode 将这些工具作为带类型的函数呈现给模型。模型编写一个 JavaScript async 箭头函数,可调用多个工具、组合结果并应用控制流。IframeSandboxExecutor 在页面沙箱 iframe 中运行生成的代码。

此集成不会让 agent 控制 remote browser。要检查网站、截图或通过 Chrome DevTools Protocol (CDP) 自动化页面,请参阅 Browser tools

安装 Code Mode

在 client 应用中安装包:

npm i @cloudflare/codemode

@cloudflare/codemode/browser 入口使用 JSON Schema 与 browser API。不需要 @cloudflare/codemode/ai 使用的 AI SDK 或 Zod peer dependency。

将 Code Mode 加入 Agent 聊天 UI

Browser 创建 Code Mode tool 并注册为动态 client tool。Agent 接收 tool schema,但 tool 实现保留在 browser。

  1. 用 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 函数内验证不可信输入。

  2. 用 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

  3. 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 加入对话。

  4. 在 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 描述符并在客户端工具层注册更新的描述符。

Iframe 执行与安全

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-srcimg-srcform-action 等 directive 可能让生成的 iframe 代码与外部系统通信。该代码可能暴露 browser tool 返回的值。保持出站 destination 窄,不要在 tool result 中放置 secret。Browser 自有 tool 在 parent 页面单独执行,保留其实现提供的能力。

审批约束

createBrowserCodeTool() 排除 needsApprovaltrue 或函数的工具。Code Mode 不会暂停 iframe 执行以请求这些工具的审批。

将需审批门控的操作保持在 Code Mode 描述符外。将其注册为标准工具,改用 useAgentChat() 审批流程

这篇文档对您有帮助吗?