浏览网页
通过 Browser Run 工具,让你的 Agent 获得完整的 Chrome DevTools Protocol (CDP) 访问能力。Beta
LLM 不再受限于固定的浏览器操作集(点击、截屏、导航),而是可以编写 JavaScript 代码,针对真实的浏览器会话运行 CDP 命令——访问该协议中所有的 domain、命令、事件和类型。
提供了两个工具:
| 工具 | 描述 |
|---|---|
| browser_search | 查询 CDP 规范以发现命令、事件和类型。规范从浏览器的 CDP 端点动态获取并缓存。 |
| browser_execute | 通过 cdp helper 针对真实浏览器运行 CDP 命令。每次调用都会打开一个新的浏览器会话,执行代码,然后关闭它。 |
何时使用浏览器工具
当你的 Agent 需要做以下事情时,浏览器工具会很有用:
- 检查网页 — DOM 结构、计算样式、可访问性树
- 调试前端问题 — 网络瀑布、控制台错误、性能跟踪
- 抓取结构化数据 — 从渲染后的页面提取内容
- 截屏或生成 PDF — 网页内容的视觉快照
- 性能分析 — Core Web Vitals、JavaScript 性能分析、内存分析
对于不需要渲染 DOM 的基础页面抓取,请改用 fetch()。
安装
浏览器工具需要 Agents SDK 和 @cloudflare/codemode:
Terminal window
npm install agents @cloudflare/codemode ai zod
快速开始
1. 配置绑定
将 Browser Run(原 Browser Rendering)和 Worker Loader 绑定添加到你的 wrangler 配置中:
JSONC
{
"compatibility_flags": ["nodejs_compat"],
"browser": {
"binding": "BROWSER",
},
"worker_loaders": [
{
"binding": "LOADER",
},
],
}
TOML
compatibility_flags = [ "nodejs_compat" ]
[browser]
binding = "BROWSER"
[[worker_loaders]]
binding = "LOADER"
2. 创建浏览器工具
JavaScript
import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
TypeScript
import { createBrowserTools } from "agents/browser/ai";
const browserTools = createBrowserTools({
browser: env.BROWSER,
loader: env.LOADER,
});
要连接到自定义 CDP 端点而不是 Browser Run 绑定,请传入 cdpUrl。
3. 与 streamText 配合使用
将浏览器工具与其他工具一并传入。model 可以是任何 AI SDK 提供方——这里使用 Workers AI:
JavaScript
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,
},
});
TypeScript
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,
},
});
两个工具都接受一个 code 参数,内容是一个 JavaScript 异步箭头函数。沙箱会根据工具注入不同的全局变量——browser_search 注入 spec,browser_execute 注入 cdp。
当 LLM 使用 browser_search 时,代码通过注入的 spec 对象查询 CDP 规范:
JavaScript
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 命令:
JavaScript
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;
};
与 Agent 配合使用
典型用法是在 AIChatAgent 的消息处理函数中创建浏览器工具,这样可以获得消息持久化和流式传输能力:
JavaScript
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();
}
}
TypeScript
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,使用 /tanstack-ai 导出:
JavaScript
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,
});
TypeScript
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每次调用都打开一个新的浏览器会话,向沙箱代码暴露一个小型的cdphelper API,执行结束时关闭会话。- LLM 生成的代码运行在 Worker 沙箱中。CDP 流量保留在宿主 Worker 中。
CDP helper API
在 browser_execute 内部,沙箱代码可以使用以下函数。
cdp.send(method, params?, options?)
发送一个 CDP 命令并等待响应。
| 参数 | 类型 | 描述 |
|---|---|---|
| method | string | CDP 方法,例如 “DOM.getDocument” 或 “Network.enable” |
| params | unknown | 方法参数 |
| options.timeoutMs | number | 单条命令的超时时间(默认:10 秒) |
| options.sessionId | string | 目标会话 ID(页面级命令必填) |
cdp.attachToTarget(targetId, options?)
附加到一个 target 并获取会话 ID。使用 Target.attachToTarget 并设置 flatten: true。
| 参数 | 类型 | 描述 |
|---|---|---|
| targetId | string | 要附加到的 target |
| options.timeoutMs | number | 附加命令的超时时间 |
返回 sessionId 字符串。
cdp.getDebugLog(limit?)
获取最近的 CDP 调试日志条目(发送、接收、错误)。默认最近 50 条,最多 400 条。
cdp.clearDebugLog()
清空调试日志缓冲区。
配置
createBrowserTools(options)
返回 AI SDK 工具(browser_search 和 browser_execute)。
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| browser | Fetcher | — | Browser Run 绑定 |
| cdpUrl | string | — | 自定义 CDP 端点的可选覆盖 |
| cdpHeaders | Record<string, string> | — | 用于 CDP URL 发现的请求头(例如 Cloudflare Access) |
| loader | WorkerLoader | required | 用于沙箱执行的 Worker Loader 绑定 |
| timeout | number | 30000 | 执行超时时间(毫秒) |
browser 和 cdpUrl 至少要提供其一。两个都设置时,以 cdpUrl 为准。
原始访问
对于自定义集成,可以直接导入构件:
JavaScript
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();
TypeScript
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 设置在本地和部署时都能工作。
只在你确实想连接到其他 CDP 兼容的浏览器端点(例如隧道或手动管理的 Chrome 实例)时才使用 cdpUrl。
安全考量
- LLM 生成的代码运行在隔离的 Worker 沙箱中——每次执行都会获得自己的 Worker 实例
- 沙箱在运行时层面屏蔽外部网络访问(
fetch、connect) - CDP 命令通过 Workers RPC 派发——WebSocket 位于宿主中,而不是沙箱中
- CDP 规范保留在服务器上——只有查询结果会流向 LLM
- 响应被截断到约 6,000 个 token,以防止上下文窗口溢出
当前限制
- 每次 execute 调用一个会话 — 每次
browser_execute调用都会打开一个新的浏览器会话。多步工作流必须在单个代码块中完成。 - 没有已认证的会话 — 浏览器启动时不带任何 cookie 或登录状态。
- 需要将
@cloudflare/codemode作为 peer dependency。 - 沙箱中仅支持 JavaScript 执行(不支持 TypeScript 语法)。
直接使用 Puppeteer
如果你更愿意通过编程方式控制浏览器,而不依赖 LLM 生成的代码,可以直接通过 Browser Run API 使用 Puppeteer。
npm yarn pnpm bun
npm i -D @cloudflare/puppeteer
yarn add -D @cloudflare/puppeteer
pnpm add -D @cloudflare/puppeteer
bun add -d @cloudflare/puppeteer
JavaScript
import 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;
}
}
TypeScript
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 配置中:
JSONC
{
"ai": {
"binding": "AI",
},
"browser": {
"binding": "BROWSER",
},
}
TOML
[ai]
binding = "AI"
[browser]
binding = "BROWSER"
使用 Browserbase
你也可以通过在 Agent 内部直接调用 Browserbase API 来使用 Browserbase ↗。
获得 Browserbase API key ↗ 后,可以通过创建密钥将其添加到你的 Agent:
Terminal window
cd your-agent-project-folder
npx wrangler@latest secret put BROWSERBASE_API_KEY
安装 @cloudflare/puppeteer 包,在 Agent 中使用它来调用 Browserbase API:
npm yarn pnpm bun
npm i @cloudflare/puppeteer
yarn add @cloudflare/puppeteer
pnpm add @cloudflare/puppeteer
bun add @cloudflare/puppeteer
JavaScript
import 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;
}
}
TypeScript
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;
}
}