# Runtimes & compatibility

> Running pipedog on Bun, Node, Deno and Cloudflare Workers, and which packages are verified where.

Source: https://polvallverdu.github.io/pipedog/docs/guides/runtimes/



`@pipedog/core` and `@pipedog/asyncio` are runtime-agnostic: they run unchanged on **Bun**,
**Node.js** (22 or later), **Deno** and **Cloudflare Workers** (workerd). Code that only works on
some runtimes lives in its own package, and every package records where it is verified.

## Compatibility table [#compatibility-table]

Generated at build time from the `pipecat.runtimes` field of each `packages/*/package.json`:

<CompatTable />

`runtimes` lists only what is verified: the compat harness runs each package's smoke test (its
`compat/smoke.ts`, or a default that imports every public entry) on each runtime, and
`bun run compat --write` records the runtimes that passed. It is never edited by hand. An empty
row means the package has not been run through the harness yet.

A package that cannot work on a runtime (SmallWebRTC on Workers, which has no UDP) lists it in
`pipecat.compat.unsupportedRuntimes`; the harness skips it there. Optional peers a package's users
are expected to install go in `pipecat.compat.usesOptionalPeers`; all other optional peers are made
unresolvable during the run, so a package that silently depends on one fails.

Every package is verified on all four runtimes except `@pipedog/transport-smallwebrtc` (no UDP
on Workers), `@pipedog/transport-livekit`, `@pipedog/heygen` and `@pipedog/transport-heygen`
(LiveKit's media SDK is a native addon, which Workers can't load), the local models
`@pipedog/kokoro`, `@pipedog/moonshine`, `@pipedog/funasr`, `@pipedog/pocket-tts` and the eSpeak NG
phonemizer `@pipedog/espeak-ng` (model files and memory exceed what Workers allow), `@pipedog/transport-local`
(it runs sox or ffmpeg on your machine), `@pipedog/mcp` (a stdio tool for Bun and Node) and `@pipedog/cli` (a command line tool: Bun, Node and Deno). The harness checks that each
package loads and runs its smoke test; inside a verified package, a few entry points still need a
server runtime:

| Entry point                                                                                             | Runs on                       | Why                                                                                                 |
| ------------------------------------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- |
| Google Cloud Speech-to-Text and Text-to-Speech, and Gemini TTS on GCP (`@pipedog/google`)               | Bun, Node, Deno               | gRPC over HTTP/2 through Google's Node SDKs. The Gemini API services run on Workers too.            |
| `SageMakerBidiClient` (`@pipedog/aws`)                                                                  | Bun, Node 22+ (Deno untested) | Full-duplex HTTP/2 through the AWS SDK.                                                             |
| Nova Sonic with `transport: "http2"` (`@pipedog/aws`)                                                   | Bun, Node (Deno untested)     | The default WebSocket transport runs everywhere, Workers included.                                  |
| `main()`, `serve()` (`@pipedog/runner`)                                                                 | Bun, Node (with `ws`), Deno   | They listen on a port. On Workers, use the fetch handler from `createRunnerApp()` (untested there). |
| `serveFetch`, `websocketServe`, `SingleClientWebsocketServerTransport` (`@pipedog/transport-websocket`) | Bun, Node (with `ws`), Deno   | They listen on a port. On Workers, use `websocketFetchHandler`.                                     |

Local turn detection (`@pipedog/audio-silero`, `@pipedog/audio-smart-turn`) runs on Workers with
`onnxruntime-web`, but the models and runtime are large; provider-side turn detection is the usual
choice there. See [Turn detection](https://polvallverdu.github.io/pipedog/docs/concepts/turn-detection.md).

## What core avoids [#what-core-avoids]

To stay portable, `@pipedog/core` and `@pipedog/asyncio`:

* import no `node:*` modules except `node:async_hooks` (for `AsyncLocalStorage`, in asyncio);
* use `Uint8Array` for bytes, never `Buffer` (PCM is 16-bit little-endian);
* use no Bun- or Deno-specific APIs;
* use the global `fetch` and `WebSocket` for network I/O;
* treat process signals as optional (`WorkerRunner` handles SIGINT/SIGTERM only where the runtime
  has them).

## Bun, Node and Deno [#bun-node-and-deno]

The npm packages ship compiled JavaScript, and from a checkout Bun and Deno run the TypeScript
sources directly (Node with its built-in type stripping). `WorkerRunner` installs a SIGINT handler
by default, so Ctrl-C cancels the bot cleanly. Node needs version 22 or later.

On Bun and Node, a WebSocket is upgraded by the server rather than from the `Request`:
`serveFetch()` from `@pipedog/transport-websocket/serve` serves a fetch handler with upgrades on
Bun, Node (with the `ws` package) and Deno, and on Bun you can also pass the server `Bun.serve`
gives `fetch` to `websocketFetchHandler`'s handler. See [WebSocket](https://polvallverdu.github.io/pipedog/docs/transports/websocket.md).

## Cloudflare Workers [#cloudflare-workers]

Workers are a good fit for pipedog bots that talk to clients over WebSockets, with a few
constraints:

* **Enable `nodejs_compat`.** Task tracking uses `AsyncLocalStorage` from `node:async_hooks`,
  which workerd provides under the `nodejs_compat` compatibility flag.

  ```jsonc title="wrangler.jsonc"
  {
    "compatibility_flags": ["nodejs_compat"]
  }
  ```

* **No WebRTC.** Workers can't run WebRTC, so the WebRTC-based transports (Daily, SmallWebRTC,
  LiveKit) don't work there. Use a **WebSocket transport** instead; WebSocket-based transports and
  serializers must work on Workers. Pipecat clients can connect over WebSocket with RTVI as usual.
  For WebRTC users, [Cloudflare Realtime](https://polvallverdu.github.io/pipedog/docs/transports/cloudflare-realtime.md) is the WebRTC peer
  and streams the audio to the bot over WebSockets.

* **WASM is imported as a module.** workerd refuses to compile WebAssembly from bytes at runtime
  (`WebAssembly.compile(buffer)`), so a `.wasm` file must be imported. Packages with WASM ship two
  entries selected by the **`workerd` export condition**, which wrangler resolves automatically:

  ```json title="package.json"
  {
    "exports": {
      ".": {
        "types": "./src/index.ts",
        "workerd": "./src/workerd.ts",
        "default": "./src/index.ts"
      }
    }
  }
  ```

  The `workerd` entry does `import wasm from "../wasm/x.wasm"` and instantiates that
  `WebAssembly.Module`; the default entry loads the bytes (a `fetch` of
  `new URL(..., import.meta.url)`). Both accept an explicit `WebAssembly.Module | BufferSource` so
  apps can supply their own. `@pipedog/resampler-wasm` is built this way.

* **Models are never bundled.** Packages that need a model (Silero VAD, Smart Turn) load it from a
  path or URL you give them, with the upstream file name as the default. On Workers, serve it from
  R2, KV, static assets or a `fetch`.

* **No threads, limited CPU.** Upstream work done in a thread pool (`run_in_executor`) runs inline
  or in an injected runner. Keep per-call CPU low: Workers have CPU-time limits.

* **No process signals.** `handleSigint` is a no-op; end the runner from your Worker code (for
  example when the WebSocket closes) with `runner.end()` or `runner.cancel()`.

* **Many sessions per isolate.** One isolate serves many requests and Durable Objects.
  `@pipedog/asyncio` runs each callback in the I/O context that scheduled it, so parallel sessions
  in separate Durable Objects don't do each other's I/O. `examples/workers-durable-voice-agent`
  tests this with parallel sessions on workerd.

## Native and ML dependencies [#native-and-ml-dependencies]

Where Pipecat uses native modules (onnxruntime, Krisp, Koala, soxr), the pipedog package takes
the model as an external file or URL and accepts an injected runtime: `onnxruntime-node` on
Bun/Node, `onnxruntime-web` (WASM) on Workers. Pure-algorithm fallbacks, such as a linear
resampler, may live in core.
