Skip to content

computer: Add the codemode:browser module - #299

Open
ben-reitz wants to merge 1 commit into
host-objectsfrom
browser/browser-module
Open

ben-reitz wants to merge 1 commit into
host-objectsfrom
browser/browser-module

Conversation

@ben-reitz

@ben-reitz ben-reitz commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

An agent with an Browser can drive it through browserTool, but code running in a Workspace can't. Building a site, opening it, taking screenshots and comparing them takes several model turns instead of one exec.

createBrowserModule() installs the browser as codemode:browser. Its exports are the same browser.* functions browserTool gives its code, so the docs and rules read the same:

"codemode:browser": createBrowserModule({
  browser: this.browser,
  //  default scope: "shared". in pi, scope it to conversation ID.
  scope: ({ metadata }) => (metadata.user ? `user:${metadata.user}` : "shared"),
}),
import * as browser from "codemode:browser";
await browser.cdp({ method: "Page.navigate", params: { url }, sessionId: "active" });

It keeps its browser session in a scoped context, from createCodemodeModule() (#301). A run opens one session on its first call and closes it when the run waits for a person or ends, which saves its tab for the next run or browserTool call. When the run ends, the session's notices go to its stderr, so nothing is written while the code runs and replay still matches. cdp is marked unsafeToRepeat(), (the same thing pi marks on browserTool).

Scope

The browser scope picks the tab. It defaults to "shared", and can be a string or a function of the run's metadata (#291, #292), so runs started for one user can share a tab. Runs in one browser scope take turns, because pi and the AI SDK run a message's tool calls in parallel and would otherwise see each other's navigation. A run gives up waiting after lockTimeoutMs, 45 seconds by default, with its own error before the run times out. A run that browsed and is waiting for a person keeps its browser scope, so other runs fail at once instead of waiting hours for an answer. That includes a run that gets its turn just as the one ahead of it pauses, so it checks again once it has the lock. The module remembers which runs browsed in memory, so after a Durable Object restart a waiting run holds nothing until it resumes.

Every function needs Workspace write access. The browser keeps its logins, so browsing can change things on other sites, and a read-only backend refuses it, as it refuses codemode:container's exec.

Computer types the agents contract by shape and takes no dependency on agents. The module needs agents 0.33.0 or later, which added functionDocs(), session.functions, the session lock and its notices.

The model is told, in the module's description, to save screenshots with node:fs and open them with read, to take about eight per run because host calls share 8 MiB, and to work on several pages in one run. It's also told not to call browserTool through codemode:tools, which would wait for its own turn. That case isn't caught automatically, since neither module can see the other.

Unit tests cover the browser scope options, the paused-run check, the lock timeout, both kinds of close and the description, against a fake browser shaped like the agents one. Two end-to-end tests run it through the real backend: one session per scope, closed on pause, with notices logged at the end; and a second run failing at once while a waiting run holds the browser scope. agents can't be a dev dependency yet: its optional peer range for pi conflicts with the version this repository uses. docs/17_isolate_javascript.md has a new codemode:browser section, which notes that an app writing exec's description by hand must include the module's.


Devin Review

@ben-reitz ben-reitz added the allow-pr Allow a PR to remain open. label Nov 3, 2026
@changeset-bot

changeset-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 73ef9d3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@cloudflare/computer Minor
@cloudflare/dofs Minor
@cloudflare/computer-rpc Minor
@cloudflare/computerd Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ben-reitz
ben-reitz added this pull request to stack #294 November 3, 2026 15:42
devin-ai-integration[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@cloudflare/computer@299

commit: 73ef9d3

@ben-reitz
ben-reitz removed this pull request from stack #294 November 4, 2026 10:44
@ben-reitz
ben-reitz force-pushed the browser/browser-module branch from 9f56716 to af42aa2 Compare November 4, 2026 10:47
@ben-reitz
ben-reitz changed the base branch from browser/module-hooks to browser/codemode-module-fixes November 4, 2026 10:47
@ben-reitz
ben-reitz added this pull request to stack #304 November 4, 2026 10:50
@ben-reitz
ben-reitz force-pushed the browser/browser-module branch 2 times, most recently from 050b4c2 to d3ee669 Compare November 4, 2026 12:03
devin-ai-integration[bot]

This comment was marked as resolved.

@ben-reitz
ben-reitz force-pushed the browser/browser-module branch from d3ee669 to 3760a07 Compare November 4, 2026 12:09
Base automatically changed from browser/codemode-module-fixes to main November 4, 2026 13:37
@ben-reitz
ben-reitz removed this pull request from stack #304 November 4, 2026 13:38
@ben-reitz
ben-reitz changed the base branch from main to host-objects November 4, 2026 13:39
@ben-reitz
ben-reitz added this pull request to stack #306 November 4, 2026 13:39
devin-ai-integration[bot]

This comment was marked as resolved.

An agent with an agents `Browser` can drive it through `browserTool`,
but code running in a Workspace can't: it has to stop, call the tool,
and start a new run to use what it saw. That rules out building a
site, opening it, taking screenshots and comparing them in one run.

`createBrowserModule({ browser, scope })` from
`@cloudflare/computer/modules/browser` is a codemode module that
exports the browser's `browser.*` functions, the same ones
`browserTool` gives its code, so code imports it as a namespace and
the browser's docs and rules read the same. The session is a scoped
context: a run opens one on its first call and closes it when the run waits
for a person or ends, which saves its active tab for the next run or tool
call. When the run ends, the session's notices, and how long the run
waited for its turn, go to its stderr; nothing is written while the
code runs, so replay's output check holds. `cdp` is marked unsafe to
repeat, as pi marks `browserTool`.

The browser scope, which tab a run drives, defaults to "shared" and
can be picked from the run's metadata, so a pi app can put `exec` and
`browserTool` in the same conversation's tab. Runs in one browser
scope take turns through the session's lock, since pi and the AI SDK
run a message's tool calls in parallel. A run gives up after
`lockTimeoutMs`, 45 seconds by default, with its own error rather
than a timed-out run. A run that browsed and is waiting for a person
keeps its browser scope: others fail at once rather than wait for an
answer that may take hours, including a run that got its turn while
the one ahead of it paused. Every function needs Workspace write
access, since browsing can change things on sites the browser is
logged in to.

Computer types the agents contract by shape and takes no dependency
on agents.
@ben-reitz
ben-reitz force-pushed the browser/browser-module branch from 3760a07 to 73ef9d3 Compare November 4, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

allow-pr Allow a PR to remain open.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant