add _experimental_batch - #2608
Conversation
|
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 37 files
Architecture diagram
sequenceDiagram
participant SDK as SDK Client (TS/Py/Go)
participant CDP as CDP Session (Worker)
participant SW as Service Worker
participant Router as RPCRouter
participant InProc as InProcessCommandClient
participant Page as Page / Locator Wrappers
participant Browser as Browser Runtime
Note over SDK,Browser: NEW: Experimental Batch Flow
SDK->>CDP: Runtime.evaluate (callback batch expression)
Note over SDK,CDP: Wraps callback source, serialized input, options with pageId & timeout
CDP->>SW: globalThis.__stagehandRunCallbackBatch(callback, input, options)
alt Another batch already active
SW-->>CDP: { ok: false, error: "Another batch running" }
CDP-->>SDK: Propagate error
else Valid batch
SW->>SW: Mark active, create AbortController, set timeout
SW->>InProc: new InProcessCommandClient(router, signal)
SW->>Router: context.pages (or context.active_page)
Router->>InProc: Return page list / active page
InProc-->>SW: Page reference
Note over SW,Page: Callback executes with worker-local objects
SW->>Page: callback({ page, context, act, observe, extract, metrics }, input)
Page->>InProc: locator.click({ selector: "button" })
InProc->>Router: handle({ method: "locator.click", params })
Router->>Browser: Execute CDP click command
Browser-->>Router: Result
Router-->>InProc: { clicked: true }
InProc-->>Page: Return result
Page->>InProc: page.title()
InProc->>Router: handle({ method: "page.title", params })
Router->>Browser: Execute CDP title command
Browser-->>Router: "Example Page"
Router-->>InProc: "Example Page"
InProc-->>Page: Return title
Page-->>SW: { title: "Example Page" }
SW->>SW: JSON round-trip result
alt Result is undefined
SW-->>CDP: { ok: true, valueIsUndefined: true }
else JSON-serializable result
SW-->>CDP: { ok: true, value: { title: "Example Page" } }
end
CDP-->>SDK: Decode envelope, return result
end
Note over SDK,SW: Key Constraints<br/>- context.close() excluded from callback scope<br/>- No recursive batch calls<br/>- Overall timeout via AbortController + cooperative check<br/>- CDP uses awaitPromise: true, returnByValue: true
Note over SDK,CDP: Host-only features excluded in worker:<br/>- screenshot path (no Node fs)<br/>- file upload local paths<br/>- Buffer → Uint8Array conversion
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 40 files
Architecture diagram
sequenceDiagram
participant SDK as SDK Client (TS/Python/Go)
participant CDP as CDP Client / WebSocket
participant SW as Service Worker
participant CB as callbackBatch Runner
participant RPCR as RPCRouter (In-Browser)
participant Page as Page/Locator Wrapper
participant Context as BrowserContext
Note over SDK,Context: NEW: Experimental Batch Flow (avoiding SDK↔Server RTT per operation)
SDK->>CDP: sendCommand("Runtime.evaluate", { expression, awaitPromise, returnByValue })
Note over SDK,CDP: Expression wraps callback source + serialized input + options
CDP->>SW: Runtime.evaluate CDP command (target service worker session)
alt SW supports batches
SW->>SW: Check globalThis.__stagehandRunCallbackBatch exists
SW->>CB: invoke __stagehandRunCallbackBatch(callback, input, options)
CB->>CB: Check no other batch active (one-batch-at-a-time guard)
CB->>CB: Create InProcessCommandClient backed by RPCRouter
CB->>Context: Resolve selected page (by pageId or activePage())
Context->>RPCR: context.pages / context.active_page
RPCR-->>Context: Page list / active page ref
Context-->>CB: Page object
CB->>CB: Build callback context { page, context, act, observe, extract, metrics }
Note over CB: context.close() is intentionally excluded
CB->>CB: Invoke callback(stagehand, input)
loop Inside callback
Page->>CB: page.goto(url)
CB->>RPCR: stagehand.act / stagehand.observe / stagehand.extract
RPCR-->>CB: Result
Page->>CB: page.locator("h1").innerText()
CB->>RPCR: locator.innerText
RPCR-->>CB: Text value
end
alt Result is undefined
CB-->>SW: { ok: true, valueIsUndefined: true }
else Result is JSON-serializable
CB->>CB: JSON round-trip validation
CB-->>SW: { ok: true, value: <JSON> }
end
else SW incompatible
SW-->>CDP: { ok: false, error: "StagehandRuntimeIncompatibleError" }
end
SW-->>CDP: Runtime.evaluate result (envelope JSON)
CDP->>CDP: Parse CallbackBatchEnvelopeSchema
alt OK envelope
alt valueIsUndefined
CDP-->>SDK: undefined/null
else has value
CDP-->>SDK: Decoded result
end
else Error envelope
CDP-->>SDK: Reconstructed Error with name & message
end
Note over SDK,SW: Timeout handling: cooperative + CDP evaluation grace period
opt Timeout expires
CB->>CB: Abort controller triggers
CB-->>SW: { ok: false, error: "Stagehand callback batch timed out" }
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 13 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…batch # Conflicts: # packages/extension/callbackBatch.ts # packages/extension/rpcRouter.ts # packages/extension/tests/callback-batch.test.ts # packages/extension/tests/stagehand-clients.test.ts # packages/protocol/stagehand.v4.json # packages/sdk-go/internal/extensionassets/stagehand-extension.zip # packages/sdk-go/models.gen.go # packages/sdk-python/src/stagehand/_generated/input_types.py # packages/sdk-python/src/stagehand/_generated/models.py # packages/sdk-ts/src/page.ts # rules/ast-grep/example-parity.test.ts
miguelg719
left a comment
There was a problem hiding this comment.
Two blocking items before merge (correctness, not security — the architecture is sound). Remaining should-fix / nits to follow.
miguelg719
left a comment
There was a problem hiding this comment.
rest of the review — should-fix + nits. none of these block on their own, but the page-pinning and extract heuristic ones are easy footguns.
There was a problem hiding this comment.
All reported issues were addressed across 29 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
why
client-server round trips can be expensive on remote browsers. this PR adds an experimental batch API that runs a callback inside the Stagehand extension service worker. commands issued by that callback route directly to the existing worker runtime, avoiding the client-server round trip between each operation
what changed
stagehand._experimental_batch()to the TypeScript and Python SDKs andStagehand.ExperimentalBatch()to GoglobalThis.__stagehandRunCallbackBatchin the service worker, which selects the active or explicitly requested page, constructs the callback context, applies the overall timeout, invokes the callback, and returns a JSON result/error envelopeStagehandCommandClientinterface and moved the publicPage,Locator,BrowserContext,Response, clipboard, and WebMCP wrappers onto itRPCRouterpage,context,act,observe,extract, andmetricsto callbacks. the callback context intentionally excludescontext.close()and does not expose Stagehand lifecycle or recursive batch operationsundefined, and reconstructed worker errors in the calling SDKpathcannot write to the caller's filesystem;Uint8Arrayrather than a NodeBufferExperimentalBatchas the equivalent of_experimental_batchin TypeScript and PythonTypeScript:
Python:
Go:
test plan
PageandLocatoroperations through the in-browser RPC routercontext.close()is absent from the callback surfaceundefinedresult envelopeawaitPromise/returnByValue.Summary by cubic
Adds an experimental batch API that runs a trusted JavaScript callback inside the Stagehand service worker to speed up multi-step flows by skipping SDK↔browser round trips. The API is
stagehand.experimentalBatch()(TypeScript),stagehand.experimental_batch()(Python), andStagehand.ExperimentalBatch()(Go);page.screenshot()now returnsUint8Array.New Features
stagehand.callback_batch; the CDP transport attaches the executablecallbackwithRuntime.evaluatewhile using the normal pending RPC path.{ page, context, act, observe, extract, metrics }, returns JSON‑serializable results (preservesundefined), and preserves callback names with a bundled__namehelper.options.pageonly setsbatch.page.StagehandCommandClient; public wrappers (Page,Locator,BrowserContext,Response, clipboard,WebMCP) use it for in‑worker execution.page.screenshot()returnsUint8Array.Bug Fixes
stagehand.callback_batchin the RPC timeout policy; cap batch timeouts at2_147_483_647 - 10_000ms.StagehandMetricstyping; all SDKs validate inputs, reject empty/null options and non‑string sources; Python checks for a closed RPC client.page.screenshot()returnsUint8Array; disallowpathin batches; avoid double‑encoding and count bytes before encoding; throwRangeErrorfor uploads >50MB; Node‑only FS reads are dynamically imported with clearer errors (including init scripts).Written for commit e5a1b1c. Summary will update on new commits.