|
| 1 | +# headless-coder-sdk |
| 2 | +> Unified SDK for headless AI coders (Codex, Claude, Gemini) |
| 3 | +
|
| 4 | +[](https://www.npmjs.com/package/@headless-coder-sdk/core) |
| 5 | +[](LICENSE) |
| 6 | +[](https://github.com/OhadAssulin/headless-coder-sdk/actions) |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +**Headless Coder SDK** unifies multiple *headless AI-coder SDKs* - OpenAI Codex, Anthropic Claude Agent, and Google Gemini CLI - under one consistent interface. |
| 11 | +It standardizes threads, streaming, structured outputs, permissions, and sandboxing, allowing you to build AI coding tools or autonomous agents that switch backends with a single line of code. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## 🚀 Why use it? |
| 16 | +- Avoid vendor lock-in between AI-coder SDKs |
| 17 | +- Unified threads and streaming API |
| 18 | +- Structured output and sandbox enforcement |
| 19 | +- Works in Node, Electron, or CI pipelines |
| 20 | +- Extensible - add your own adapters easily |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## 📦 Packages |
| 25 | + |
| 26 | +- `@headless-coder-sdk/core` – Shared types and the `createCoder` factory |
| 27 | +- `@headless-coder-sdk/codex-adapter` – Wraps the OpenAI Codex SDK |
| 28 | +- `@headless-coder-sdk/claude-adapter` – Wraps Anthropic Claude Agent SDK |
| 29 | +- `@headless-coder-sdk/gemini-adapter` – Invokes the Gemini CLI (headless mode) |
| 30 | +- `@headless-coder-sdk/examples` – Example scripts demonstrating runtime wiring |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## 🧭 Quickstart |
| 35 | + |
| 36 | +```bash |
| 37 | +npm i @headless-coder-sdk/core @headless-coder-sdk/codex-adapter |
| 38 | +``` |
| 39 | + |
| 40 | +```ts |
| 41 | +import { registerAdapter, createCoder } from '@headless-coder-sdk/core'; |
| 42 | +import { CODER_NAME as CODEX, createAdapter as createCodex } from '@headless-coder-sdk/codex-adapter'; |
| 43 | + |
| 44 | +registerAdapter(CODEX, createCodex); |
| 45 | + |
| 46 | +const coder = createCoder(CODEX); |
| 47 | +const thread = await coder.startThread(); |
| 48 | +const result = await thread.run('Write a hello world script'); |
| 49 | +console.log(result.text); |
| 50 | +``` |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## ▶️ Basic Run (Codex) |
| 55 | + |
| 56 | +```ts |
| 57 | +import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory'; |
| 58 | +import { CODER_NAME as CODEX_CODER, createAdapter as createCodexAdapter } from '@headless-coder-sdk/codex-adapter'; |
| 59 | + |
| 60 | +registerAdapter(CODEX_CODER, createCodexAdapter); |
| 61 | + |
| 62 | +const coder = createCoder(CODEX_CODER, { workingDirectory: process.cwd() }); |
| 63 | +const thread = await coder.startThread(); |
| 64 | +const result = await thread.run('Generate a test plan for the API gateway.'); |
| 65 | +console.log(result.text); |
| 66 | +``` |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## 🌊 Streaming Example (Claude) |
| 71 | + |
| 72 | +```ts |
| 73 | +import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory'; |
| 74 | +import { CODER_NAME as CLAUDE_CODER, createAdapter as createClaudeAdapter } from '@headless-coder-sdk/claude-adapter'; |
| 75 | + |
| 76 | +registerAdapter(CLAUDE_CODER, createClaudeAdapter); |
| 77 | + |
| 78 | +const claude = createCoder(CLAUDE_CODER, { |
| 79 | + workingDirectory: process.cwd(), |
| 80 | + permissionMode: 'bypassPermissions', |
| 81 | +}); |
| 82 | + |
| 83 | +const thread = await claude.startThread(); |
| 84 | +for await (const event of thread.runStreamed('Plan end-to-end tests')) { |
| 85 | + if (event.type === 'message' && event.role === 'assistant') { |
| 86 | + process.stdout.write(event.delta ? event.text ?? '' : `\n${event.text}\n`); |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +const resumed = await claude.resumeThread(thread.id!); |
| 91 | +const followUp = await resumed.run('Summarise the agreed test plan.'); |
| 92 | +console.log(followUp.text); |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## 🧩 Structured Output Example (Gemini) |
| 98 | + |
| 99 | +```ts |
| 100 | +import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory'; |
| 101 | +import { CODER_NAME as GEMINI_CODER, createAdapter as createGeminiAdapter } from '@headless-coder-sdk/gemini-adapter'; |
| 102 | + |
| 103 | +registerAdapter(GEMINI_CODER, createGeminiAdapter); |
| 104 | + |
| 105 | +const gemini = createCoder(GEMINI_CODER, { |
| 106 | + workingDirectory: process.cwd(), |
| 107 | + includeDirectories: [process.cwd()], |
| 108 | +}); |
| 109 | + |
| 110 | +const thread = await gemini.startThread(); |
| 111 | +const turn = await thread.run('Summarise the repo in JSON', { |
| 112 | + outputSchema: { |
| 113 | + type: 'object', |
| 114 | + properties: { |
| 115 | + summary: { type: 'string' }, |
| 116 | + components: { type: 'array', items: { type: 'string' } }, |
| 117 | + }, |
| 118 | + required: ['summary', 'components'], |
| 119 | + }, |
| 120 | +}); |
| 121 | + |
| 122 | +console.log(turn.json); |
| 123 | +``` |
| 124 | + |
| 125 | +> ⚠️ Gemini CLI resume support is pending upstream ([PR #10719](https://github.com/google-gemini/gemini-cli/pull/10719)). |
| 126 | +
|
| 127 | +--- |
| 128 | + |
| 129 | +## 🔁 Resume Example (Codex) |
| 130 | + |
| 131 | +```ts |
| 132 | +import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory'; |
| 133 | +import { CODER_NAME as CODEX_CODER, createAdapter as createCodexAdapter } from '@headless-coder-sdk/codex-adapter'; |
| 134 | + |
| 135 | +registerAdapter(CODEX_CODER, createCodexAdapter); |
| 136 | + |
| 137 | +const codex = createCoder(CODEX_CODER, { |
| 138 | + workingDirectory: process.cwd(), |
| 139 | + sandboxMode: 'workspace-write', |
| 140 | + skipGitRepoCheck: true, |
| 141 | +}); |
| 142 | + |
| 143 | +const session = await codex.startThread({ model: 'gpt-5-codex' }); |
| 144 | +await session.run('Draft a CLI plan.'); |
| 145 | + |
| 146 | +const resumed = await codex.resumeThread(session.id!); |
| 147 | +const followUp = await resumed.run('Continue with implementation details.'); |
| 148 | +console.log(followUp.text); |
| 149 | +``` |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## 🔄 Multi-Provider Workflow |
| 154 | + |
| 155 | +For a full multi-coder workflow (Codex + Claude + Gemini), see [examples/multi-provider.ts](packages/examples/src/multi-provider.ts). |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## ⚙️ Development |
| 160 | + |
| 161 | +**Install** |
| 162 | +```bash |
| 163 | +pnpm install |
| 164 | +``` |
| 165 | + |
| 166 | +**Build** |
| 167 | +```bash |
| 168 | +pnpm build |
| 169 | +``` |
| 170 | + |
| 171 | +**Test** |
| 172 | +```bash |
| 173 | +pnpm test |
| 174 | +``` |
| 175 | + |
| 176 | +**Run examples** |
| 177 | +```bash |
| 178 | +pnpm run examples |
| 179 | +``` |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## ⏹️ Handling Interrupts |
| 184 | + |
| 185 | +All adapters support cooperative cancellation via `RunOpts.signal` or thread-level interrupts: |
| 186 | + |
| 187 | +```ts |
| 188 | +import { AbortController } from 'node-abort-controller'; |
| 189 | + |
| 190 | +const coder = createCoder(CODEX_CODER, { workingDirectory: process.cwd() }); |
| 191 | +const controller = new AbortController(); |
| 192 | +const thread = await coder.startThread(); |
| 193 | +const runPromise = thread.run('Generate a summary of CONTRIBUTING.md', { signal: controller.signal }); |
| 194 | + |
| 195 | +setTimeout(() => controller.abort('User cancelled'), 2000); |
| 196 | +``` |
| 197 | + |
| 198 | +When aborted, streams emit a `cancelled` event and async runs throw an `AbortError` (`code: 'interrupted'`). |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +## 🧱 Build Your Own Adapter |
| 203 | + |
| 204 | +Want to support another provider? |
| 205 | +Follow the [Create Your Own Adapter guide](docs/create-your-own-adapter.md) - it covers exports, registry usage, event mapping, and sandbox permissions. |
| 206 | + |
| 207 | +--- |
| 208 | + |
| 209 | +## 💬 Feedback & Contributing |
| 210 | + |
| 211 | +Contributions welcome! |
| 212 | +Open an [issue](https://github.com/OhadAssulin/headless-coder-sdk/issues) or submit a PR. |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +© 2025 Ohad Assulin - MIT License |
0 commit comments