agents 0.16.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +587 -137
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +1918 -72
  26. package/dist/chat/index.js +1730 -245
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  41. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  42. package/dist/email.js +1 -1
  43. package/dist/email.js.map +1 -1
  44. package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
  45. package/dist/index.d.ts +91 -71
  46. package/dist/index.js +562 -24
  47. package/dist/index.js.map +1 -1
  48. package/dist/mcp/client.d.ts +18 -14
  49. package/dist/mcp/client.js +1 -1
  50. package/dist/mcp/index.d.ts +30 -30
  51. package/dist/mcp/index.js +7 -7
  52. package/dist/mcp/index.js.map +1 -1
  53. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  54. package/dist/message-builder-BymO4N_D.js.map +1 -0
  55. package/dist/observability/index.d.ts +1 -1
  56. package/dist/observability/index.js +4 -2
  57. package/dist/observability/index.js.map +1 -1
  58. package/dist/react.d.ts +123 -110
  59. package/dist/react.js +44 -11
  60. package/dist/react.js.map +1 -1
  61. package/dist/serializable.d.ts +1 -1
  62. package/dist/skills/compile.js +1 -1
  63. package/dist/skills/compile.js.map +1 -1
  64. package/dist/skills/index.js +5 -5
  65. package/dist/skills/index.js.map +1 -1
  66. package/dist/sub-routing.d.ts +6 -6
  67. package/dist/utils.js +1 -1
  68. package/dist/utils.js.map +1 -1
  69. package/dist/vite.js +5 -5
  70. package/dist/vite.js.map +1 -1
  71. package/dist/wire-types-nflOzNuU.js +240 -0
  72. package/dist/wire-types-nflOzNuU.js.map +1 -0
  73. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  74. package/dist/workflow-types.d.ts +25 -21
  75. package/dist/workflow-types.js.map +1 -1
  76. package/dist/workflows.d.ts +22 -21
  77. package/dist/workflows.js +31 -7
  78. package/dist/workflows.js.map +1 -1
  79. package/docs/adding-to-existing-project.md +450 -0
  80. package/docs/agent-class.md +503 -0
  81. package/docs/agent-tools.md +552 -0
  82. package/docs/browse-the-web.md +430 -0
  83. package/docs/callable-methods.md +627 -0
  84. package/docs/chat-agents.md +1687 -0
  85. package/docs/chat-sdk.md +181 -0
  86. package/docs/client-sdk.md +520 -0
  87. package/docs/client-tools-continuation.md +177 -0
  88. package/docs/codemode.md +440 -0
  89. package/docs/configuration.md +775 -0
  90. package/docs/cross-domain-authentication.md +171 -0
  91. package/docs/durable-execution.md +537 -0
  92. package/docs/email.md +663 -0
  93. package/docs/get-current-agent.md +204 -0
  94. package/docs/getting-started.md +305 -0
  95. package/docs/http-websockets.md +668 -0
  96. package/docs/human-in-the-loop.md +661 -0
  97. package/docs/index.md +151 -0
  98. package/docs/long-running-agents.md +730 -0
  99. package/docs/mcp-client.md +620 -0
  100. package/docs/mcp-servers.md +526 -0
  101. package/docs/mcp-transports.md +308 -0
  102. package/docs/migration-to-ai-sdk-v5.md +96 -0
  103. package/docs/migration-to-ai-sdk-v6.md +163 -0
  104. package/docs/observability.md +261 -0
  105. package/docs/push-notifications.md +367 -0
  106. package/docs/queue.md +329 -0
  107. package/docs/readonly-connections.md +278 -0
  108. package/docs/resumable-streaming.md +127 -0
  109. package/docs/retries.md +444 -0
  110. package/docs/routing.md +749 -0
  111. package/docs/scheduling.md +898 -0
  112. package/docs/securing-mcp-servers.md +359 -0
  113. package/docs/server-driven-messages.md +477 -0
  114. package/docs/sessions.md +1024 -0
  115. package/docs/state.md +512 -0
  116. package/docs/sub-agents.md +389 -0
  117. package/docs/webhooks.md +604 -0
  118. package/docs/workflows.md +877 -0
  119. package/package.json +41 -14
  120. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  121. package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
  122. package/dist/client-BXJ9n2f7.js.map +0 -1
@@ -0,0 +1,430 @@
1
+ # Browse the Web (Experimental)
2
+
3
+ Browser tools give your agents full access to the Chrome DevTools Protocol (CDP) through the code mode pattern. Instead of a fixed set of browser actions (click, screenshot, navigate), the LLM writes code that runs CDP commands against a live browser session — accessing all domains, commands, events, and types in the protocol.
4
+
5
+ One durable tool is provided:
6
+
7
+ - **`browser_execute`** — run sandboxed code against a live browser via the `cdp` connector. Executions are recorded on a durable codemode runtime (abort-and-replay), so a run can pause for approval and resume with its browser session intact.
8
+
9
+ > **Experimental** — this feature may have breaking changes in future releases.
10
+
11
+ ## When to use browser tools
12
+
13
+ Browser tools are useful when your agent needs to:
14
+
15
+ - **Inspect web pages** — DOM structure, computed styles, accessibility tree
16
+ - **Debug frontend issues** — network waterfalls, console errors, performance traces
17
+ - **Scrape structured data** — extract content from rendered pages
18
+ - **Capture screenshots or PDFs** — visual snapshots of web content
19
+ - **Profile performance** — Core Web Vitals, JavaScript profiling, memory analysis
20
+
21
+ For simple page fetches where you do not need a full browser, `fetch()` is simpler.
22
+
23
+ ## Installation
24
+
25
+ Browser tools require the Agents SDK and `@cloudflare/codemode`:
26
+
27
+ ```sh
28
+ npm install agents @cloudflare/codemode ai zod
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### 1. Configure bindings
34
+
35
+ Add the Browser Rendering and Worker Loader bindings to your `wrangler.jsonc`:
36
+
37
+ ```jsonc
38
+ // wrangler.jsonc
39
+ {
40
+ "browser": { "binding": "BROWSER" },
41
+ "worker_loaders": [{ "binding": "LOADER" }],
42
+ "compatibility_flags": ["nodejs_compat"]
43
+ }
44
+ ```
45
+
46
+ ### 2. Export the runtime class
47
+
48
+ The durable runtime behind the tool lives in a Durable Object facet, so your worker entry must export it (the `@cloudflare/codemode/vite` plugin does this automatically):
49
+
50
+ ```ts
51
+ export { CodemodeRuntime } from "agents/browser";
52
+ ```
53
+
54
+ ### 3. Create browser tools
55
+
56
+ Browser tools must be created from inside a Durable Object (e.g. an Agent) — the runtime facet and the session store live on its `ctx`:
57
+
58
+ ```ts
59
+ import { createBrowserTools } from "agents/browser/ai";
60
+
61
+ const browserTools = createBrowserTools({
62
+ ctx: this.ctx,
63
+ browser: this.env.BROWSER,
64
+ loader: this.env.LOADER
65
+ });
66
+ ```
67
+
68
+ If you need to connect to a custom CDP endpoint instead of the Browser Rendering binding, pass `cdpUrl`.
69
+
70
+ ### 4. Use with streamText
71
+
72
+ Pass browser tools alongside your other tools:
73
+
74
+ ```ts
75
+ import { streamText } from "ai";
76
+
77
+ const result = streamText({
78
+ model,
79
+ system: "You are a helpful assistant that can inspect web pages.",
80
+ messages,
81
+ tools: {
82
+ ...browserTools,
83
+ ...otherTools
84
+ }
85
+ });
86
+ ```
87
+
88
+ When the LLM uses `browser_execute`, the `code` field is an async arrow function. Connector methods take a single object argument:
89
+
90
+ ```javascript
91
+ async () => {
92
+ const { targetId } = await cdp.send({
93
+ method: "Target.createTarget",
94
+ params: { url: "https://example.com" }
95
+ });
96
+ const { sessionId } = await cdp.attachToTarget({ targetId });
97
+ const { root } = await cdp.send({ method: "DOM.getDocument", sessionId });
98
+ const { outerHTML } = await cdp.send({
99
+ method: "DOM.getOuterHTML",
100
+ params: { nodeId: root.nodeId },
101
+ sessionId
102
+ });
103
+ await cdp.send({ method: "Target.closeTarget", params: { targetId } });
104
+ return outerHTML;
105
+ };
106
+ ```
107
+
108
+ To discover protocol surface, the model calls `cdp.spec()` — the live, normalized CDP protocol description (domains with commands, events, and types) — or uses the runtime's built-in `codemode.search` / `codemode.describe`.
109
+
110
+ ## Use with an Agent
111
+
112
+ The typical pattern is to create browser tools inside the agent's message handler:
113
+
114
+ ```ts
115
+ import { Agent } from "agents";
116
+ import { createBrowserTools } from "agents/browser/ai";
117
+ import { streamText, convertToModelMessages, stepCountIs } from "ai";
118
+
119
+ export class MyAgent extends Agent<Env> {
120
+ async onChatMessage() {
121
+ const browserTools = createBrowserTools({
122
+ ctx: this.ctx,
123
+ browser: this.env.BROWSER,
124
+ loader: this.env.LOADER
125
+ });
126
+
127
+ const result = streamText({
128
+ model,
129
+ system: "You can browse the web and inspect pages.",
130
+ messages: await convertToModelMessages(this.messages),
131
+ tools: {
132
+ ...browserTools,
133
+ ...this.mcp.getAITools()
134
+ },
135
+ stopWhen: stepCountIs(10)
136
+ });
137
+
138
+ return result.toUIMessageStreamResponse();
139
+ }
140
+ }
141
+ ```
142
+
143
+ > Using `@cloudflare/think`? The unified execute tool (`createExecuteTool(this)`) already includes `cdp.*` alongside `state.*` and `tools.*` when `env.BROWSER` is bound. See the [Think tools documentation](https://github.com/cloudflare/agents/blob/main/docs/think/tools.md).
144
+
145
+ ## TanStack AI
146
+
147
+ For TanStack AI, use the `/tanstack-ai` export:
148
+
149
+ ```ts
150
+ import { createBrowserTools } from "agents/browser/tanstack-ai";
151
+ import { chat } from "@tanstack/ai";
152
+
153
+ const browserTools = createBrowserTools({
154
+ ctx: this.ctx,
155
+ browser: env.BROWSER,
156
+ loader: env.LOADER
157
+ });
158
+
159
+ const stream = chat({
160
+ adapter: openaiText("gpt-4o"),
161
+ tools: [...browserTools, ...otherTools],
162
+ messages
163
+ });
164
+ ```
165
+
166
+ ## Session lifecycle
167
+
168
+ By default each execution gets a fresh browser session, torn down when the run ends (`one-shot`). Two more modes via the `session` option:
169
+
170
+ ```ts
171
+ createBrowserTools({
172
+ ctx: this.ctx,
173
+ browser: this.env.BROWSER,
174
+ loader: this.env.LOADER,
175
+ session: { mode: "dynamic" } // or { mode: "reuse", key: "main" }
176
+ });
177
+ ```
178
+
179
+ - **`one-shot`** (default) — fresh session per execution; deterministic cleanup when the execution reaches a terminal status.
180
+ - **`reuse`** — a named shared session that persists across executions until explicitly closed or swept.
181
+ - **`dynamic`** — starts one-shot; the model can promote the session with `cdp.startSession()` (e.g. after logging in to a page) so later executions continue in the same browser.
182
+
183
+ In `reuse`/`dynamic` modes the sandbox additionally gets `cdp.startSession()`, `cdp.sessionInfo()`, `cdp.closeSession()`, and `cdp.resetSession()`.
184
+
185
+ Sessions are tracked durably (in the DO's storage), so they survive hibernation and approval pauses — a run that pauses for human approval resumes with its browser session, tabs, and cookies intact. If Browser Rendering expires the session while a pause waits, the resume surfaces a clear error and the model starts over.
186
+
187
+ ### Host-side management and cleanup
188
+
189
+ `createBrowserRuntime` returns the moving parts for host-side wiring:
190
+
191
+ ```ts
192
+ import { createBrowserRuntime } from "agents/browser/ai";
193
+
194
+ const { runtime, connector, tools } = createBrowserRuntime({
195
+ ctx: this.ctx,
196
+ browser: this.env.BROWSER,
197
+ loader: this.env.LOADER,
198
+ session: { mode: "dynamic" }
199
+ });
200
+
201
+ await connector.sessionInfo(); // shared session id + open targets
202
+ await connector.liveView(); // Live View URLs for the shared session's tabs
203
+ await connector.closeSession(); // close the shared session
204
+ await connector.sweep(); // reclaim expired/stale sessions — call from a scheduled task
205
+ await runtime.expirePaused(); // reject stale never-approved pauses, freeing their sessions
206
+ ```
207
+
208
+ ## Quick Actions (stateless browsing)
209
+
210
+ `browser_execute` drives a full, stateful CDP session — the right tool for interactive, multi-step automation. But a lot of agent browsing is really one-shot: _read this page as Markdown_, _extract these fields_, _list the links_. For those, [Quick Actions](https://developers.cloudflare.com/browser-run/quick-actions/) are simpler, faster, and cheaper. They need only the `browser` binding — no Durable Object, Worker Loader, or sandbox — so they work from any Worker.
211
+
212
+ ```ts
213
+ import { createQuickActionTools } from "agents/browser/ai";
214
+
215
+ const tools = createQuickActionTools({ browser: this.env.BROWSER });
216
+ // browser_markdown, browser_extract, browser_links, browser_scrape
217
+ const result = await generateText({ model, tools, messages });
218
+ ```
219
+
220
+ By default you get the text-returning, model-friendly tools; `browser_content` (raw HTML) is opt-in via `actions`.
221
+
222
+ **Context safety.** Every result is bounded to roughly `maxChars` (default 50000) so a single browse cannot blow the context window, while preserving each result's shape so the model sees a consistent type: text (markdown/content) is truncated to a string, oversized link/scrape arrays are trimmed but stay arrays, and only an opaque oversized object (e.g. a sprawling `extract`) degrades to a `{ truncated, note, preview }` summary. Set `maxChars: 0` to disable.
223
+
224
+ **Authenticated and JavaScript-heavy pages.** The model only ever supplies the page (`url`/`html`) and action-specific fields. Host-supplied options — `cookies`, `authenticate`, `setExtraHTTPHeaders` for protected pages, or `gotoOptions` / `viewport` for pages that need to settle — are passed once via `options` and merged into every request:
225
+
226
+ ```ts
227
+ const tools = createQuickActionTools({
228
+ browser: this.env.BROWSER,
229
+ actions: ["markdown", "extract", "links"],
230
+ maxChars: 20_000,
231
+ options: {
232
+ authenticate: { username: "user", password: env.SITE_PASSWORD },
233
+ gotoOptions: { waitUntil: "networkidle0" }
234
+ }
235
+ });
236
+ ```
237
+
238
+ You can also call the primitives directly:
239
+
240
+ ```ts
241
+ import { browserMarkdown, browserExtract } from "agents/browser";
242
+
243
+ const md = await browserMarkdown(this.env.BROWSER, { url });
244
+ const data = await browserExtract<{ price: number }>(this.env.BROWSER, {
245
+ url,
246
+ prompt: "the product price",
247
+ response_format: { type: "json_schema", schema: priceSchema }
248
+ });
249
+ ```
250
+
251
+ For an endpoint or option not yet wrapped, `runQuickAction(browser, action, params)` returns the raw `Response`; its `params` are typed against the action (`"json"` expects an extract input, `"scrape"` expects `elements`, and so on).
252
+
253
+ **Using them with `browser_execute`.** Quick Actions and the durable CDP tool share the same `BROWSER` binding and complement each other — one-shot reads versus interactive sessions. `createBrowserTools` / `createBrowserRuntime` expose **both by default** whenever a `browser` binding is present, and resolve `ctx` from the current Agent (via `getCurrentAgent()`) so you can skip threading `this.ctx`:
254
+
255
+ ```ts
256
+ // Inside an Agent method — ctx is picked up automatically:
257
+ const tools = createBrowserTools({
258
+ browser: this.env.BROWSER,
259
+ loader: this.env.LOADER
260
+ });
261
+ // browser_execute + browser_markdown + browser_extract + browser_links + browser_scrape
262
+
263
+ // Configure or disable the Quick Action half:
264
+ createBrowserTools({ browser, loader, quickActions: { maxChars: 20_000 } });
265
+ createBrowserTools({ browser, loader, quickActions: false });
266
+ ```
267
+
268
+ When only `cdpUrl` is set (no binding), the Quick Action tools are skipped silently.
269
+
270
+ **Using them with `@cloudflare/think`.** Quick Action tools are an ordinary `ToolSet`, so a Think agent exposes them by spreading them from `getTools()`:
271
+
272
+ ```ts
273
+ class Researcher extends Think<Env> {
274
+ getTools() {
275
+ return { ...createQuickActionTools({ browser: this.env.BROWSER }) };
276
+ }
277
+ }
278
+ ```
279
+
280
+ Quick Actions require a Worker `compatibility_date` of `2026-03-24` or later and `remote: true` on the browser binding for local `wrangler dev`.
281
+
282
+ ## Live View and human-in-the-loop
283
+
284
+ [Live View](https://developers.cloudflare.com/browser-run/features/live-view/) lets a human open a URL and watch — or take control of — a running browser session in real time. It is the building block for human-in-the-loop steps such as logging in, solving a CAPTCHA, completing MFA, or entering data you do not want to pass through an automation script.
285
+
286
+ Because the codemode runtime can already pause a run for approval _with the browser session intact_, a handoff is a four-step pattern:
287
+
288
+ 1. The model calls `cdp.getLiveViewUrl()` to get a link to the current tab.
289
+ 2. It surfaces the link to the user (for example by returning it, writing it to state, or sending it via Slack or email).
290
+ 3. It makes an approval-gated call, so the run pauses durably while the human acts in the live browser.
291
+ 4. After approval, the run resumes against the same session — cookies and login state intact.
292
+
293
+ ```javascript
294
+ async () => {
295
+ const { targetId } = await cdp.send({
296
+ method: "Target.createTarget",
297
+ params: { url: "https://example.com/login" }
298
+ });
299
+
300
+ // A link the user opens to log in themselves.
301
+ const { url } = await cdp.getLiveViewUrl({ targetId, mode: "tab" });
302
+ return { needsHumanLogin: url };
303
+ };
304
+ ```
305
+
306
+ Pass `mode: "tab"` for a standalone interactive page view (best for a handoff) or `mode: "devtools"` for the full DevTools inspector panel. The URL is valid for about five minutes; call again for a fresh one.
307
+
308
+ From the host side, `connector.liveView()` returns the shared (`reuse`/`dynamic`) session's tabs and their Live View URLs, so an agent can render a "take over this session" link in its own UI without entering the sandbox. Each tab also carries its current `pageUrl`, so a UI can label tabs and skip blank or internal (`about:blank`, `chrome://`) pages.
309
+
310
+ ## Session recording
311
+
312
+ Where Live View lets you watch a session _live_, [session recording](https://developers.cloudflare.com/browser-run/features/session-recording/) captures everything the agent did so you can review it _afterward_ — an [rrweb](https://github.com/rrweb-io/rrweb) capture of DOM changes, input, and navigation (structured JSON, not video). It is the natural audit trail for an autonomous browser run.
313
+
314
+ Opt in per session via the `session` option; sessions this connector creates then record until they close:
315
+
316
+ ```ts
317
+ const { connector, tools } = createBrowserRuntime({
318
+ ctx: this.ctx,
319
+ browser: this.env.BROWSER,
320
+ loader: this.env.LOADER,
321
+ session: { mode: "reuse", key: "main", recording: true }
322
+ });
323
+ ```
324
+
325
+ A recording is only finalized once the session closes (an explicit `connector.closeSession()`, idle `keep_alive` expiry, or `connector.sweep()`), so capture the session id while the session is alive and fetch the recording later:
326
+
327
+ ```ts
328
+ import { getBrowserRecording } from "agents/browser";
329
+
330
+ const { sessionId } = (await connector.sessionInfo()) ?? {};
331
+ // ...later, after the session has closed...
332
+ const recording = await getBrowserRecording({
333
+ accountId: this.env.CF_ACCOUNT_ID,
334
+ apiToken: this.env.CF_API_TOKEN,
335
+ sessionId
336
+ });
337
+ // recording.events is keyed by CDP target (one rrweb event array per tab),
338
+ // ready to hand to rrweb-player.
339
+ ```
340
+
341
+ Retrieval goes through the Browser Rendering REST API, so it needs an account id and an API token with `Browser Rendering` read access (the Workers binding cannot read recordings). Recordings are retained for 30 days and capped at 2 hours per session.
342
+
343
+ Be deliberate with recording on shared (`reuse`/`dynamic`) sessions: the recording spans the session's _entire_ lifetime — every turn, and every user that shares the session `key` — until it closes. rrweb masks input fields by default, but treat a recording as potentially sensitive and scope the session `key` accordingly.
344
+
345
+ ## Execution model
346
+
347
+ - LLM-generated code runs in a Worker sandbox; the CDP WebSocket and the browser session stay in the host worker.
348
+ - Every `cdp.*` call is recorded in the runtime's durable log. If a run pauses (approval) or the sandbox aborts, resuming replays the log and continues — which is why connector calls must be sequential and deterministic (wrap nondeterministic non-connector work in `codemode.step`).
349
+ - `cdp.attachToTarget` returns `{ sessionId }` where the id is a **stable session handle** (not a raw CDP session id), so handles stay valid across pause/resume reconnects.
350
+ - The protocol spec is fetched from the live browser, normalized, and cached per binding.
351
+
352
+ ## CDP connector API
353
+
354
+ Inside `browser_execute`, the `cdp` namespace provides (all methods take one object argument):
355
+
356
+ | Method | Description |
357
+ | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
358
+ | `cdp.send({ method, params?, sessionId?, timeoutMs? })` | Send a CDP command and wait for the response |
359
+ | `cdp.attachToTarget({ targetId, timeoutMs? })` | Attach to a target; returns `{ sessionId }` for page-scoped `send` calls |
360
+ | `cdp.spec()` | The searchable, normalized CDP protocol spec |
361
+ | `cdp.getDebugLog({ limit? })` | Recent CDP traffic (sends, receives, warnings) for this execution's connection |
362
+ | `cdp.clearDebugLog()` | Clear the debug log buffer |
363
+ | `cdp.getLiveViewUrl({ targetId?, mode? })` | A Live View URL a human can open to watch/control the session in real time |
364
+ | `cdp.startSession()` _(reuse/dynamic)_ | Promote/ensure the shared session; returns its info |
365
+ | `cdp.sessionInfo()` _(reuse/dynamic)_ | Shared session info, or `null` |
366
+ | `cdp.closeSession()` _(reuse/dynamic)_ | Close the shared session |
367
+ | `cdp.resetSession()` _(reuse/dynamic)_ | Close and replace the shared session |
368
+
369
+ ## Configuration
370
+
371
+ ### `createBrowserTools(options)` / `createBrowserRuntime(options)`
372
+
373
+ | Option | Type | Default | Description |
374
+ | ------------ | -------------------------------- | ---------- | ---------------------------------------------------------- |
375
+ | `ctx` | `DurableObjectState` | required | The DO hosting the runtime facet and session store |
376
+ | `browser` | `BrowserBinding` | — | Browser Rendering binding |
377
+ | `cdpUrl` | `string` | — | Optional override for a custom CDP endpoint |
378
+ | `cdpHeaders` | `Record<string, string>` | — | Headers for CDP URL discovery (e.g. Cloudflare Access) |
379
+ | `loader` | `WorkerLoader` | required | Worker Loader binding for sandboxed execution |
380
+ | `session` | `BrowserConnectorSessionOptions` | `one-shot` | Session lifecycle mode |
381
+ | `store` | `BrowserSessionStore` | DO storage | Durable store for session ids |
382
+ | `timeout` | `number` | `30000` | Execution timeout (also the per-CDP-command timeout) |
383
+ | `name` | `string` | `browser` | Runtime name — the durable identity of executions/snippets |
384
+
385
+ Either `browser` or `cdpUrl` must be provided. When both are set, `cdpUrl` takes priority.
386
+
387
+ ### Raw access
388
+
389
+ For custom integrations, import the building blocks directly:
390
+
391
+ ```ts
392
+ import { BrowserConnector, CdpSession, connectUrl } from "agents/browser";
393
+
394
+ // Connect to a custom CDP endpoint
395
+ const session = await connectUrl("http://localhost:9222");
396
+ const version = await session.send("Browser.getVersion");
397
+ session.close();
398
+
399
+ // Or plug the connector into your own codemode runtime
400
+ const connector = new BrowserConnector(this.ctx, {
401
+ browser: this.env.BROWSER,
402
+ store,
403
+ session: { mode: "dynamic" }
404
+ });
405
+ ```
406
+
407
+ ## Local development
408
+
409
+ Recent Wrangler releases support Browser Rendering in local development. `npx wrangler dev` provisions the browser automatically, so the same `browser: env.BROWSER` setup works locally and when deployed.
410
+
411
+ Use `cdpUrl` only when you intentionally want to connect to some other CDP-compatible browser endpoint, such as a tunnel or a manually managed Chrome instance.
412
+
413
+ ## Security considerations
414
+
415
+ - LLM-generated code runs in **isolated Worker sandboxes** — each execution gets its own Worker instance
416
+ - External network access (`fetch`, `connect`) is **blocked** in the sandbox at the runtime level
417
+ - CDP commands are dispatched via Workers RPC — the WebSocket lives in the host, not the sandbox
418
+ - The CDP spec stays on the server — only query results flow to the LLM
419
+ - Completed results are truncated to approximately 6,000 tokens to prevent context window overflow; the full result is kept on the execution record
420
+
421
+ ## Current limitations
422
+
423
+ - **Sequential connector calls** — the durable replay log requires deterministic ordering, so model code must not `Promise.all` CDP calls (the tool instructions enforce this).
424
+ - **Local development depends on Wrangler support** — if Browser Rendering local mode is unavailable in your environment, upgrade Wrangler or provide `cdpUrl` explicitly. The local simulator also differs from production in places (e.g. session DELETE is a no-op).
425
+ - **No authenticated sessions out of the box** — the browser starts without cookies or login state, but with `dynamic`/`reuse` modes a logged-in session can be kept alive across executions.
426
+ - Requires `@cloudflare/codemode` as a peer dependency
427
+
428
+ ## Example
429
+
430
+ See [`examples/ai-chat/`](https://github.com/cloudflare/agents/tree/main/examples/ai-chat) for a working example that combines browser tools with other AI SDK tools, MCP servers, and tool approval, and [`examples/codemode-connectors/`](https://github.com/cloudflare/agents/tree/main/examples/codemode-connectors) for the connector playground (including in-sandbox approvals).