# Vercel

> Serve the pipedog endpoints from a Bun server on Vercel Functions.

Source: https://polvallverdu.github.io/pipedog/docs/frameworks/vercel/



Vercel runs a `server.ts` with the Bun runtime, so the [Bun](https://polvallverdu.github.io/pipedog/docs/frameworks/bun.md) snippet's shape
works, with a `routes` map for static responses.

## Install [#install]

```bash
bun add @pipedog/core @pipedog/asyncio @pipedog/transport-smallwebrtc @pipedog/transport-websocket @pipedog/serializer-protobuf @pipedog/openai @pipedog/audio-silero @pipedog/audio-smart-turn
```

`@pipedog/audio-smart-turn` isn't imported anywhere: the default turn strategies load it. To run
the snippet from a checkout instead, see [Installation](https://polvallverdu.github.io/pipedog/docs/installation/#from-source).

## Files [#files]

Set the runtime in `vercel.json`:

```json title="vercel.json"
{ "bunVersion": "1.x" }
```

The endpoints come from the shared module on the [overview](https://polvallverdu.github.io/pipedog/docs/frameworks/#the-two-endpoints).
`voice.ts` re-exports it:

```ts title="voice.ts"
export { offer, runSocketAgent, socket } from "../shared/voice.ts";

```

```ts title="server.ts"
import { bunWebSocket, type UpgradingServer } from "@pipedog/transport-websocket/fetch";
import { offer, socket } from "./voice.ts";

// Vercel Functions run this server with the Bun runtime (`"bunVersion": "1.x"` in
// vercel.json). Each WebSocket is pinned to one function instance for its lifetime,
// and messages don't cross instances: keep shared state in a database.
const config = {
  routes: {
    "/": new Response("voice agent", { headers: { "content-type": "text/plain" } }),
  },
  fetch(request: Request, server: UpgradingServer) {
    const { pathname } = new URL(request.url);
    if (pathname === "/api/offer") return offer(request);
    // `socket` upgrades with the server Bun passes to `fetch`.
    if (pathname === "/ws") return socket(request, server);
    return new Response("not found", { status: 404 });
  },
  websocket: bunWebSocket,
};

export default config;

```

## Run it [#run-it]

Locally, Bun serves the default export:

```bash
OPENAI_API_KEY=sk-... bun server.ts
```

Then deploy with `vercel deploy`, and set `OPENAI_API_KEY` in the project's environment variables.

## Caveats [#caveats]

* **WebRTC media.** The offer route is mounted, but its media runs over UDP, which a function isn't
  expected to carry. It's untested here. Use the WebSocket for audio on Vercel.
* **One instance per socket.** Each WebSocket is pinned to one function instance for its lifetime,
  and messages don't cross instances. Keep shared state in a database.
* **Duration.** A function's duration limit also ends a long session.
* **Daemons.** Vercel Daemons are always-on workers with no inbound HTTP or TCP traffic, so they
  can't serve these endpoints. They suit background workers that pull their own work.

## Authentication [#authentication]

Neither route has auth. Check the session in `fetch` before `offer` and `socket` run, as in [Authentication](https://polvallverdu.github.io/pipedog/docs/frameworks/#authentication).
