← Создание агентов на Cloudflare / agents / tools / codemode
Интеграция браузера
Используйте @cloudflare/codemode/browser когда браузер владеет инструментами, которые должна оркестровать модель. Например, эти инструменты могут считывать состояние страницы, обращаться к API браузера или обновлять данные, хранящиеся в вашем приложении.
Code Mode полезен, когда модели нужно вызывать несколько клиентских инструментов с циклами, условиями или промежуточными результатами. Для отдельного действия в браузере вместо этого используйте обычный инструмент на стороне клиента.
Code Mode предоставляет эти инструменты модели в виде типизированных функций. Модель пишет одну асинхронную arrow-функцию JavaScript, которая может вызывать несколько инструментов, объединять их результаты и управлять потоком выполнения. IframeSandboxExecutor выполняет этот сгенерированный код в изолированном iframe на странице.
Эта интеграция не даёт агенту контроль над удалённым браузером. Для просмотра веб-страниц, создания снимков экрана или автоматизации страниц с помощью Chrome DevTools Protocol (CDP) обратитесь к Инструменты браузера.
Установка Code Mode
Установите пакет в своём клиентском приложении:
npm i @cloudflare/codemode @cloudflare/codemode/browser точка входа использует JSON Schema и API браузера. Она не требует peer-зависимостей AI SDK или Zod, которые использует @cloudflare/codemode/ai.
Добавьте Code Mode в чат-интерфейс Agent
Браузер создаёт инструмент Code Mode и регистрирует его как динамический клиентский инструмент. Agent получает схему инструмента, но сама реализация инструмента остаётся в браузере.
-
Определите инструменты, принадлежащие браузеру, с помощью JSON Schema и
executeфункцию.src/browser-tools.jsexport 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.tsimport 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()не использует схему для проверки аргументов во время выполнения. Проверяйте ненадёжные входные данные внутри каждогоexecuteфункцию при необходимости. -
Создайте дескриптор Code Mode с исполнителем iframe.
src/codemode-tool.jsimport { IframeSandboxExecutor, createBrowserCodeTool, } from "@cloudflare/codemode/browser"; import { browserTools } from "./browser-tools"; export const codemodeTool = createBrowserCodeTool({ tools: browserTools, executor: new IframeSandboxExecutor(), });src/codemode-tool.tsimport { IframeSandboxExecutor, createBrowserCodeTool } from "@cloudflare/codemode/browser"; import { browserTools } from "./browser-tools"; export const codemodeTool = createBrowserCodeTool({ tools: browserTools, executor: new IframeSandboxExecutor() });createBrowserCodeTool()возвращает простой дескриптор с именемcodemode. Его описание включает сгенерированные определения TypeScript для инструментов браузера. Его входные данные содержат сгенерированный моделью JavaScript вcodeсвойство.executorопция необязательна. Если её не указать,createBrowserCodeTool()создаётIframeSandboxExecutorс настройками по умолчанию. -
Зарегистрируйте дескриптор с помощью
useAgentChat()и выполняет вызовы клиентских инструментов.src/client.jsximport { 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.tsximport { 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()отправляет схему зарегистрированного клиентского инструмента в Agent. Когда модель вызываетcodemode,onToolCallвыполняет дескриптор в браузере и добавляет его результат в разговор. -
В Agent преобразуйте клиентские схемы в инструменты модели.
src/server.jsimport { 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.tsimport { 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 сообщает модели схему, предоставленную клиентом. Он не выполняет сгенерированный код и не запускает реализации инструментов браузера.
Если набор инструментов браузера изменяется во время выполнения, создайте новый дескриптор Code Mode и зарегистрируйте обновлённый дескриптор в клиентском слое инструментов.
Выполнение iframe и безопасность
IframeSandboxExecutor создаёт скрытый iframe для каждого выполнения. Этот iframe использует sandbox="allow-scripts" и получает сгенерированный код через postMessage. Вызовы инструментов возвращаются на родительскую страницу, которая запускает соответствующий execute функцию.
Сообщения ограничены текущим iframe и nonce выполнения. После завершения, ошибки или тайм-аута executor удаляет iframe и слушатель сообщений.
Executor принимает следующие параметры:
| Опция | Тип | По умолчанию | Поведение |
|---|---|---|---|
timeout |
number |
30000 |
Завершает выполнение по истечении указанного количества миллисекунд. |
csp |
string |
default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; |
Устанавливает Content Security Policy (CSP) для документа iframe. |
Политика CSP по умолчанию блокирует все ресурсы, кроме встроенных и выполняемых через eval скриптов, необходимых для запуска сгенерированного кода. Передавайте собственную политику только если сгенерированному коду требуются дополнительные возможности iframe.
Ослабление директив, таких как connect-src, img-src, или form-action может позволить сгенерированному коду iframe взаимодействовать с внешними системами. Такой код способен раскрыть значения, возвращённые инструментами браузера. Ограничивайте набор допустимых адресов для исходящих запросов и не помещайте секреты в результаты инструментов. Инструменты, принадлежащие браузеру, выполняются отдельно, на родительской странице, с теми возможностями, которые предоставляет их реализация.
Ограничения подтверждения
createBrowserCodeTool() исключает любой инструмент, чей needsApproval значение равно true или функцию. Code Mode не приостанавливает выполнение iframe, чтобы запросить подтверждение для этих инструментов.
Держите действия, требующие подтверждения, вне дескриптора Code Mode. Регистрируйте их как обычные инструменты и используйте useAgentChat() поток подтверждения взамен.