JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 162
  • Score
    100M100P100Q95402F
  • License MIT

Unified SDK for headless AI coders (Codex, Claude, Gemini) with standardized threading, streaming, and sandboxing.

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)

npm version License: MIT Build Status


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 the createCoder factory
  • @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-adapter
import { 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 install

Build

pnpm build

Test

pnpm test

Run 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