Browser Run ツールで、ウェブを閲覧し、ページを検査し、スクリーンショットを取得し、フロントエンドの問題をデバッグするエージェントを構築します。ベータ
クリック、スクリーンショット、遷移といった固定のブラウザー操作ではなく、LLM が JavaScript を書き、ライブのブラウザーセッションに対して CDP コマンドを実行します。プロトコル上のすべてのドメイン、コマンド、イベント、型にアクセスできます。
提供されるツールは次の 2 つです。
| ツール | 説明 |
|---|---|
browser_search |
CDP 仕様を照会し、コマンド、イベント、型を見つけます。仕様はブラウザーの CDP エンドポイントから動的に取得し、キャッシュします。 |
browser_execute |
cdp ヘルパー経由で、ライブのブラウザーに対して CDP コマンドを実行します。呼び出しごとに新しいセッションを開き、コードを実行し、閉じます。 |
次のような処理が必要なときに、ブラウザーツールが役立ちます。
- ウェブページの検査 — DOM 構造、計算済みスタイル、アクセシビリティツリー
- フロントエンドのデバッグ — ネットワークウォーターフォール、コンソールエラー、パフォーマンストレース
- 構造化データのスクレイピング — 描画済みページからコンテンツを抽出
- スクリーンショットや PDF の取得 — ウェブコンテンツの視覚的なスナップショット
- パフォーマンスのプロファイリング — Core Web Vitals、JavaScript プロファイリング、メモリ分析
描画済み DOM が不要な単純なページ取得には、代わりに fetch() を使います。
ブラウザーツールには Agents SDK と @cloudflare/codemode が必要です。
npm install agents @cloudflare/codemode ai zodwrangler 設定に、Browser Run(旧 Browser Rendering)と Worker Loader のバインディングを追加します。
{
"compatibility_flags": ["nodejs_compat"],
"browser": {
"binding": "BROWSER",
},
"worker_loaders": [
{
"binding": "LOADER",
},
],
}compatibility_flags = [ "nodejs_compat" ]
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});Browser Run バインディングではなく、独自の CDP エンドポイントに接続する場合は cdpUrl を渡します。
ブラウザーツールを、ほかのツールと一緒に渡します。model には任意の AI SDK プロバイダーを使えます。ここでは Workers AI を使います。
import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant that can inspect web pages.",
messages,
tools: {
...browserTools,
...otherTools,
},
});import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
const workersai = createWorkersAI({ binding: env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You are a helpful assistant that can inspect web pages.",
messages,
tools: {
...browserTools,
...otherTools,
},
});どちらのツールも、JavaScript の async アロー関数を入れる code パラメーターを受け取ります。サンドボックスはツールに応じてグローバルを注入します。browser_search には spec、browser_execute には cdp です。
LLM が browser_search を使うとき、コードは注入された spec オブジェクト経由で CDP 仕様を照会します。
async () => {
const s = await spec.get();
return s.domains
.find((d) => d.name === "Network")
.commands.map((c) => ({ method: c.method, description: c.description }));
};LLM が browser_execute を使うとき、コードは注入された cdp ヘルパー経由で CDP コマンドを実行します。
async () => {
const { targetId } = await cdp.send("Target.createTarget", {
url: "https://example.com",
});
const sessionId = await cdp.attachToTarget(targetId);
const { root } = await cdp.send("DOM.getDocument", {}, { sessionId });
const { outerHTML } = await cdp.send(
"DOM.getOuterHTML",
{ nodeId: root.nodeId },
{ sessionId },
);
await cdp.send("Target.closeTarget", { targetId });
return outerHTML;
};典型的なパターンは、AIChatAgent のメッセージハンドラー内でブラウザーツールを作ることです。メッセージの永続化とストリーミングが使えます。
import { AIChatAgent } from "@cloudflare/ai-chat";
import { createBrowserTools } from "agents/browser/ai";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
export class MyAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You can browse the web and inspect pages.",
messages: await convertToModelMessages(this.messages),
tools: {
...browserTools,
},
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
}import { AIChatAgent } from "@cloudflare/ai-chat";
import { createBrowserTools } from "agents/browser/ai";
import { createWorkersAI } from "workers-ai-provider";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
export class MyAgent extends AIChatAgent<Env> {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
system: "You can browse the web and inspect pages.",
messages: await convertToModelMessages(this.messages),
tools: {
...browserTools,
},
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
}TanStack AI では、/tanstack-ai エクスポートを使います。
import { createBrowserTools } from "agents/browser/tanstack-ai";
import { chat, workersAIText } from "@tanstack/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
const stream = chat({
adapter: workersAIText(env.AI, "@cf/zai-org/glm-4.7-flash"),
tools: [...browserTools, ...otherTools],
messages,
});import { createBrowserTools } from "agents/browser/tanstack-ai";
import { chat, workersAIText } from "@tanstack/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
const stream = chat({
adapter: workersAIText(env.AI, "@cf/zai-org/glm-4.7-flash"),
tools: [...browserTools, ...otherTools],
messages,
});browser_searchは、ブラウザーの/json/protocolエンドポイントからライブの CDP プロトコルを取得し、短時間キャッシュします。browser_executeは呼び出しごとに新しいブラウザーセッションを開き、サンドボックス内のコードへ小さなcdpヘルパー API を公開し、実行終了時にセッションを閉じます。- LLM が生成したコードは Worker サンドボックスで実行されます。CDP トラフィックはホスト Worker 側に残ります。
browser_execute 内では、サンドボックスのコードから次の関数を使えます。
CDP コマンドを送信し、応答を待ちます。
| パラメーター | 型 | 説明 |
|---|---|---|
method |
string |
CDP メソッド。例: "DOM.getDocument" や "Network.enable" |
params |
unknown |
メソッドのパラメーター |
options.timeoutMs |
number |
コマンドごとのタイムアウト(デフォルト: 10 秒) |
options.sessionId |
string |
ターゲットのセッション ID(ページスコープのコマンドでは必須) |
ターゲットにアタッチし、セッション ID を取得します。flatten: true で Target.attachToTarget を使います。
| パラメーター | 型 | 説明 |
|---|---|---|
targetId |
string |
アタッチ先のターゲット |
options.timeoutMs |
number |
アタッチコマンドのタイムアウト |
戻り値は sessionId 文字列です。
直近の CDP デバッグログ(送信、受信、エラー)を取得します。デフォルトは直近 50 件、最大 400 件です。
デバッグログのバッファーをクリアします。
AI SDK ツール(browser_search と browser_execute)を返します。
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
browser |
Fetcher |
— | Browser Run バインディング |
cdpUrl |
string |
— | 独自 CDP エンドポイントへの任意の上書き |
cdpHeaders |
Record<string, string> |
— | CDP URL 検出用ヘッダー(例: Cloudflare Access) |
loader |
WorkerLoader |
必須 | サンドボックス実行用の Worker Loader バインディング |
timeout |
number |
30000 |
実行タイムアウト(ミリ秒) |
browser または cdpUrl のどちらかが必須です。両方を指定した場合は cdpUrl が優先されます。
独自の統合では、構成要素を直接インポートします。
import {
CdpSession,
connectBrowser,
connectUrl,
createBrowserToolHandlers,
} from "agents/browser";
// Connect to a custom CDP endpoint
const session = await connectUrl("http://localhost:9222");
const version = await session.send("Browser.getVersion");
session.close();import {
CdpSession,
connectBrowser,
connectUrl,
createBrowserToolHandlers,
} from "agents/browser";
// Connect to a custom CDP endpoint
const session = await connectUrl("http://localhost:9222");
const version = await session.send("Browser.getVersion");
session.close();最近の Wrangler リリースは、ローカル開発で Browser Run に対応しています。npx wrangler dev がブラウザーを自動で用意するので、同じ browser: env.BROWSER の設定がローカルとデプロイ先の両方で動きます。
cdpUrl は、トンネルや手動管理の Chrome など、別の CDP 互換エンドポイントへ意図的に接続したい場合にだけ使います。
- LLM が生成したコードは 隔離された Worker サンドボックス で実行されます。実行ごとに専用の Worker インスタンスが割り当てられます
- サンドボックスからの外部ネットワークアクセス(
fetch、connect)は、ランタイムレベルで 遮断 されます - CDP コマンドは Workers RPC 経由で送出されます。WebSocket はホスト側にあり、サンドボックスにはありません
- CDP 仕様はサーバー側に残ります。LLM へ流れるのは照会結果だけです
- コンテキストウィンドウの溢流を防ぐため、応答は約 6,000 トークンで切り詰められます
- execute 呼び出しごとに 1 セッション —
browser_executeの呼び出しごとに新しいブラウザーセッションが開きます。複数ステップのワークフローは、1 つのコードブロック内で完了する必要があります。 - 認証済みセッションなし — ブラウザーは Cookie やログイン状態なしで起動します。
- ピア依存関係として
@cloudflare/codemodeが必要です。 - サンドボックス内の実行は JavaScript に限られます(TypeScript 構文は使えません)。
LLM が生成したコードではなく、プログラムからブラウザーを制御したい場合は、Browser Run API と Puppeteer を直接使えます。
npm i -D @cloudflare/puppeteeryarn add -D @cloudflare/puppeteerpnpm add -D @cloudflare/puppeteerbun add -d @cloudflare/puppeteerimport puppeteer from "@cloudflare/puppeteer";
export class MyAgent extends Agent {
async browse(browserInstance, urls) {
let responses = [];
for (const url of urls) {
const browser = await puppeteer.launch(browserInstance);
const page = await browser.newPage();
await page.goto(url);
await page.waitForSelector("body");
const bodyContent = await page.$eval(
"body",
(element) => element.innerHTML,
);
let resp = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
messages: [
{
role: "user",
content: `Return a JSON object with the product names, prices and URLs from the website content below. <content>${bodyContent}</content>`,
},
],
});
responses.push(resp);
await browser.close();
}
return responses;
}
}import puppeteer from "@cloudflare/puppeteer";
interface Env {
BROWSER: Fetcher;
AI: Ai;
}
export class MyAgent extends Agent<Env> {
async browse(browserInstance: Fetcher, urls: string[]) {
let responses = [];
for (const url of urls) {
const browser = await puppeteer.launch(browserInstance);
const page = await browser.newPage();
await page.goto(url);
await page.waitForSelector("body");
const bodyContent = await page.$eval(
"body",
(element) => element.innerHTML,
);
let resp = await this.env.AI.run("@cf/zai-org/glm-4.7-flash", {
messages: [
{
role: "user",
content: `Return a JSON object with the product names, prices and URLs from the website content below. <content>${bodyContent}</content>`,
},
],
});
responses.push(resp);
await browser.close();
}
return responses;
}
}wrangler 設定にブラウザーバインディングを追加します。
{
"ai": {
"binding": "AI",
},
"browser": {
"binding": "BROWSER",
},
}[ai]
binding = "AI"
[browser]
binding = "BROWSER"Browserbase ↗ も使えます。Agent 内から Browserbase API を直接呼び出します。
Browserbase API キー ↗ を取得したら、シークレット を作成して Agent に追加します。
cd your-agent-project-folder
npx wrangler@latest secret put BROWSERBASE_API_KEY@cloudflare/puppeteer パッケージをインストールし、Agent 内から Browserbase API を呼び出します。
npm i @cloudflare/puppeteeryarn add @cloudflare/puppeteerpnpm add @cloudflare/puppeteerbun add @cloudflare/puppeteerimport puppeteer from "@cloudflare/puppeteer";
export class MyAgent extends Agent {
async browse(url) {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${this.env.BROWSERBASE_API_KEY}`,
});
const page = await browser.newPage();
await page.goto(url);
const content = await page.content();
await browser.close();
return content;
}
}import puppeteer from "@cloudflare/puppeteer";
interface Env {
BROWSERBASE_API_KEY: string;
}
export class MyAgent extends Agent<Env> {
async browse(url: string) {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${this.env.BROWSERBASE_API_KEY}`,
});
const page = await browser.newPage();
await page.goto(url);
const content = await page.content();
await browser.close();
return content;
}
}