feat(python): extension_cdp_url for local_browser.connect

Merged
SE
16h ago
+102-3

Why

Stagehand's extension opens its own CDP connection to the browser, and it is handed the URL the Python client connected with. That only works when the client and the browser can use the same address.

Sometimes they can't. Our case: the client runs in a server process and reaches a sandboxed Chrome through an authenticated gateway, wss://<gateway>/.../cdp?api_key=.... The extension, running inside that Chrome, should dial the browser's local endpoint (ws://127.0.0.1:<port>/devtools/browser/<id>). Using the gateway URL would hand the gateway's credentials to the browser, which loads arbitrary pages, and send every extension command out and back in through the gateway. Today the only workaround is overriding the private Stagehand._worker_init_params.

What

browser = await local_browser.connect(
    cdp_url="wss://gateway/cdp?api_key=...",
    extension_cdp_url="ws://127.0.0.1:9300/devtools/browser/<id>",
)
stagehand = await Stagehand.create(browser=browser)
  • local_browser.connect takes an optional extension_cdp_url, which must be a ws:// or wss:// URL with a host.
  • It is carried on the claimed browser's worker metadata, and Stagehand.create sends it as the init param browser_cdp_url in place of the client's URL.
  • When it is unset, behaviour is unchanged.
  • Python SDK only, with a minor changeset. The TypeScript SDK has the same assumption and could take the same option; happy to follow up.

Tests

  • New tests cover the init param, connect carrying the value through, the default, and rejection of non-WebSocket URLs.
  • ruff format --check, ruff check, ty check and generate.py --check pass.
  • pytest: 793 passed. test_official_api_routes_through_and_never_closes_a_caller_http_client fails with and without this change in my checkout, because it needs the extension built in packages/extension/dist.

🤖 Generated with Claude Code


Summary by cubic

Adds an optional extension_cdp_url to local_browser.connect in the Python SDK so Stagehand's extension can dial a different CDP WebSocket than the client's, when the client reaches Chrome through a proxy or gateway the extension cannot use.

  • When set, Stagehand.create sends it as the init param browser_cdp_url in place of the client's URL; when unset, behavior is unchanged.
  • The value must be a ws:// or wss:// URL with a host; invalid schemes, ports (including 0), or missing hostnames raise a validation error.
  • Python SDK only, with a minor changeset; the TypeScript SDK could take the same option in a follow-up.
  • New tests cover the init param, metadata carrying the value through, the default, and rejection of invalid URLs. pytest reports 793 passed; test_official_api_routes_through_and_never_closes_a_caller_http_client fails with and without this change because it needs the extension built in packages/extension/dist.