feat(sdk): opt Browserbase sessions into the built-in Stagehand extension - #2805
feat(sdk): opt Browserbase sessions into the built-in Stagehand extension#2805miguelg719 wants to merge 3 commits into
Conversation
|
There was a problem hiding this comment.
2 issues found across 43 files
Confidence score: 3/5
- The TypeScript example in
packages/docs/v4/configuration/browser.mdxfails to type-check against the repository’s SDK becauseBrowserSettingsrejectsextensions, blocking users before session creation; update the example or SDK type definition so they match. - The test lifecycle in
packages/sdk-python/tests/test_browser.pycan leak a claimed browser handle when an assertion fails, potentially affecting later tests or resources; wrap claim/release/close cleanup intry/finally.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/sdk-python/tests/test_browser.py">
<violation number="1" location="packages/sdk-python/tests/test_browser.py:141">
P3: When this new assertion fails, `test_claim_release_reclaim_and_errors` leaks the claimed browser handle because cleanup is not exception-safe. Wrap the claim/release/close lifecycle in `try/finally` so failed assertions do not leave the handle claimed or its CDP connection open.
(Based on your team's feedback about cleaning up claimed Python browser handles.)</violation>
</file>
<file name="packages/docs/v4/configuration/browser.mdx">
<violation number="1" location="packages/docs/v4/configuration/browser.mdx:443">
P2: The TypeScript Browserbase-SDK example does not type-check with the repository’s `@browserbasehq/sdk` version because `BrowserSettings` does not declare `extensions`, producing an excess-property error before session creation. Use an SDK release that declares the field or add a narrow compatibility cast or error annotation to this example.</violation>
</file>
Architecture diagram
sequenceDiagram
participant User as Caller Code
participant SDK as Stagehand SDK (TS/Go/Python)
participant BB_API as Browserbase API
participant Worker as Stagehand Worker (Resident)
Note over User,Worker: Session Creation / Connection Flow
User->>SDK: launch() or connect()
SDK->>SDK: NEW: processExtensions(browserSettings)
Note right of SDK: De-duplicates caller list and<br/>appends "stagehand" if missing.
alt NEW: Resident Extension Path (No extensionId)
SDK->>BB_API: POST /v1/sessions { extensions: ["stagehand", ...] }
Note right of SDK: CHANGED: No longer calls /v1/extensions upload API
BB_API-->>SDK: Session Metadata (CDP URL, ID)
SDK->>SDK: NEW: Set residentBrowserConnection = true
else Legacy/Custom Extension Path (extensionId provided)
SDK->>BB_API: POST /v1/sessions { extensionId: "..." }
BB_API-->>SDK: Session Metadata
SDK->>SDK: Set residentBrowserConnection = false
end
Note over User,Worker: Stagehand Worker Initialization
User->>SDK: Stagehand.create(browser)
SDK->>Worker: stagehand.init(StagehandInitParams)
alt NEW: residentBrowserConnection is true
Note over SDK,Worker: CHANGED: Omit browserCdpUrl in RPC params
Worker->>Worker: NEW: Connect to browser via internal loopback proxy
else residentBrowserConnection is false
Note over SDK,Worker: Include browserCdpUrl in RPC params
Worker->>Worker: Connect to browser via public CDP endpoint
end
Worker-->>SDK: Init Success
SDK-->>User: Stagehand Instance Ready
Note over User,BB_API: Cleanup Flow (on browser.close)
User->>SDK: browser.close()
SDK->>BB_API: POST /v1/sessions/:id/release
Note right of SDK: CHANGED: No longer calls /v1/extensions delete API
BB_API-->>SDK: Success
SDK-->>User: Closed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| projectId: process.env.BROWSERBASE_PROJECT_ID!, | ||
| // Add configuration options here | ||
| browserSettings: { | ||
| extensions: ["stagehand"], |
There was a problem hiding this comment.
P2: The TypeScript Browserbase-SDK example does not type-check with the repository’s @browserbasehq/sdk version because BrowserSettings does not declare extensions, producing an excess-property error before session creation. Use an SDK release that declares the field or add a narrow compatibility cast or error annotation to this example.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/configuration/browser.mdx, line 443:
<comment>The TypeScript Browserbase-SDK example does not type-check with the repository’s `@browserbasehq/sdk` version because `BrowserSettings` does not declare `extensions`, producing an excess-property error before session creation. Use an SDK release that declares the field or add a narrow compatibility cast or error annotation to this example.</comment>
<file context>
@@ -439,7 +439,10 @@ const bb = new Browserbase({
projectId: process.env.BROWSERBASE_PROJECT_ID!,
- // Add configuration options here
+ browserSettings: {
+ extensions: ["stagehand"],
+ // Add other browser settings here
+ },
</file context>
|
|
||
| first = _claim_browser(handle) | ||
| assert first.cdp_client is fake_cdp.instances[-1] | ||
| assert first.resident_browser_connection is False |
There was a problem hiding this comment.
P3: When this new assertion fails, test_claim_release_reclaim_and_errors leaks the claimed browser handle because cleanup is not exception-safe. Wrap the claim/release/close lifecycle in try/finally so failed assertions do not leave the handle claimed or its CDP connection open.
(Based on your team's feedback about cleaning up claimed Python browser handles.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-python/tests/test_browser.py, line 141:
<comment>When this new assertion fails, `test_claim_release_reclaim_and_errors` leaks the claimed browser handle because cleanup is not exception-safe. Wrap the claim/release/close lifecycle in `try/finally` so failed assertions do not leave the handle claimed or its CDP connection open.
(Based on your team's feedback about cleaning up claimed Python browser handles.) </comment>
<file context>
@@ -138,6 +138,7 @@ async def test_claim_release_reclaim_and_errors(fake_cdp: type[FakeCDPClient]) -
first = _claim_browser(handle)
assert first.cdp_client is fake_cdp.instances[-1]
+ assert first.resident_browser_connection is False
with pytest.raises(
RuntimeError,
</file context>
…sion Browserbase images now ship the Stagehand extension. All three SDKs request it through browserSettings.extensions (caller list de-duplicated in first-seen order, "stagehand" appended when absent) and stop uploading/deleting an extension through the Browserbase Extensions API, with no fallback. Caller extensionId values (top-level and browserSettings) pass through untouched. A new residentBrowserConnection flag on the claimed browser (true for browserbase.launch and for browserbase.connect without a caller extensionId) makes stagehand.init omit browserCdpUrl so the worker attaches over the session's loopback resident proxy instead of public CDP. Protocol gains the BrowserbaseExtension enum and browserSettings.extensions; stagehand.v4.json, models.gen.go and the Python generated models are regenerated. The packaged zip / STAGEHAND_EXTENSION_ARCHIVE_PATH stay; the Go extensionassets package keeps only Materialize() for local loadUnpacked. The Browserbase smoke is gated on BROWSERBASE_RESIDENT_SMOKE everywhere (ci.yml reads vars.BROWSERBASE_RESIDENT_SMOKE) until Core accepts the built-in extension in production.
…attach path browserbase.connect() is resident-by-default: without a caller extensionId it assumes the session opted into the built-in Stagehand extension and stagehand.init omits browserCdpUrl. The evals harness creates its session through the raw Browserbase SDK with a runner-uploaded extension and never opts in, so it now forwards session.extensionId to connect() and keeps the public-CDP attach instead of failing against an inactive resident proxy. Document the rule in the v4 browser configuration docs: raw-created sessions must include "stagehand" in browserSettings.extensions (all three SDK tabs), or pass the uploaded extensionId to connect(); there is no automatic fallback. The stale note claiming launch() uploads and deletes an extension is rewritten.
a1e550b to
ed7b5e6
Compare
745dc5d to
400822a
Compare
Summary
Stack E/5 (resident Stagehand on
main):feat/resident-main-a-transport->-b-target-safety->-c-gateway-runtime->-d-artifacts->-e-browserbase-opt-in(this PR). Supersedes #2462, which was based on the archived v4-spike.Browserbase sessions now use the image-resident Stagehand extension instead of a per-session upload:
BrowserbaseExtensionenum (onepassword,browser-events,stagehand) andbrowserSettings.extensions[];stagehand.v4.json,models.gen.go,models.py/input_types.pyregenerated (not hand-edited).extensionId(top-level and nested) is kept for caller-uploaded extensions.browserbase.launchsendsbrowserSettings.extensions= caller's list de-duplicated in first-seen order +"stagehand"appended if absent; every other caller field (includingextensionId) passes through untouched; caller inputs are never mutated.browserbaseExtension.ts, Go multipart upload + delete requests/responses, Pythonupload_extension/delete_extension/build_extension_archive). Cleanup on failure is now release-only.residentBrowserConnectionflag on the claimed browser:trueforbrowserbase.launch,trueforbrowserbase.connectwithout a callerextensionId,falsewith one,falsefor local. When set,stagehand.initomitsbrowserCdpUrl, so the worker (stack C) attaches through the session's loopback resident proxy instead of dialing public CDP from inside the browser (the Opt into the built-in Browserbase extension #2462 review finding that Go still sentbrowserCdpUrlis fixed here by construction: all three SDKs share the same rule).STAGEHAND_EXTENSION_ARCHIVE_PATHand the packaged zip (stack D; consumed by evals/eve/preview). Gointernal/extensionassetskeeps onlyMaterialize()for localloadUnpacked; the staleArchive()helper and its "uploaded to Browserbase" comment are gone. The embedded Go zip is not regenerated here (stack D owns it).ci.yml,turbo.json, the TS smoke and the Go live test all useBROWSERBASE_RESIDENT_SMOKE; CI reads it from the repo variablevars.BROWSERBASE_RESIDENT_SMOKE(unset = skipped) so the previous env-name mismatch can no longer turn the smoke into a silent no-op, and the smoke no longer auto-enables just becauseBROWSERBASE_API_KEYis present.Rollout gate (do not merge before)
browserSettings.extensions: ["stagehand"]) and Core #10866 (image loads the built-in unpacked extension) deployed to production. Until then everybrowserbase.launchfrom this SDK version fails at session create.BROWSERBASE_RESIDENT_SMOKE=1so CI runs the Browserbase smoke again.browserbase.connectand raw-created sessions (decision)browserbase.connect()is resident-by-default: without a callerextensionIdit assumes the session opted into the built-in extension andstagehand.initomitsbrowserCdpUrl. There is deliberately no automatic fallback to public CDP (design non-goal), and the SDK does not probe the session to guess. Consequences, applied identically in TS/Go/Python:browserSettings.extensions: ["stagehand"](packages/docs/v4/configuration/browser.mdx"Alternative: Browserbase SDK" now shows this in all three tabs, and the stale "Stagehand uploads its extension" note is rewritten).extensionIdtobrowserbase.connect/launch(legacy public-CDP attach).packages/evals/initStagehand.tsnow forwardssession.extensionIdfromlaunchRunnerProvidedBrowserbaseChrome()(the evals runner still uploads its own build), so the Browserbase evals path does not regress on merge.Validation
See the gate list in the PR checks:
pnpm check, sdk-ts unit tests, protocol tests,go test ./...+go vet+ gofmt + generator--check, Pythonpytest+ ruff + ty +generate.py --check, andgit diff --exit-codeon regenerated files after re-running the generators.