Skip to content

Latest commit

 

History

History
54 lines (47 loc) · 3.46 KB

browser-async-context.md

File metadata and controls

54 lines (47 loc) · 3.46 KB

Browser SDK async context

Add ...browserHost({ include: actorModules }) from @mcp-b/do-runtime/vite to the application's Vite plugins. It aliases bare and node: async_hooks to @mcp-b/do-runtime/browser/async-hooks and runs doRuntimeAwaitTransform({ include, asyncContext: true }) in Worker bundles. It also runs the transform in the application plugins, but only while serving: unbundled development serves Worker modules through them. Include the actor and every SDK module whose async functions need context, and keep those modules out of optimizeDeps in development, because pre-bundling skips plugin transforms. A build that bundles actor code outside worker, as the extension example's facet passes do, adds the transform itself. The extension example has a complete setup.

The shim supports the Agents SDK's AsyncLocalStorage: independent stores, run, getStore, exit, enterWith, disable, bind and static snapshot. Snapshots capture the current set of stores. Each synchronous callback restores its caller's scope in finally; it never leaves one mutable store active while a promise is pending. Actor entry, reentry, critical sections and timer callbacks capture that scope before the runtime waits for its input lock.

installActorScope also binds native ReadableStream and TransformStream callbacks. Sources and transformers created inside an actor retain its actor and async stores when later demand or network chunks arrive. Pull, transform, flush and cancel re-enter through the existing input gate; start remains synchronous. Streams constructed outside an actor retain native behavior. This covers the AI SDK's streamed tool execution, whose callbacks bypass transformed awaits.

This requires compilation. Native await bypasses Promise.prototype.then, as the TC39 async-context proposal explains. The opt-in Vite transform first gates awaits, then uses Vite's installed Oxc transformer to lower async functions and generators to Promise callbacks. Importing the shim installs context binding on Promise.prototype.then once per realm. Importing the runtime alone does not patch Promise.

The opt-in transform also corrects the generator helper from Vite's bundled Oxc (≥ 0.144.0): an early return must resume awaited finally cleanup with next, while a delegated yield* may still require return. This follows the upstream Babel helper. The correction checks the helper's exact code shape and fails with a review message if it changes, so a compiler upgrade cannot silently retain a stale fix.

Untransformed native async functions and top-level module awaits do not propagate this context. Bind callbacks registered with external event APIs explicitly; ordinary actor timers already capture it. This is not a complete Node async_hooks implementation: async-hook instrumentation and AsyncResource are unsupported and resource construction throws.

Run pnpm --dir vendor/agents --filter @cloudflare/think test:browser for real Chromium regressions covering overlapping entries in one actor and separate actors, rejection, getCurrentAgent(), tracing parentage, and deferred generator completion/early-return cleanup. The native test caller stays untransformed to detect scopes escaping into unrelated work. The extension e2e additionally checks two overlapping PKCE scopes in the built Agents SDK inside an MV3 Worker.