Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

浏览网页

通过 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 每次调用都打开一个新的浏览器会话,向沙箱代码暴露一个小型的 cdp helper API,执行结束时关闭会话。
  • LLM 生成的代码运行在 Worker 沙箱中。CDP 流量保留在宿主 Worker 中。

CDP helper API

browser_execute 内部,沙箱代码可以使用以下函数。

cdp.send(method, params?, options?)

发送一个 CDP 命令并等待响应。

参数类型描述
methodstringCDP 方法,例如 “DOM.getDocument” 或 “Network.enable”
paramsunknown方法参数
options.timeoutMsnumber单条命令的超时时间(默认:10 秒)
options.sessionIdstring目标会话 ID(页面级命令必填)

cdp.attachToTarget(targetId, options?)

附加到一个 target 并获取会话 ID。使用 Target.attachToTarget 并设置 flatten: true

参数类型描述
targetIdstring要附加到的 target
options.timeoutMsnumber附加命令的超时时间

返回 sessionId 字符串。

cdp.getDebugLog(limit?)

获取最近的 CDP 调试日志条目(发送、接收、错误)。默认最近 50 条,最多 400 条。

cdp.clearDebugLog()

清空调试日志缓冲区。

配置

createBrowserTools(options)

返回 AI SDK 工具(browser_searchbrowser_execute)。

选项类型默认值描述
browserFetcherBrowser Run 绑定
cdpUrlstring自定义 CDP 端点的可选覆盖
cdpHeadersRecord<string, string>用于 CDP URL 发现的请求头(例如 Cloudflare Access)
loaderWorkerLoaderrequired用于沙箱执行的 Worker Loader 绑定
timeoutnumber30000执行超时时间(毫秒)

browsercdpUrl 至少要提供其一。两个都设置时,以 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 实例
  • 沙箱在运行时层面屏蔽外部网络访问(fetchconnect)
  • 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;

  }

}