モデルがオーケストレーションするツールをブラウザーが持っているときは、@cloudflare/codemode/browser を使います。たとえば、ページ状態の読み取り、ブラウザー API へのアクセス、アプリケーションが持つデータの更新です。
モデルがループ、条件、中間結果を伴って複数のクライアントツールを呼ぶ必要があるときに、Code Mode が役立ちます。ブラウザー操作が 1 回だけなら、通常のクライアント側ツールを使います。
Code Mode は、それらのツールを型付き関数としてモデルに見せます。モデルは、複数ツールの呼び出し、結果の結合、制御フローを書ける JavaScript の async アロー関数を 1 つ書きます。IframeSandboxExecutor は、生成されたコードをページ上のサンドボックス iframe で実行します。
この連携は、エージェントにリモートブラウザーの制御を与えません。Web サイトの確認、スクリーンショットの取得、Chrome DevTools Protocol(CDP)によるページ自動化は、ブラウザーツール を参照してください。
クライアントアプリケーションにパッケージをインストールします。
npm i @cloudflare/codemodeyarn add @cloudflare/codemodepnpm add @cloudflare/codemodebun add @cloudflare/codemode@cloudflare/codemode/browser のエントリポイントは、JSON Schema とブラウザー API を使います。@cloudflare/codemode/ai が使う AI SDK や Zod のピア依存は不要です。
ブラウザーが Code Mode ツールを作成し、動的なクライアントツールとして登録します。エージェントはツールスキーマを受け取りますが、実装はブラウザー側に残ります。
-
JSON Schema と
execute関数で、ブラウザー側のツールを定義します。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()は、実行時にスキーマで引数を検証しません。信頼できない入力は、必要なときに各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という名前のプレーンなツール定義を返します。description には、ブラウザーツールの生成済み TypeScript 定義が含まれます。入力のcodeプロパティに、モデルが生成した JavaScript が入ります。executorオプションは省略できます。省略すると、createBrowserCodeTool()はデフォルト設定のIframeSandboxExecutorを作成します。 -
useAgentChat()にツール定義を登録し、クライアントツール呼び出しを実行します。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()は、登録したクライアントツールのスキーマをエージェントへ送ります。モデルがcodemodeを呼ぶと、onToolCallがブラウザーでツール定義を実行し、出力を会話に追加します。 -
エージェント側で、クライアントスキーマをモデルツールへ変換します。
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(); } }エージェントは、クライアントから渡されたスキーマをモデルに公開します。生成コードやブラウザーツールの実装は実行しません。
実行時にブラウザーツールの集合が変わる場合は、新しい Code Mode ツール定義を作成し、更新した定義をクライアントツール層へ登録します。
IframeSandboxExecutor は、実行ごとに非表示の iframe を作成します。iframe は sandbox="allow-scripts" を使い、postMessage で生成コードを受け取ります。ツール呼び出しは親ページへ戻り、対応するブラウザー側の execute 関数が走ります。
メッセージは、現在の iframe と実行 nonce にスコープされます。完了、失敗、タイムアウトのあと、実行クラスは iframe とメッセージリスナーを削除します。
実行クラスは次のオプションを受け取ります。
| オプション | 型 | デフォルト | 動作 |
|---|---|---|---|
timeout |
number |
30000 |
指定したミリ秒後に実行を終了します。 |
csp |
string |
default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; |
iframe ドキュメントのコンテンツセキュリティポリシー(CSP)を設定します。 |
デフォルトの CSP は、生成コードの実行に必要なインラインおよび評価スクリプト以外のリソースをブロックします。生成コードに追加の iframe 能力が必要なときだけ、カスタムポリシーを渡します。
connect-src、img-src、form-action などのディレクティブを緩めると、生成された iframe コードが外部システムと通信できます。そのコードは、ブラウザーツールが返した値を漏らす可能性があります。送信先は狭く保ち、ツール結果に秘密情報を入れないでください。ブラウザー側のツールは親ページで別に実行され、実装が与えた能力を持ちます。
createBrowserCodeTool() は、needsApproval が true または関数のツールを除外します。Code Mode は、それらのツールの承認を求めるために iframe 実行を一時停止しません。
承認が必要な操作は、Code Mode ツール定義の外に置きます。通常のツールとして登録し、useAgentChat() の承認フロー を使います。