Skip to content

Commit 94ad9f0

Browse files
OhadAssulinclaude
andcommitted
feat(core,claude-adapter): add custom tools support following Claude Agent SDK patterns
Add comprehensive custom tools infrastructure to the headless-coder-sdk: Core package changes: - Add ToolDefinition, ToolHandler, ToolResult, and MCPServer types - Create tools.ts with helper functions: tool(), createMCPServer(), normalizeInputSchema(), getToolName(), getServerToolNames() - Update StartOpts to better document mcpServers and allowedTools - Export tools helpers from core index Claude adapter changes: - Add tools.ts with conversion utilities for bridging generic tools to Claude Agent SDK format - Implement convertToolToClaudeTool() and convertMCPServerToClaudeServer() - Update buildOptions() to auto-convert MCP servers to Claude format - Export tool creation helpers from claude-adapter Examples: - Add claude-custom-tools.test.ts with comprehensive examples - Add custom-tools-simple.ts for quick reference - Demonstrate weather, calculator, and formatter tools - Show streaming, multiple tools, and selective allowlists Documentation: - Add CLAUDE.md file for future Claude Code instances This implementation follows the Claude Agent SDK's tool and createSdkMcpServer patterns while providing a unified interface across all adapters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent e8794f3 commit 94ad9f0

File tree

10 files changed

+992
-2
lines changed

10 files changed

+992
-2
lines changed

CLAUDE.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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

Comments
 (0)