Browser·续1
export class BrowserAssistant extends DurableObject<Env> {
// Browser, harness, and lifecycle as in the Pi Durable example.
async getLiveView() {
const view = await this.browser.liveView({ mode: "tab" });
if (!view) {
return [];
}
return view.targets
.filter((tab) => tab.pageUrl?.startsWith("http"))
.map((tab) => ({
title: tab.title,
pageUrl: tab.pageUrl,
liveViewUrl: tab.url,
}));
}
}
browser.liveView() returns an object with the following fields:
| Field | Description |
|---|---|
sessionId |
The Browser Run session ID. |
targets |
One entry per open tab, with its targetId, its Live View url, and the tab's current pageUrl, title, and type. |
expiresInMs |
How long the Live View URLs remain valid for starting a connection, about five minutes. |
Pass mode: "tab" for an interactive page view, or mode: "devtools" for the full DevTools inspector. Omit mode to use the default view from Browser Run.
browserTool does not pause a run while a person acts. Hand off between turns instead:
- The model reaches a step it cannot complete, such as a login page, and asks the user for help.
- Your application calls
browser.liveView()and shows the link to the user. - The user completes the step in Live View.
- On the next turn, the model continues in the same browser, with the new cookies and login in place.
If the person closes the model's active tab, the model's next result says that the tab was closed.
Caution
Live View URLs are bearer credentials. Anyone with a link can control the browser, including the sites it is logged in to. Show the links only to trusted, authenticated users. Do not give them to the model, and do not store them. Call browser.liveView() again to create fresh links.
Close the browser
Call browser.close() to end the browser. It deletes the Browser Run session and returns false if no browser was open. The next use of the Browser starts a new browser, and each scope that worked in the old browser receives restarted: true.
await this.browser.close();
await this.browser.close();
Recording
Session recording captures a Browser Run session as structured rrweb events: DOM changes, input, and navigation, not video. Use recordings to audit or debug what an agent did in its browser.
To turn on recording, set recording: true in the provider options:
export class BrowserAssistant extends DurableObject {
browser = new Browser({
provider: browserRun(this.env.BROWSER, { recording: true }),
});
}
export class BrowserAssistant extends DurableObject<Env> {
browser = new Browser({
provider: browserRun(this.env.BROWSER, { recording: true }),
});
}
The option applies to every browser the Browser creates, including replacements after a restart. Each Browser Run session has its own recording. A recording is finalized when its session closes, after browser.close() or when the browser is idle for longer than keepAliveMs.
Note the Browser Run session ID while the browser is open, then fetch the recording after the browser closes:
import { getBrowserRecording } from "agents/browser";
// While the browser is open. resolve() starts a browser if none is open.
const { sessionId } = await this.browser.resolve();
// After the browser closes:
const recording = await getBrowserRecording({
accountId: this.env.CF_ACCOUNT_ID,
apiToken: this.env.CF_API_TOKEN,
sessionId,
});
// recording.events holds one rrweb event array per tab, keyed by target ID.
import { getBrowserRecording } from "agents/browser";
// While the browser is open. resolve() starts a browser if none is open.
const { sessionId } = await this.browser.resolve();
// After the browser closes:
const recording = await getBrowserRecording({
accountId: this.env.CF_ACCOUNT_ID,
apiToken: this.env.CF_API_TOKEN,
sessionId,
});
// recording.events holds one rrweb event array per tab, keyed by target ID.
getBrowserRecording() uses the Browser Run REST API, so it needs your account ID and an API token with access to Browser Run. The Workers binding cannot read recordings. Recordings are retained for 30 days and capped at two hours per session.
Caution
A persistent browser's recording spans every turn and every scope that shares the Browser, until that browser closes. If several users share one Browser, one recording can include all of their activity. rrweb masks input fields by default, but treat every recording as sensitive.
CDP API
Model code runs in a sandbox with a cdp object. Methods with arguments take a single object:
| Method | Description |
|---|---|
cdp.send({ method, params?, sessionId?, timeoutMs? }) |
Send a CDP command and return its result. |
cdp.attachToTarget({ targetId, timeoutMs? }) |
Attach to a tab and return { sessionId }, a handle for page-scoped send calls. |
cdp.spec() |
Return the searchable, normalized CDP protocol description from the live browser. |
cdp.getDebugLog({ limit? }) |
Return recent CDP traffic, including sends, receives, and warnings, for this run. |
cdp.clearDebugLog() |
Clear the debug log. |
The cdp object also follows these rules:
sessionId: "active"addresses the scope's active tab.Browser.*andTarget.*commands take nosessionId.cdp.attachToTarget()makes the tab active. Its handle keeps working in later runs.- The model cannot send
Browser.closeorBrowser.crash, because your application owns the browser. To close a tab, the model sendsTarget.closeTarget. - A single
cdp.sendresult over 1 MB fails the run. To capture a long page, take a viewport screenshot or use JPEG.
Read pages without a browser
To read a page or document at a known URL, use the web_fetch tool instead of a browser. It does not start a browser, so each read is a single HTTP request. It also converts PDFs and Office documents to Markdown and splits long pages into parts the model can read in turn.
Use browserTool when the page needs a browser:
- The page renders its content with JavaScript.
- The page needs a login, or the model must click, type, or move through several pages.
- The agent must return to the same page, tabs, or session on a later turn.
- You need a screenshot of the page.
Legacy browser tools
createBrowserTools and createBrowserRuntime from agents/browser/ai are still supported, but they are legacy. The same applies to createBrowserTools from agents/browser/tanstack-ai and to the Think browser tools in @cloudflare/think/tools/browser, which are built on them. New code should use Browser with browserTool.
You might still use the legacy tools for the following:
- Your own browser. Connect to a CDP endpoint you manage with
cdpUrl.Browsersupports only Browser Run's Chromium engine. - Kitesurf. A Kitesurf browser lives only as long as its CDP WebSocket, so it cannot be a persistent browser.
- Approval or human handoff within a run. The durable Code Mode runtime can pause a run for approval and resume it with the browser session intact.
browserTooldoes not pause runs.
You do not need the legacy tools to get a fresh browser for each run. With browserTool, call browser.close() after a turn, or set a short keepAliveMs, so the next run starts a new browser. To read a page without a browser, use the web_fetch tool.
Basic pattern
Create browser tools with the Browser Run and Worker Loader bindings, then pass those tools to your model call. The model writes code against the CDP API in the browser_execute tool.
import { AIChatAgent } from "@cloudflare/ai-chat";
import { createBrowserTools } from "agents/browser/ai";
import { streamText, convertToModelMessages, stepCountIs } from "ai";
import { createAI } from "agents/models/ai-sdk";
export class BrowserAgent extends AIChatAgent {
async onChatMessage() {
const ai = createAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
ctx: this.ctx,
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: ai("@cf/zai-org/glm-4.7-flash"),
system: "You can inspect web pages with browser tools.",
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 { streamText, convertToModelMessages, stepCountIs } from "ai";
import { createAI } from "agents/models/ai-sdk";
export class BrowserAgent extends AIChatAgent<Env> {
async onChatMessage() {
const ai = createAI({ binding: this.env.AI });
const browserTools = createBrowserTools({
ctx: this.ctx,
browser: this.env.BROWSER,
loader: this.env.LOADER,
});
const result = streamText({
model: ai("@cf/zai-org/glm-4.7-flash"),
system: "You can inspect web pages with browser tools.",
messages: await convertToModelMessages(this.messages),
tools: browserTools,
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
}
Browser tools must be created from inside a Durable Object, such as an Agent. The durable runtime facet and the session store live on its ctx. The helper exposes one durable CDP tool plus stateless Quick Action tools when a browser binding is present:
| Tool | Description |
|---|---|
browser_execute |
Run sandboxed code against a live browser over CDP — screenshots, DOM reads, JavaScript evaluation, and more. |
browser_markdown |
Read a page or raw HTML as Markdown. |
browser_extract |
Extract structured data from a page with AI. |
browser_links |
List links on a page. |
browser_scrape |
Scrape specific elements by CSS selector. |
The TanStack AI createBrowserTools returns only browser_execute. To discover protocol surface, the model calls cdp.spec() or the runtime's built-in codemode.search() and codemode.describe().
Configuration
The legacy tools use the same BROWSER and LOADER bindings and the same CodemodeRuntime export as browserTool. Refer to Use it with Pi Durable for the Wrangler configuration. The @cloudflare/codemode/vite plugin adds the CodemodeRuntime export automatically.
createBrowserTools and createBrowserRuntime accept the following options:
| Option | Default | Description |
|---|---|---|
browser |
— | Browser Run binding. |
cdpHeaders |
— | Headers for CDP URL discovery, such as Cloudflare Access headers. |
cdpUrl |
— | A custom CDP endpoint. Takes priority over browser when both are set. |
ctx |
The current Agent's state | The Durable Object that hosts the runtime facet and session store. |
loader |
Required | Worker Loader binding for sandboxed execution. |
name |
"browser" |
Runtime name, the durable identity of executions. |
quickActions |
On with a browser binding |
Quick Action tools to include. Pass false or { actions, maxChars, options }. |
session |
{ mode: "one-shot" } |
Session lifecycle mode, recording, and engine. |
store |
Durable Object storage | Durable store for Browser Run session IDs. |
timeout |
30000 |
Execution timeout in milliseconds, also used as the per-command CDP timeout. |
