|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +This is a **monorepo** for the **headless-coder-sdk** - a unified TypeScript SDK that provides a consistent interface for multiple headless AI coders (Codex, Claude, Gemini). It uses **pnpm workspaces** and is published as separate npm packages under the `@headless-coder-sdk/*` scope. |
| 8 | + |
| 9 | +The SDK standardizes threads, streaming, structured outputs, permissions, and sandboxing across different AI coder backends, allowing seamless switching with a single line of code. |
| 10 | + |
| 11 | +## Build & Development Commands |
| 12 | + |
| 13 | +**Install dependencies:** |
| 14 | +```bash |
| 15 | +pnpm install |
| 16 | +``` |
| 17 | + |
| 18 | +**Build all packages:** |
| 19 | +```bash |
| 20 | +pnpm build |
| 21 | +# Internally runs: node ./scripts/run-workspaces.mjs build |
| 22 | +``` |
| 23 | + |
| 24 | +**Lint:** |
| 25 | +```bash |
| 26 | +pnpm lint |
| 27 | +``` |
| 28 | + |
| 29 | +**Test all packages:** |
| 30 | +```bash |
| 31 | +pnpm test |
| 32 | +# Internally runs: node ./scripts/run-workspaces.mjs test |
| 33 | +``` |
| 34 | + |
| 35 | +**Run smoke tests:** |
| 36 | +```bash |
| 37 | +pnpm smoke |
| 38 | +# Set HEADLESS_CODER_KEEP_SMOKE_TMP=1 to keep temp project for inspection |
| 39 | +``` |
| 40 | + |
| 41 | +**Run ACP server E2E tests:** |
| 42 | +```bash |
| 43 | +pnpm acp:e2e |
| 44 | +``` |
| 45 | + |
| 46 | +**Work with specific packages:** |
| 47 | +```bash |
| 48 | +# Build a single package |
| 49 | +pnpm --filter @headless-coder-sdk/core build |
| 50 | + |
| 51 | +# Run ACP server in dev mode |
| 52 | +pnpm --filter @headless-coder-sdk/acp-server dev |
| 53 | +``` |
| 54 | + |
| 55 | +**Run examples:** |
| 56 | +```bash |
| 57 | +pnpm run examples |
| 58 | +``` |
| 59 | + |
| 60 | +## Architecture |
| 61 | + |
| 62 | +### Core Design Pattern |
| 63 | + |
| 64 | +The SDK uses an **adapter pattern** with a **central registry** to provide unified access to different AI coder backends: |
| 65 | + |
| 66 | +1. **`@headless-coder-sdk/core`** - Defines shared types (`HeadlessCoder`, `ThreadHandle`, `CoderStreamEvent`) and the adapter registry (`factory.ts`) |
| 67 | +2. **Adapter packages** - Each adapter (codex, claude, gemini) implements the `HeadlessCoder` interface and exports: |
| 68 | + - `CODER_NAME` - unique provider identifier constant |
| 69 | + - `createAdapter()` - factory function with `coderName` property for auto-registration |
| 70 | + - `createHeadless*()` - convenience helper that registers and returns coder in one call |
| 71 | + |
| 72 | +### Key Interfaces (packages/core/src/types.ts) |
| 73 | + |
| 74 | +- **`HeadlessCoder`** - Main interface with `startThread()`, `resumeThread()`, `getThreadId()`, `close()` |
| 75 | +- **`ThreadHandle`** - Returned by startThread/resumeThread, has `run()` and `runStreamed()` methods |
| 76 | +- **`CoderStreamEvent`** - Unified event types: `init`, `message`, `tool_use`, `tool_result`, `progress`, `permission`, `file_change`, `plan_update`, `usage`, `error`, `cancelled`, `done` |
| 77 | +- **`RunResult`** - Contains `threadId`, `text`, `json`, `usage`, `raw` |
| 78 | + |
| 79 | +### Registry Pattern (packages/core/src/factory.ts) |
| 80 | + |
| 81 | +Adapters register themselves via `registerAdapter(factory)` which reads the factory's `coderName` property. Users then call `createCoder(CODER_NAME, opts)` to instantiate. |
| 82 | + |
| 83 | +### Workspace Structure |
| 84 | + |
| 85 | +``` |
| 86 | +packages/ |
| 87 | +├── core/ # Shared types, factory, registry |
| 88 | +├── codex-adapter/ # OpenAI Codex SDK wrapper (server-only, Node.js) |
| 89 | +├── claude-adapter/ # Anthropic Claude Agent SDK wrapper |
| 90 | +├── gemini-adapter/ # Google Gemini CLI wrapper |
| 91 | +├── acp-server/ # Next.js ACP protocol server (REST + streaming) |
| 92 | +└── examples/ # Example scripts demonstrating runtime wiring |
| 93 | +``` |
| 94 | + |
| 95 | +### Server-Only Adapters |
| 96 | + |
| 97 | +**Important:** The Codex adapter (and other CLI-based adapters) must run in Node.js environments. They cannot run in browser/client contexts. In frameworks like Next.js: |
| 98 | +- Use lazy imports in server components/API routes: `const { createHeadlessCodex } = await import('@headless-coder-sdk/codex-adapter')` |
| 99 | +- Routes should export `runtime = 'nodejs'` |
| 100 | +- Gate with `if (typeof window !== 'undefined')` checks |
| 101 | + |
| 102 | +### Stream Event Mapping |
| 103 | + |
| 104 | +Each adapter normalizes provider-specific events into the unified `CoderStreamEvent` schema. The `originalItem` field always preserves the raw provider event for debugging. |
| 105 | + |
| 106 | +Adapters must emit at minimum: `init` → `message` → `done` |
| 107 | + |
| 108 | +### Cancellation & Interrupts |
| 109 | + |
| 110 | +All adapters support cooperative cancellation: |
| 111 | +- `RunOpts.signal` (AbortSignal) for individual runs |
| 112 | +- `thread.interrupt(reason?)` for thread-level cancellation |
| 113 | +- Aborted runs throw `AbortError` with `code: 'interrupted'` |
| 114 | +- Streams emit a `cancelled` event before ending |
| 115 | + |
| 116 | +## Package Build Configuration |
| 117 | + |
| 118 | +Each publishable package uses **tsup** for dual ESM/CJS builds: |
| 119 | +- Entry points at `dist/*.js` (ESM) and `dist/*.cjs` (CJS) |
| 120 | +- Type definitions at `dist/*.d.ts` |
| 121 | +- Exports defined via `package.json` "exports" field for proper resolution |
| 122 | +- All packages emit flattened entry points (no deep `dist/*/src` paths) |
| 123 | + |
| 124 | +## Testing |
| 125 | + |
| 126 | +- **Unit tests:** Verify provider event mapping to `CoderStreamEvent` |
| 127 | +- **Integration tests:** Test `init → message → done` sequences |
| 128 | +- **Smoke tests:** Build, pack tarballs, install in throwaway project, exercise both ESM/CJS |
| 129 | +- **ACP E2E:** Test the ACP server's REST + streaming endpoints |
| 130 | + |
| 131 | +Test files use Node.js test runner (`tsx --test`) and are located in `test/*.test.ts` within adapter packages. |
| 132 | + |
| 133 | +## ACP Server (packages/acp-server) |
| 134 | + |
| 135 | +A Next.js application that exposes the Headless Coder SDK via the Agent Communication Protocol: |
| 136 | +- Dynamic provider config via `acp.config.json` (validated against schema) |
| 137 | +- NDJSON streaming for real-time responses |
| 138 | +- Optional Bearer token auth via `ACP_TOKEN` env var |
| 139 | +- Key routes: |
| 140 | + - `GET /api/acp/agents` - List enabled agents |
| 141 | + - `POST /api/acp/sessions` - Create thread |
| 142 | + - `POST /api/acp/messages?stream=true` - Stream events as NDJSON |
| 143 | + |
| 144 | +Sessions are in-memory by default (add Redis/Postgres for persistence). |
| 145 | + |
| 146 | +## Creating Custom Adapters |
| 147 | + |
| 148 | +See `docs/create-your-own-adapter.md` for detailed guide. Key requirements: |
| 149 | +1. Export `CODER_NAME` constant and `createAdapter()` function |
| 150 | +2. Assign `createAdapter.coderName = CODER_NAME` |
| 151 | +3. Implement `HeadlessCoder` interface |
| 152 | +4. Map provider events to unified `CoderStreamEvent` types |
| 153 | +5. Support cancellation via AbortSignal |
| 154 | +6. Preserve raw events in `originalItem` field |
| 155 | + |
| 156 | +## Distribution Notes |
| 157 | + |
| 158 | +- Helper factories (`createHeadlessCodex/Claude/Gemini`) register adapters and return coders in one call |
| 159 | +- `package.json` exposed via exports map for runtime version inspection |
| 160 | +- Server-only adapters use direct SDK calls with AbortSignal-based cancellation (no worker assets needed) |
0 commit comments