# `@av-pi-studio/protocol`

Shared wire schemas, binary frame codecs, and capability flags for Pi-Studio. This package is the
**single source of truth for everything that crosses the WebSocket** between the daemon and any
client (CLI, web, mobile, desktop).

It is a **zero-runtime-workspace-dependency** library — the only external dependency is `zod` —
so it can be imported by browser and React Native clients as freely as by the Node.js daemon.

---

## Install

```bash
npm install @av-pi-studio/protocol
```

## What's in here

- **JSON wire schemas** (`messages.ts`) — every message type that can appear on the WebSocket,
  as [Zod](https://zod.dev) schemas plus their inferred TypeScript types: the `hello`/`status`
  handshake, RPC requests/responses, broadcasts (`agent_update`, `agent_stream`, …), and the
  top-level envelope union that wraps them all.
- **Binary frame codecs** (`binary-frames/`) — encode/decode functions for the two binary wire
  formats: terminal I/O (`[opcode][slot][payload]`) and chunked file transfer. Both use
  `Uint8Array`, never `Buffer`, so they run unchanged in a browser.
- **Client capability flags** (`client-capabilities.ts`) — `CLIENT_CAPS` (what a client can
  advertise in `hello.capabilities`) and `SERVER_FEATURES` (what the daemon advertises in
  `server_info.features`), plus a `supports()` helper.
- **Endpoint parsing** (`endpoint.ts`) — turns a daemon target string (`host:port`, `ws://…`,
  `relay://…`) into a structured `EndpointDescriptor`.
- **Provider manifest types** (`provider-manifest.ts`) — UI-facing scaffolding for describing
  providers/modes (labels, color tiers, capability flags).

## Usage

```ts
import {
  helloSchema,
  agentStreamEventSchema,
  encodeTerminalFrame,
  decodeTerminalFrame,
  parseEndpoint,
  rpcName,
} from "@av-pi-studio/protocol";

// Validate + parse an inbound message
const hello = helloSchema.parse(rawJson);

// Build a canonical dotted RPC name
const name = rpcName("agent", "permission", "respond", "request");
// → "agent.permission.respond.request"

// Parse a daemon connection target
const endpoint = parseEndpoint("workstation.local:6767");
// → { kind: "direct", host: "workstation.local", port: 6767, ssl: false, raw: "…" }
```

Every exported schema has a paired TypeScript type (`XSchema` / `X`), inferred with `z.infer<>` —
import whichever you need.

## Design rules

These are enforced by convention and by the package's own tests, not by tooling:

- **Append-only.** Fields are only ever added, and only as optional. Nothing is ever removed,
  narrowed, or given a new discriminant value. This is what lets an older daemon load data
  written by a newer one (and vice versa) without a migration step.
- **Schema passthrough.** Most schemas use Zod's `.passthrough()`, so unknown fields from a newer
  protocol version survive a round-trip through an older decoder instead of being stripped or
  rejected.
- **Zero workspace imports.** This package never imports from another `@av-pi-studio/*` package —
  it has to stay usable standalone by any future client, including ones that don't otherwise
  depend on the rest of the monorepo.
- **Cross-platform binary codecs.** The binary frame encode/decode functions only use
  `Uint8Array` — no Node-only APIs — so they work identically in Node, the browser, and React
  Native.
- **Dotted RPC names.** New RPCs are named via `rpcName(domain, sub, op, dir)` →
  `"domain.provider.operation.direction"` (e.g. `agent.permission.respond.request`). Legacy flat
  names are still accepted on the wire via aliases, but never generated by new code.

## Adding a new wire message type

1. Define the Zod schema (and its inferred type) in `messages.ts` or a new sibling file.
2. Add it to the `sessionMessageSchema` discriminated union.
3. Export it from `index.ts`.
4. Add a round-trip test (`messages.envelopes.test.ts` or a new `*.test.ts`).
5. Register the corresponding handler in the daemon's `HandlerRegistry`
   (`@av-pi-studio/server`).

## Development

```bash
npm run build       # tsc -b
npx vitest run packages/protocol
```

Tests are Vitest and live alongside their source files as `*.test.ts`.
