npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@svatah/yam-adapter-bidi

v0.2.0

Published

WebDriver BiDi adapter (independence proof)

Readme

@svatah/yam-adapter-bidi

The WebDriver BiDi adapter: AgentSurface over the W3C protocol, against stock browsers, with no driver SDK and no patched build (REQ-ADP-4, LLD §7.3).

It exists to prove the surface boundary (HLD ADR-8). Nothing in it shares a line with @svatah/yam-adapter-playwright — a WebSocket, a command table, an injected script, and the same interface on top — so a plan that replays identically through both is evidence that the boundary is real rather than a description of Playwright.

pnpm bidi:independence          # the whole proof, and reports/adapter-bidi.md
yam surface conform --adapter bidi
yam run --adapter bidi       # via `adapter: bidi` in yam.config.yaml

Where the endpoint comes from

A browser exposes BiDi in one of two ways, and the adapter covers both.

| Route | How | For | |---|---|---| | Attach to a server | YAM_BIDI_URL=ws://…/session | geckodriver, Firefox's remote agent, a remote grid — an endpoint where no session exists yet | | Attach to a session | YAM_BIDI_URL=ws://…/session/<id> | Stock Chrome and Edge through chromedriver / msedgedriver started with the webSocketUrl capability | | Launch | A Gecko binary, found in Playwright's browser cache, in YAM_BIDI_BROWSER, or installed on the machine | CI and the default developer loop |

The two attach shapes differ by one path segment and behave completely differently (LLD §7.3, Draft 2.6). A bare /session is a server: the adapter creates a session with session.new. A /session/<id> is a session someone already created through a driver, and session.new there is answered with session not created: session already exists — so the adapter does not send it, and learns the browser from session.status instead. It also leaves that session alone on close: it did not create it, and whoever did will DELETE /session/<id> when they are finished.

Firefox's remote agent is a BiDi server: firefox --remote-debugging-port=0 prints WebDriver BiDi listening on ws://127.0.0.1:<port> and serves the protocol at <url>/session with nothing else in the loop. Chrome and Edge expose CDP rather than BiDi on their remote-debugging port, so their driver hosts the BiDi mapper and the adapter attaches to it — which is what the W3C protocol intends and is not a Yam-specific arrangement:

chromedriver --port=9515 &
# start a session with
#   { "alwaysMatch": { "webSocketUrl": true, "unhandledPromptBehavior": "ignore" } }
# and read `capabilities.webSocketUrl` from the response
YAM_BIDI_URL=ws://127.0.0.1:9515/session/<id> yam surface conform --adapter bidi

unhandledPromptBehavior: "ignore" is not optional. Without it the driver answers every dialog itself the moment it opens — WebDriver's default is dismiss and notify — so a confirm a flow says to accept is already dismissed when the adapter tries to accept it. The adapter asks for ignore on the sessions it creates; on a session a driver hosts, whoever creates it has to.

node scripts/bidi-independence.mjs runs exactly those commands for you whenever a chromedriver or msedgedriver is on PATH or named by YAM_CHROMEDRIVER, and records the browser it reached in reports/adapter-bidi.md.

yam surface conform prints which binary or URL answered, and reports/adapter-bidi.md records it, because "BiDi passes" is not a result without it.

What BiDi cannot do

Capability flags declare it rather than emulating it (LLD §7.3). The executor refuses a plan whose actions need a missing capability at start, not mid-run.

| Flag | | Why | |---|---|---| | dialogs | yes | browsingContext.userPromptOpened and handleUserPrompt | | frames | yes | Frames are browsing contexts; switchFrame narrows to one | | windows | yes | contextCreated / contextDestroyed | | upload | yes | input.setFiles | | drag | yes | input.performActions pointer streams | | screenshot | yes | browsingContext.captureScreenshot, masked by a page overlay | | restore | yes | Navigate plus storage.setCookie | | trace | no | BiDi has no tracing. Playwright's trace viewer is a Playwright artefact; a file this adapter wrote would be a different thing with the same name | | webmcp | no | REQ-ADP-9, P2 for every adapter |

Two things the adapter has to do that Playwright does for you

Actionability. There is no "wait until this button is ready" in the protocol, so session.ts implements the wait LLD §7.3 names — visible, enabled, and a box unchanged over two frames — and no caller above the surface has to.

Accessible names and roles. BiDi exposes the DOM, not an accessibility tree with references. page-script.ts computes both. It is a port of the Playwright fallback's walker, copied rather than imported: an adapter that reached into @svatah/yam-adapter-playwright for the thing it is meant to be an independent implementation of would prove the opposite. What keeps the copy honest is packages/cli/test/snapshot-parity.test.ts, which drives both adapters over the same pages and requires the same roles, names and states (REQ-SURF-4).

Environment

| Variable | Meaning | |---|---| | YAM_BIDI_URL | A running BiDi endpoint to attach to | | YAM_BIDI_BROWSER | The Gecko binary to launch | | YAM_BIDI_TRACE=1 | Print every BiDi message sent and received |