-
Notifications
You must be signed in to change notification settings - Fork 226
Add browser-use-to-stagehand skill: migrate browser-use to Stagehand on Browserbase #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9b30962
Add bu-to-bb skill: migrate browser-use to Stagehand on Browserbase
6dd02ab
Remove Playwright references; full browser-use → Stagehand conversion
c67c629
Address review (PR #130): add LICENSE.txt, fold docs into references/
e8d714a
Merge branch 'main' into bu-to-bb
rcbrowder 5b3c33e
Fix runtime API bugs + add doc-resilience (validated via live eval)
shrey150 5dcc704
Rename skill bu-to-bb -> browser-use-to-stagehand
shrey150 f24de18
Add agent-tools + MCP mappings; real-world coverage (validated via ti…
shrey150 d2ad662
Note Stagehand 3.6.0 stdio-MCP runtime bug in §3.7
shrey150 31e60cd
Address Bugbot: experimental flag in prompt/determinism, complete log…
shrey150 5b67461
Address Bugbot: align prompt.md determinism levels with canonical scale
shrey150 649ef83
Address Bugbot: §3.5 saveUrl tool reads URL from page, not the model
shrey150 828211d
Address Bugbot: enforce allow-list after every navigation in login ex…
shrey150 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,258 @@ | ||
| # Examples: browser-use → Stagehand | ||
|
|
||
| Before/after pairs showing the migration patterns. Each "before" is a browser-use (Python) script; | ||
| each "after" is its Stagehand v3 (TypeScript) rewrite on Browserbase. Illustrative — validate | ||
| against your real site and tighten the `act(...)` prompts to the actual on-page labels. | ||
|
|
||
| See [SKILL.md](SKILL.md) for the workflow, [the guide](references/guide.md) for the philosophy + | ||
| feature mapping, and [references/determinism.md](references/determinism.md) for the decision framework. | ||
|
|
||
| ## Running an "after" example | ||
|
|
||
| ```bash | ||
| npm install @browserbasehq/stagehand zod | ||
| npm install -D tsx dotenv | ||
| ``` | ||
|
|
||
| `.env`: | ||
| ```bash | ||
| BROWSERBASE_API_KEY=... | ||
| BROWSERBASE_PROJECT_ID=... | ||
| ANTHROPIC_API_KEY=... # or OPENAI_API_KEY, matching the model string in the file | ||
| ``` | ||
|
|
||
| ```bash | ||
| npx tsx example.ts | ||
| ``` | ||
|
|
||
| Swap `env: "BROWSERBASE"` for `env: "LOCAL"` (with Chrome installed) to run locally during dev. | ||
|
|
||
| --- | ||
|
|
||
| ## 1. Simple task | ||
|
|
||
| A fully-agentic task becomes a deterministic `page.goto` + one `act()` — no agent loop. | ||
|
|
||
| **Before — browser-use** | ||
| ```python | ||
| import asyncio | ||
|
|
||
| from browser_use import Agent, ChatAnthropic | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| agent = Agent( | ||
| task="Go to Hacker News (news.ycombinator.com) and open the top story", | ||
| llm=ChatAnthropic(model="claude-sonnet-4-6"), | ||
| ) | ||
| history = await agent.run(max_steps=20) | ||
| print(history.final_result()) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) | ||
| ``` | ||
|
|
||
| **After — Stagehand v3** | ||
| ```typescript | ||
| import "dotenv/config"; | ||
| import { Stagehand } from "@browserbasehq/stagehand"; | ||
|
|
||
| async function main() { | ||
| const stagehand = new Stagehand({ | ||
| env: "BROWSERBASE", // use "LOCAL" for local dev with a real Chrome | ||
| model: "anthropic/claude-sonnet-4-6", | ||
| }); | ||
| await stagehand.init(); | ||
| try { | ||
| const page = stagehand.context.pages()[0]; | ||
|
|
||
| await page.goto("https://news.ycombinator.com"); // deterministic | ||
| await stagehand.act("click the top story's title link"); // AI: markup varies | ||
|
|
||
| console.log("Opened:", page.url()); | ||
| } finally { | ||
| await stagehand.close(); | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 2. Structured extraction | ||
|
|
||
| A Pydantic `output_model_schema` becomes a zod `extract()` — no agent loop, just navigate then read. | ||
|
|
||
| **Before — browser-use** | ||
| ```python | ||
| import asyncio | ||
|
|
||
| from pydantic import BaseModel | ||
|
|
||
| from browser_use import Agent, ChatOpenAI | ||
|
|
||
|
|
||
| class Story(BaseModel): | ||
| title: str | ||
| points: int | ||
| comments: int | ||
|
|
||
|
|
||
| class TopStories(BaseModel): | ||
| stories: list[Story] | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| agent = Agent( | ||
| task="Go to Hacker News and return the top 5 stories with title, points, and comment count", | ||
| llm=ChatOpenAI(model="gpt-5"), | ||
| output_model_schema=TopStories, | ||
| ) | ||
| history = await agent.run() | ||
| data = history.structured_output # TopStories instance | ||
| for story in data.stories: | ||
| print(story.title, story.points, story.comments) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) | ||
| ``` | ||
|
|
||
| **After — Stagehand v3** | ||
| ```typescript | ||
| import "dotenv/config"; | ||
| import { Stagehand } from "@browserbasehq/stagehand"; | ||
| import { z } from "zod"; | ||
|
|
||
| async function main() { | ||
| const stagehand = new Stagehand({ env: "BROWSERBASE", model: "openai/gpt-5" }); | ||
| await stagehand.init(); | ||
| try { | ||
| const page = stagehand.context.pages()[0]; | ||
| await page.goto("https://news.ycombinator.com"); | ||
| await page.waitForLoadState("domcontentloaded"); // settle before the AI snapshot | ||
|
|
||
| const stories = await stagehand.extract( | ||
| "extract the top 5 stories with their title, points, and comment count", | ||
| z.array( | ||
| z.object({ | ||
| title: z.string(), | ||
| points: z.number(), | ||
| comments: z.number().describe("number of comments"), | ||
| }), | ||
| ), | ||
| ); | ||
|
|
||
| for (const s of stories) console.log(s.title, s.points, s.comments); | ||
| } finally { | ||
| await stagehand.close(); | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 3. Login with sensitive data | ||
|
|
||
| `sensitive_data` → `variables` (secrets never reach the LLM). The known form is driven with | ||
| deterministic `act()` steps. `allowed_domains` has no direct Stagehand equivalent — replace it with | ||
| a `page.url()` host check. For repeat runs, reuse a Browserbase **Context** to skip re-login. | ||
|
|
||
| **Before — browser-use** | ||
| ```python | ||
| import asyncio | ||
| import os | ||
|
|
||
| from browser_use import Agent, Browser, ChatAnthropic | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| sensitive_data = { | ||
| "https://example.com": { | ||
| "x_user": os.environ["APP_USER"], | ||
| "x_pass": os.environ["APP_PASS"], | ||
| } | ||
| } | ||
| agent = Agent( | ||
| task="Log into example.com using username x_user and password x_pass, then open the dashboard", | ||
| llm=ChatAnthropic(model="claude-sonnet-4-6"), | ||
| sensitive_data=sensitive_data, | ||
| use_vision=False, # don't leak secrets via screenshots | ||
| browser=Browser(allowed_domains=["https://*.example.com"]), | ||
| ) | ||
| await agent.run() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) | ||
| ``` | ||
|
|
||
| **After — Stagehand v3** | ||
| ```typescript | ||
| import "dotenv/config"; | ||
| import { Stagehand } from "@browserbasehq/stagehand"; | ||
|
|
||
| async function main() { | ||
| const stagehand = new Stagehand({ | ||
| env: "BROWSERBASE", | ||
| model: "anthropic/claude-sonnet-4-6", | ||
| // Reuse auth across runs with a Context: | ||
| // browserbaseSessionCreateParams: { | ||
| // projectId: process.env.BROWSERBASE_PROJECT_ID!, | ||
| // browserSettings: { context: { id: process.env.BB_CONTEXT_ID!, persist: true } }, | ||
| // }, | ||
| }); | ||
| await stagehand.init(); | ||
| try { | ||
| const page = stagehand.context.pages()[0]; | ||
| await page.goto("https://example.com/login"); | ||
|
|
||
| await stagehand.act("type %username% into the email field", { | ||
| variables: { username: process.env.APP_USER! }, | ||
| }); | ||
| await stagehand.act("type %password% into the password field", { | ||
| variables: { password: process.env.APP_PASS! }, | ||
| }); | ||
| await stagehand.act("click the sign in button"); | ||
|
|
||
| await page.waitForLoadState("domcontentloaded"); | ||
|
|
||
| // Best-effort stand-in for allowed_domains=["https://*.example.com"]. browser-use | ||
| // enforces the allow-list across the ENTIRE run; a host check only covers the moment | ||
| // it runs, so call it after *every* navigation — not just sign-in. For real continuous | ||
| // enforcement use Browserbase proxy domain rules (api-mapping §5); this throw is only a | ||
| // tripwire and is flagged "needs human review" in the migration summary. | ||
| const assertAllowedHost = () => { | ||
| const host = new URL(page.url()).hostname; | ||
| if (host !== "example.com" && !host.endsWith(".example.com")) { | ||
| throw new Error(`navigated off the allow-list: ${page.url()}`); | ||
| } | ||
| }; | ||
| assertAllowedHost(); | ||
| console.log("Logged in:", page.url()); | ||
|
|
||
| // Second half of the task ("…then open the dashboard") — don't stop at login. | ||
| await stagehand.act("open the dashboard"); | ||
| await page.waitForLoadState("domcontentloaded"); | ||
| assertAllowedHost(); // re-check: the guardrail must cover this navigation too | ||
| console.log("Dashboard:", page.url()); | ||
|
shrey150 marked this conversation as resolved.
|
||
| } finally { | ||
| await stagehand.close(); | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| MIT License | ||
|
|
||
| Copyright (c) 2026 Browserbase, Inc. | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.