Skip to content

feat(sdk): opt Browserbase sessions into the built-in Stagehand extension - #2805

Open
miguelg719 wants to merge 3 commits into
feat/resident-main-d-artifactsfrom
feat/resident-main-e-browserbase-opt-in
Open

feat(sdk): opt Browserbase sessions into the built-in Stagehand extension#2805
miguelg719 wants to merge 3 commits into
feat/resident-main-d-artifactsfrom
feat/resident-main-e-browserbase-opt-in

Conversation

@miguelg719

Copy link
Copy Markdown
Collaborator

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:

  • Protocol: new BrowserbaseExtension enum (onepassword, browser-events, stagehand) and browserSettings.extensions[]; stagehand.v4.json, models.gen.go, models.py/input_types.py regenerated (not hand-edited). extensionId (top-level and nested) is kept for caller-uploaded extensions.
  • All three SDKs (TS/Go/Python), identical semantics:
    • browserbase.launch sends browserSettings.extensions = caller's list de-duplicated in first-seen order + "stagehand" appended if absent; every other caller field (including extensionId) passes through untouched; caller inputs are never mutated.
    • The Browserbase Extensions API upload/create/delete path is removed with no fallback (TS browserbaseExtension.ts, Go multipart upload + delete requests/responses, Python upload_extension/delete_extension/build_extension_archive). Cleanup on failure is now release-only.
    • New residentBrowserConnection flag on the claimed browser: true for browserbase.launch, true for browserbase.connect without a caller extensionId, false with one, false for local. When set, stagehand.init omits browserCdpUrl, 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 sent browserCdpUrl is fixed here by construction: all three SDKs share the same rule).
  • Kept: STAGEHAND_EXTENSION_ARCHIVE_PATH and the packaged zip (stack D; consumed by evals/eve/preview). Go internal/extensionassets keeps only Materialize() for local loadUnpacked; the stale Archive() helper and its "uploaded to Browserbase" comment are gone. The embedded Go zip is not regenerated here (stack D owns it).
  • Smoke gating: ci.yml, turbo.json, the TS smoke and the Go live test all use BROWSERBASE_RESIDENT_SMOKE; CI reads it from the repo variable vars.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 because BROWSERBASE_API_KEY is present.
  • No changeset in this stack (deliberate; release notes will be written at publish time). The changeset bot warning is expected.

Rollout gate (do not merge before)

  1. Core #10864 (API accepts browserSettings.extensions: ["stagehand"]) and Core #10866 (image loads the built-in unpacked extension) deployed to production. Until then every browserbase.launch from this SDK version fails at session create.
  2. Go #3196 (pid2 activates the loopback Stagehand proxy for opted-in reservations; depends on #3193/#3194/#3195) deployed. Until then the worker's resident bootstrap has nothing to connect to.
  3. After both: set repo variable BROWSERBASE_RESIDENT_SMOKE=1 so CI runs the Browserbase smoke again.

browserbase.connect and raw-created sessions (decision)

browserbase.connect() is resident-by-default: without a caller extensionId it assumes the session opted into the built-in extension and stagehand.init omits browserCdpUrl. 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:

  • Sessions created directly with the Browserbase SDK must include 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).
  • Callers that upload their own Stagehand build keep working by passing extensionId to browserbase.connect/launch (legacy public-CDP attach).
  • In-repo consumer fixed: packages/evals/initStagehand.ts now forwards session.extensionId from launchRunnerProvidedBrowserbaseChrome() (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, Python pytest + ruff + ty + generate.py --check, and git diff --exit-code on regenerated files after re-running the generators.

@miguelg719
miguelg719 requested a review from a team as a code owner August 22, 2026 19:08
@changeset-bot

changeset-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ed7b5e6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 43 files

Confidence score: 3/5

  • The TypeScript example in packages/docs/v4/configuration/browser.mdx fails to type-check against the repository’s SDK because BrowserSettings rejects extensions, 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.py can leak a claimed browser handle when an assertion fails, potentially affecting later tests or resources; wrap claim/release/close cleanup in try/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
Loading

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"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

View Feedback

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>

@miguelg719 miguelg719 changed the title [AP-000] feat(sdk): opt Browserbase sessions into the built-in Stagehand extension feat(sdk): opt Browserbase sessions into the built-in Stagehand extension Aug 24, 2026
chromiebot and others added 3 commits August 24, 2026 12:23
…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.
@miguelg719
miguelg719 force-pushed the feat/resident-main-e-browserbase-opt-in branch from a1e550b to ed7b5e6 Compare August 24, 2026 19:23
@miguelg719
miguelg719 force-pushed the feat/resident-main-d-artifacts branch from 745dc5d to 400822a Compare August 24, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants