Package Exports
- @headless-coder-sdk/core
- @headless-coder-sdk/core/factory
- @headless-coder-sdk/core/package.json
- @headless-coder-sdk/core/types
Readme
headless-coder-sdk
Unified SDK for headless AI coders (Codex, Claude, Gemini)
Headless Coder SDK unifies multiple headless AI-coder SDKs - OpenAI Codex, Anthropic Claude Agent, and Google Gemini CLI - under one consistent interface.
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.
đ Why use it?
- Avoid vendor lock-in between AI-coder SDKs
- Unified threads and streaming API
- Structured output and sandbox enforcement
- Works in Node, Electron, or CI pipelines
- Extensible - add your own adapters easily
đĻ Packages
@headless-coder-sdk/coreâ Shared types and thecreateCoderfactory@headless-coder-sdk/codex-adapterâ Wraps the OpenAI Codex SDK@headless-coder-sdk/claude-adapterâ Wraps Anthropic Claude Agent SDK@headless-coder-sdk/gemini-adapterâ Invokes the Gemini CLI (headless mode)@headless-coder-sdk/examplesâ Example scripts demonstrating runtime wiring
đ§ Quickstart
npm i @headless-coder-sdk/core @headless-coder-sdk/codex-adapterimport { registerAdapter, createCoder } from '@headless-coder-sdk/core';
import { CODER_NAME as CODEX, createAdapter as createCodex } from '@headless-coder-sdk/codex-adapter';
registerAdapter(CODEX, createCodex);
const coder = createCoder(CODEX);
const thread = await coder.startThread();
const result = await thread.run('Write a hello world script');
console.log(result.text);âļī¸ Basic Run (Codex)
import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory';
import { CODER_NAME as CODEX_CODER, createAdapter as createCodexAdapter } from '@headless-coder-sdk/codex-adapter';
registerAdapter(CODEX_CODER, createCodexAdapter);
const coder = createCoder(CODEX_CODER, { workingDirectory: process.cwd() });
const thread = await coder.startThread();
const result = await thread.run('Generate a test plan for the API gateway.');
console.log(result.text);đ Streaming Example (Claude)
import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory';
import { CODER_NAME as CLAUDE_CODER, createAdapter as createClaudeAdapter } from '@headless-coder-sdk/claude-adapter';
registerAdapter(CLAUDE_CODER, createClaudeAdapter);
const claude = createCoder(CLAUDE_CODER, {
workingDirectory: process.cwd(),
permissionMode: 'bypassPermissions',
});
const thread = await claude.startThread();
for await (const event of thread.runStreamed('Plan end-to-end tests')) {
if (event.type === 'message' && event.role === 'assistant') {
process.stdout.write(event.delta ? event.text ?? '' : `\n${event.text}\n`);
}
}
const resumed = await claude.resumeThread(thread.id!);
const followUp = await resumed.run('Summarise the agreed test plan.');
console.log(followUp.text);đ§Š Structured Output Example (Gemini)
import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory';
import { CODER_NAME as GEMINI_CODER, createAdapter as createGeminiAdapter } from '@headless-coder-sdk/gemini-adapter';
registerAdapter(GEMINI_CODER, createGeminiAdapter);
const gemini = createCoder(GEMINI_CODER, {
workingDirectory: process.cwd(),
includeDirectories: [process.cwd()],
});
const thread = await gemini.startThread();
const turn = await thread.run('Summarise the repo in JSON', {
outputSchema: {
type: 'object',
properties: {
summary: { type: 'string' },
components: { type: 'array', items: { type: 'string' } },
},
required: ['summary', 'components'],
},
});
console.log(turn.json);â ī¸ Gemini CLI resume support is pending upstream (PR #10719).
đ Resume Example (Codex)
import { registerAdapter, createCoder } from '@headless-coder-sdk/core/factory';
import { CODER_NAME as CODEX_CODER, createAdapter as createCodexAdapter } from '@headless-coder-sdk/codex-adapter';
registerAdapter(CODEX_CODER, createCodexAdapter);
const codex = createCoder(CODEX_CODER, {
workingDirectory: process.cwd(),
sandboxMode: 'workspace-write',
skipGitRepoCheck: true,
});
const session = await codex.startThread({ model: 'gpt-5-codex' });
await session.run('Draft a CLI plan.');
const resumed = await codex.resumeThread(session.id!);
const followUp = await resumed.run('Continue with implementation details.');
console.log(followUp.text);đ Multi-Provider Workflow
import {
registerAdapter,
createCoder,
} from '@headless-coder-sdk/core/factory';
import {
CODER_NAME as CODEX,
createAdapter as createCodex,
} from '@headless-coder-sdk/codex-adapter';
import {
CODER_NAME as CLAUDE,
createAdapter as createClaude,
} from '@headless-coder-sdk/claude-adapter';
import {
CODER_NAME as GEMINI,
createAdapter as createGemini,
} from '@headless-coder-sdk/gemini-adapter';
registerAdapter(CODEX, createCodex);
registerAdapter(CLAUDE, createClaude);
registerAdapter(GEMINI, createGemini);
const reviewSchema = {
type: 'object',
properties: {
issues: {
type: 'array',
items: {
type: 'object',
properties: {
file: { type: 'string' },
description: { type: 'string' },
severity: { type: 'string', enum: ['high', 'medium', 'low'] },
},
required: ['file', 'description', 'severity'],
},
},
},
required: ['issues'],
} as const;
async function runMultiProviderReview(commitHash: string) {
const [claude, codex] = [createCoder(CLAUDE), createCoder(CODEX)];
const [claudeThread, codexThread] = await Promise.all([
claude.startThread(),
codex.startThread(),
]);
const reviewPrompt = (name: string) =>
`Review commit ${commitHash} and provide structured findings as ${name}. Focus on regressions, tests, and security.`;
const [claudeReview, codexReview] = await Promise.all([
claudeThread.run(reviewPrompt('Claude'), { outputSchema: reviewSchema }),
codexThread.run(reviewPrompt('Codex'), { outputSchema: reviewSchema }),
]);
const combinedIssues = [
...(claudeReview.json?.issues ?? []),
...(codexReview.json?.issues ?? []),
];
const gemini = createCoder(GEMINI, { workingDirectory: process.cwd() });
const geminiThread = await gemini.startThread();
for (const issue of combinedIssues) {
await geminiThread.run([
{
role: 'system',
content: 'You fix code review issues one at a time. Apply patches directly when possible.',
},
{
role: 'user',
content: `Commit: ${commitHash}\nFile: ${issue.file}\nSeverity: ${issue.severity}\nIssue: ${issue.description}\nPlease fix this issue and describe the change.`,
},
]);
}
await Promise.all([claude.close?.(claudeThread), codex.close?.(codexThread), gemini.close?.(geminiThread)]);
}Two reviewers (Claude and Codex) analyze the same commit concurrently and emit structured findings. Gemini waits until both reviews finish, then applies fixes sequentially based on the shared structured payload.
âī¸ Development
Install
pnpm installBuild
pnpm buildTest
pnpm testRun examples
pnpm run examplesâšī¸ Handling Interrupts
All adapters support cooperative cancellation via RunOpts.signal or thread-level interrupts:
import { AbortController } from 'node-abort-controller';
const coder = createCoder(CODEX_CODER, { workingDirectory: process.cwd() });
const controller = new AbortController();
const thread = await coder.startThread();
const runPromise = thread.run('Generate a summary of CONTRIBUTING.md', { signal: controller.signal });
setTimeout(() => controller.abort('User cancelled'), 2000);When aborted, streams emit a cancelled event and async runs throw an AbortError (code: 'interrupted').
đ§ą Build Your Own Adapter
Want to support another provider?
Follow the Create Your Own Adapter guide - it covers exports, registry usage, event mapping, and sandbox permissions.
đŦ Feedback & Contributing
Contributions welcome!
Open an issue or submit a PR.
Š 2025 Ohad Assulin - MIT License