npm package: @koda-sl/baker-bridge
Description: HTTP server wrapping the Claude Agent SDK with SSE streaming, WebSocket, and async endpoints
Latest version: 0.31.1

# @koda-sl/baker-bridge

HTTP server wrapping the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) with WebSocket, SSE streaming, and async endpoints. Designed for running Claude Code as a headless agent with real-time bidirectional communication and Convex callback relay.

## Installation

```bash
npm install @koda-sl/baker-bridge
# or
pnpm add @koda-sl/baker-bridge
```

## Quick Start

### CLI

```bash
npx @koda-sl/baker-bridge hono
```

### Programmatic

```ts
import { createServer } from "@koda-sl/baker-bridge/hono";

const server = createServer();
await server.start();
// Server listening on http://0.0.0.0:3000
```

## Environment Variables

| Variable | Description |
|---|---|
| `AUTH_TOKEN` | Bearer token for authenticating requests |
| `BAKER_CONVEX_SITE_URL` | Convex site URL for callback relay (e.g. `https://your-deployment.convex.site`) |
| `BAKER_API_KEY` | API key for Convex callbacks (`bk_...`) |
| `ANTHROPIC_API_KEY` | Anthropic API key (required by the Agent SDK) |

All variables are validated at startup via `@t3-oss/env-core` + `zod`.

## Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/health` | None | Health check — returns `{ status, projectDir }` |
| `GET` | `/ws` | Query param `?token=` | WebSocket — persistent bidirectional chat |
| `POST` | `/message/async` | Bearer token | Fire-and-forget with Convex callback |
| `POST` | `/answer-question` | Bearer token | Resolve a pending `AskUserQuestion` tool call |

## WebSocket Protocol

Connect to `/ws?token=<AUTH_TOKEN>` for real-time bidirectional communication.

### Client to Server

**Chat message:**

```json
{
  "type": "chat",
  "threadId": "thread_abc123",
  "content": "Build a landing page for our new product",
  "attachments": [
    { "url": "https://...", "contentType": "image/png", "filename": "mockup.png" }
  ]
}
```

**Answer a pending question:**

```json
{
  "type": "answer",
  "threadId": "thread_abc123",
  "toolUseId": "tool_xyz",
  "answers": { "question_key": "user response" }
}
```

### Server to Client

The server sends Claude Agent SDK events as they stream:

- `{ type: "assistant", data: ... }` — assistant message chunks
- `{ type: "tool_use", data: ... }` — tool invocations
- `{ type: "input_request", toolUseId, questions }` — agent needs user input
- `{ type: "result", costUsd, isError, errors? }` — turn complete

All non-streaming events are also relayed to Convex via `POST /api/chat/event`.

## Async Endpoint

For server-to-server dispatch where you don't need real-time streaming. Returns `202 Accepted` immediately and processes in the background. Results are delivered via Convex callbacks.

```bash
curl -X POST http://localhost:3000/message/async \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"threadId": "thread_abc123", "prompt": "Analyze the campaign performance"}'
```

On completion, the bridge calls:
- `POST /api/chat/event` — each SDK event (excluding stream events)
- `POST /api/chat/complete` — final callback with `{ threadId, isError, costUsd, errors? }`

## Architecture

- **One session per thread** — `getOrCreateSession(threadId)` ensures all messages on the same thread share an `AgentSession`, whether they arrive via WebSocket or the async endpoint.
- **Multi-turn via resume** — the first message creates a fresh `query()`. Subsequent messages resume with the `sessionId` so conversation context carries over.
- **Attachment handling** — file URLs are downloaded to `/tmp/attachments` and appended to the prompt so the agent can use its `Read` tool to view them.
- **Convex relay** — all SDK events (except `stream_event`) are posted to the Convex backend with exponential backoff retry (3 attempts).

## Development

```bash
# From monorepo root
pnpm --filter @koda-sl/baker-bridge dev    # Watch mode with tsx
pnpm --filter @koda-sl/baker-bridge build  # Compile to dist/
```

### Local linking

```bash
pnpm --filter @koda-sl/baker-bridge link:local    # Build + link for local testing
pnpm --filter @koda-sl/baker-bridge unlink:local  # Remove link, restore registry version
```

## Publishing

### Auto-publish (CI)

Pushing to `main` with changes in `packages/bridge/` triggers the GitHub Actions workflow, which publishes to npm with the `latest` tag by default. To publish a pre-release, use the workflow dispatch with `npm_tag: next`.

### Manual publish

From the monorepo root:

```bash
# Publish as @latest (production)
./scripts/publish-package.sh bridge

# Publish as @next (pre-release)
./scripts/publish-package.sh bridge next
```

The script checks for duplicate versions, builds, and publishes. Bump the version in `package.json` before publishing.

### Testing a pre-release in sandboxes

When you publish a `@next` version, sandboxes won't use it automatically — they use the version baked into the template. To override:

```bash
# Point sandboxes to the pre-release version
npx convex env set BAKER_BRIDGE_VERSION <version>
```

The sandbox startup (`nodeActions.ts`) checks this env var and runs `npm install -g @koda-sl/baker-bridge@<version>` before starting. This is optional — when unset, the default template version is used.

After testing, always clean up to avoid running pre-release in production:

```bash
npx convex env remove BAKER_BRIDGE_VERSION
```

## Requirements

- Node.js 18+
- `ANTHROPIC_API_KEY` with access to the Claude Agent SDK