Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

ブラウザー連携

最終更新 Markdown で表示Agent セットアップ

モデルがオーケストレーションするツールをブラウザーが持っているときは、@cloudflare/codemode/browser を使います。たとえば、ページ状態の読み取り、ブラウザー API へのアクセス、アプリケーションが持つデータの更新です。

モデルがループ、条件、中間結果を伴って複数のクライアントツールを呼ぶ必要があるときに、Code Mode が役立ちます。ブラウザー操作が 1 回だけなら、通常のクライアント側ツールを使います。

Code Mode は、それらのツールを型付き関数としてモデルに見せます。モデルは、複数ツールの呼び出し、結果の結合、制御フローを書ける JavaScript の async アロー関数を 1 つ書きます。IframeSandboxExecutor は、生成されたコードをページ上のサンドボックス iframe で実行します。

この連携は、エージェントにリモートブラウザーの制御を与えません。Web サイトの確認、スクリーンショットの取得、Chrome DevTools Protocol(CDP)によるページ自動化は、ブラウザーツール を参照してください。

Code Mode をインストールする

クライアントアプリケーションにパッケージをインストールします。

npm i @cloudflare/codemode

@cloudflare/codemode/browser のエントリポイントは、JSON Schema とブラウザー API を使います。@cloudflare/codemode/ai が使う AI SDK や Zod のピア依存は不要です。

エージェントのチャット UI に Code Mode を追加する

ブラウザーが Code Mode ツールを作成し、動的なクライアントツールとして登録します。エージェントはツールスキーマを受け取りますが、実装はブラウザー側に残ります。

  1. 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 関数の内側で検証します。

  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 という名前のプレーンなツール定義を返します。description には、ブラウザーツールの生成済み TypeScript 定義が含まれます。入力の code プロパティに、モデルが生成した JavaScript が入ります。

    executor オプションは省略できます。省略すると、createBrowserCodeTool() はデフォルト設定の IframeSandboxExecutor を作成します。

  3. 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 がブラウザーでツール定義を実行し、出力を会話に追加します。

  4. エージェント側で、クライアントスキーマをモデルツールへ変換します。

    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 ツール定義を作成し、更新した定義をクライアントツール層へ登録します。

iframe 実行とセキュリティ

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-srcimg-srcform-action などのディレクティブを緩めると、生成された iframe コードが外部システムと通信できます。そのコードは、ブラウザーツールが返した値を漏らす可能性があります。送信先は狭く保ち、ツール結果に秘密情報を入れないでください。ブラウザー側のツールは親ページで別に実行され、実装が与えた能力を持ちます。

承認の制約

createBrowserCodeTool() は、needsApprovaltrue または関数のツールを除外します。Code Mode は、それらのツールの承認を求めるために iframe 実行を一時停止しません。

承認が必要な操作は、Code Mode ツール定義の外に置きます。通常のツールとして登録し、useAgentChat() の承認フロー を使います。

役に立ちましたか?