构建一个 Agent,它可以浏览网页、检查页面、截取屏幕截图,并使用 Browser Run tool 调试前端问题。 Beta
与固定 browser 操作集(click、screenshot、navigate)不同,LLM 编写 JavaScript 代码对 live browser session 运行 CDP 命令——访问协议中所有 domain、command、event 与 type。
提供两个 tool:
| Tool | 描述 |
|---|---|
browser_search |
查询 CDP spec 以发现 command、event 与 type。spec 从 browser 的 CDP 端点动态获取并缓存。 |
browser_execute |
通过 cdp helper 对 live browser 运行 CDP 命令。每次调用打开新的 browser session,执行代码后关闭。 |
当 Agent 需要以下能力时,browser tool 很有用:
- 检查网页 — DOM 结构、computed style、无障碍树
- 调试前端问题 — 网络瀑布图、console 错误、性能 trace
- 抓取结构化数据 — 从渲染后的页面提取内容
- 截取屏幕截图或 PDF — 网页内容的视觉快照
- 分析性能 — Core Web Vitals、JavaScript profiling、内存分析
不需要渲染 DOM 的基本页面 fetch 请改用 fetch()。
Browser tool 需要 Agents SDK 与 @cloudflare/codemode:
npm install agents @cloudflare/codemode ai zod在 wrangler 配置中添加 Browser Run(原 Browser Rendering)与 Worker Loader 绑定(binding):
{
"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,
});若要连接自定义 CDP 端点而非 Browser Run 绑定(binding),请传入 cdpUrl。
将 browser tool 与其他 tool 一并传入。model 可为任意 AI SDK provider——此处使用 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,
},
});两个 tool 均接受含 JavaScript async 箭头函数的 code 参数。Sandbox 按 tool 注入全局变量——browser_search 用 spec,browser_execute 用 cdp。
LLM 使用 browser_search 时,代码通过注入的 spec 对象查询 CDP spec:
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 helper 运行 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 消息 handler 内创建 browser tool,从而获得消息持久化与流式传输:
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从 browser 的/json/protocol端点获取 live CDP 协议并短暂缓存。browser_execute每次调用打开新的 browser session,向 sandbox 代码暴露小型cdp辅助 API,执行完成后关闭 session。- LLM 生成的代码在 Worker sandbox 中运行。CDP 流量保留在 host Worker 中。
在 browser_execute 内,sandbox 代码可使用以下函数。
发送 CDP 命令并等待响应。
| 参数 | 类型 | 描述 |
|---|---|---|
method |
string |
CDP method,例如 "DOM.getDocument" 或 "Network.enable" |
params |
unknown |
方法参数 |
options.timeoutMs |
number |
单条 command 超时(默认:10 秒) |
options.sessionId |
string |
Target session ID(页面级 command 必需) |
attach 到 target 并获取 session ID。使用 flatten: true 的 Target.attachToTarget。
| 参数 | 类型 | 描述 |
|---|---|---|
targetId |
string |
要 attach 的 target |
options.timeoutMs |
number |
attach command 超时 |
返回 sessionId 字符串。
获取最近 CDP debug log 条目(send、receive、error)。默认最后 50 条,最多 400。
清空 debug log 缓冲区。
返回 AI SDK tool(browser_search 与 browser_execute)。
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
browser |
Fetcher |
— | Browser Run 绑定(binding) |
cdpUrl |
string |
— | 自定义 CDP 端点的可选 override |
cdpHeaders |
Record<string, string> |
— | CDP URL 发现用的 header(例如 Cloudflare Access) |
loader |
WorkerLoader |
必需 | sandbox 执行的 Worker Loader 绑定(binding) |
timeout |
number |
30000 |
执行超时(毫秒) |
必须提供 browser 或 cdpUrl 之一。两者均设置时 cdpUrl 优先。
自定义集成时直接 import 构建块:
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 自动 provision browser,因此相同 browser: env.BROWSER 设置在本地与部署后均可用。
仅当有意连接其他 CDP 兼容 browser 端点(如 tunnel 或手动管理的 Chrome 实例)时使用 cdpUrl。
- LLM 生成的代码在隔离的 Worker sandbox 中运行——每次执行获得独立 Worker 实例
- 外部网络访问(
fetch、connect)在 runtime 层于 sandbox 中被阻止 - CDP command 通过 Workers RPC 分发——WebSocket 位于 host,不在 sandbox 中
- CDP spec 保留在服务端——仅查询结果流向 LLM
- 响应截断至约 6,000 token,防止 context window 溢出
- 每次 execute 调用一个 session — 每次
browser_execute调用打开新的 browser session。多步 workflow 必须在单个代码块内完成。 - 无认证 session — browser 启动时不带任何 cookie 或登录 state。
- 需要
@cloudflare/codemode作为 peer dependency(对等依赖)。 - sandbox 中仅支持 JavaScript 执行(不支持 TypeScript 语法)。
若希望以编程方式控制 browser 而不使用 LLM 生成代码,可直接用 Puppeteer 配合 Browser Run API。
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 配置中添加 browser 绑定(binding):
{
"ai": {
"binding": "AI",
},
"browser": {
"binding": "BROWSER",
},
}[ai]
binding = "AI"
[browser]
binding = "BROWSER"也可在 Agent 内直接调用 Browserbase API 使用 Browserbase ↗。
获得 Browserbase API key ↗ 后,通过创建 secret 添加到 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;
}
}