Add runtime-specific TypeScript SDK exports - #2420
Conversation
|
There was a problem hiding this comment.
2 issues found across 26 files
Confidence score: 2/5
- In
packages/sdk-ts/src/runtime/node/localBrowserLauncher.ts, launching a local browser breaks when the SDK install path contains spaces because the extension directory is passed as a percent-encoded URL path, so Chrome cannot load it; users in common workspace setups will hit startup failures — decode/convert the file URL to a real filesystem path before passing it to Chrome. - In
packages/sdk-ts/src/browserSource.shared.ts, authenticated CDP flows dropbrowser.headers, soStagehand.init()cannot connect to protected CDP endpoints and initialization fails for secured environments — threadcdpHeadersthroughStagehand,RPCClient, and CDP discovery/connection calls.
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-ts/src/runtime/node/localBrowserLauncher.ts">
<violation number="1" location="packages/sdk-ts/src/runtime/node/localBrowserLauncher.ts:68">
P1: Local browser launch fails when the installed SDK path contains spaces: Chrome receives a percent-encoded extension path such as `consumer%20with%20spaces/.../extension`, which is not a directory. Convert the file URL with Node's `fileURLToPath` instead of reading `.pathname`.</violation>
</file>
<file name="packages/sdk-ts/src/browserSource.shared.ts">
<violation number="1" location="packages/sdk-ts/src/browserSource.shared.ts:64">
P1: CDP connections requiring authentication ignore the supplied `browser.headers`, so `Stagehand.init()` cannot connect to protected CDP endpoints. Thread `cdpHeaders` through `Stagehand`, `RPCClient`, and CDP discovery/WebSocket transport, or reject/remove this currently nonfunctional option.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Consumer as Consumer Code
participant PkgRes as Node Package Resolution
participant Export as package.json exports
participant NodeEntry as dist/node/index.js
participant WebEntry as dist/web/index.js
participant NodeSH as Stagehand (Node)
participant WebSH as Stagehand (Web)
participant Shared as StagehandCore
participant NodeBS as Node browserSource
participant WebBS as Web browserSource
participant RemoteBS as resolveRemoteBrowserSource
participant LocalLaunch as launchLocalBrowser
participant Protocol as Protocol/Backend
Note over Consumer,Protocol: NEW: Dual-build package with conditional exports
Consumer->>PkgRes: import { Stagehand } from "@browserbasehq/stagehand"
PkgRes->>Export: Check runtime conditions
alt Runtime is Node.js
Export-->>Consumer: dist/node/index.js (type: "node")
Consumer->>NodeEntry: import resolved
NodeEntry->>NodeSH: new Stagehand(initParams)
NodeSH->>Shared: super(initParams, parsedInit, { resolveBrowserSource })
else Runtime is workerd/deno/bun/browser/default
Export-->>Consumer: dist/web/index.js (type: "workerd"/etc)
Consumer->>WebEntry: import resolved
WebEntry->>WebSH: new Stagehand(initParams)
WebSH->>Shared: super(initParams, parsedInit, { resolveBrowserSource })
end
Note over Consumer,Protocol: Init flow with runtime-specific browser source resolution
Consumer->>Shared: init(params)
Shared->>Shared: Parse initParams with runtime-specific schema
alt Node variant (has "local")
Shared->>NodeBS: resolveBrowserSource(parsed)
alt browser.type === "local"
NodeBS->>LocalLaunch: launchLocalBrowser(options)
LocalLaunch-->>NodeBS: CDP URL + close
else browser.type is "browserbase" or "cdp"
NodeBS->>RemoteBS: resolveRemoteBrowserSource(parsed, deps)
end
else Web variant (no "local")
Shared->>WebBS: resolveBrowserSource(parsed)
WebBS->>RemoteBS: resolveRemoteBrowserSource(parsed, deps)
end
Note over Consumer,Protocol: Browser source resolution details
alt browser.type === "browserbase"
RemoteBS->>Protocol: createBrowserbaseSession()
Protocol-->>RemoteBS: session info (cdpUrl, sessionId)
RemoteBS-->>Shared: ResolvedBrowserSource
else browser.type === "cdp"
RemoteBS-->>Shared: ResolvedBrowserSource (cdpUrl, headers)
end
Note over Consumer,Protocol: TypeScript type safety differs by runtime
alt Node variant
Consumer->>NodeSH: initParams type includes { type: "local" }
NodeSH-->>Consumer: OK (type and runtime both support local)
else Web variant
Consumer->>WebSH: initParams type only includes "browserbase" | "cdp"
Consumer->>Consumer: BROWSER ERROR if user passes { type: "local" }
end
Note over Consumer,Protocol: Runtime portability enforcement
Consumer->>Consumer: Static import resolves to web variant
alt User attempts local browser in web runtime
Consumer->>WebBS: resolveBrowserSource({ browser: { type: "local" } })
WebBS->>WebBS: Schema validation fails (type not in union)
WebBS-->>Shared: Throws Zod validation error
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const STAGEHAND_EXTENSION_PATH = new URL( | ||
| import.meta.url.includes("/dist/node/") ? "./extension" : "../../../../server/dist", | ||
| import.meta.url, | ||
| ).pathname; |
There was a problem hiding this comment.
P1: Local browser launch fails when the installed SDK path contains spaces: Chrome receives a percent-encoded extension path such as consumer%20with%20spaces/.../extension, which is not a directory. Convert the file URL with Node's fileURLToPath instead of reading .pathname.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-ts/src/runtime/node/localBrowserLauncher.ts, line 68:
<comment>Local browser launch fails when the installed SDK path contains spaces: Chrome receives a percent-encoded extension path such as `consumer%20with%20spaces/.../extension`, which is not a directory. Convert the file URL with Node's `fileURLToPath` instead of reading `.pathname`.</comment>
<file context>
@@ -62,7 +62,10 @@ const DEFAULT_CHROME_FLAGS = [
+const STAGEHAND_EXTENSION_PATH = new URL(
+ import.meta.url.includes("/dist/node/") ? "./extension" : "../../../../server/dist",
+ import.meta.url,
+).pathname;
export async function launchLocalBrowser(
</file context>
|
|
||
| return { | ||
| cdpUrl: browser.cdpUrl, | ||
| ...(browser.headers === undefined ? {} : { cdpHeaders: browser.headers }), |
There was a problem hiding this comment.
P1: CDP connections requiring authentication ignore the supplied browser.headers, so Stagehand.init() cannot connect to protected CDP endpoints. Thread cdpHeaders through Stagehand, RPCClient, and CDP discovery/WebSocket transport, or reject/remove this currently nonfunctional option.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-ts/src/browserSource.shared.ts, line 64:
<comment>CDP connections requiring authentication ignore the supplied `browser.headers`, so `Stagehand.init()` cannot connect to protected CDP endpoints. Thread `cdpHeaders` through `Stagehand`, `RPCClient`, and CDP discovery/WebSocket transport, or reject/remove this currently nonfunctional option.</comment>
<file context>
@@ -0,0 +1,67 @@
+
+ return {
+ cdpUrl: browser.cdpUrl,
+ ...(browser.headers === undefined ? {} : { cdpHeaders: browser.headers }),
+ keepAlive: true,
+ };
</file context>
In order to launch local Chrome, stagehand currently needs Node. But if you’re using any remote browser, the SDK itself has no Node dependencies.
This allows remote-browser users to safely use Stagehand from Bun, Deno, Workers, browsers, and other web-compatible runtimes without changing how they install or import it:
The correct implementation is selected automatically through standard package conditional exports:
{ "exports": { ".": { "workerd": "./dist/web/index.mjs", "deno": "./dist/web/index.mjs", "bun": "./dist/web/index.mjs", "browser": "./dist/web/index.mjs", "node": "./dist/node/index.mjs", "default": "./dist/web/index.mjs" } } }In Node, users can launch a local browser:
Outside Node, local is excluded from the exported TypeScript type and Zod schema, so they can't do this: