@cochon123/cli2api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/adapters/claude.d.ts +27 -0
- package/dist/adapters/claude.js +253 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +51 -0
- package/dist/adapters/codex.js +491 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/cursor.d.ts +31 -0
- package/dist/adapters/cursor.js +231 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/mock.d.ts +2 -0
- package/dist/adapters/mock.js +101 -0
- package/dist/adapters/mock.js.map +1 -0
- package/dist/adapters/opencode.d.ts +27 -0
- package/dist/adapters/opencode.js +208 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/registry.d.ts +33 -0
- package/dist/adapters/registry.js +99 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +18 -0
- package/dist/adapters/types.js +35 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/config.d.ts +126 -0
- package/dist/config.js +121 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +253 -0
- package/dist/index.js.map +1 -0
- package/dist/openrouter/catalog.d.ts +28 -0
- package/dist/openrouter/catalog.js +162 -0
- package/dist/openrouter/catalog.js.map +1 -0
- package/dist/protocol/openai.d.ts +48 -0
- package/dist/protocol/openai.js +236 -0
- package/dist/protocol/openai.js.map +1 -0
- package/dist/protocol/responses.d.ts +57 -0
- package/dist/protocol/responses.js +142 -0
- package/dist/protocol/responses.js.map +1 -0
- package/dist/protocol/tools.d.ts +7 -0
- package/dist/protocol/tools.js +143 -0
- package/dist/protocol/tools.js.map +1 -0
- package/dist/server/app.d.ts +25 -0
- package/dist/server/app.js +425 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/listen.d.ts +18 -0
- package/dist/server/listen.js +30 -0
- package/dist/server/listen.js.map +1 -0
- package/dist/session.d.ts +9 -0
- package/dist/session.js +39 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +192 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/util/process.d.ts +38 -0
- package/dist/util/process.js +292 -0
- package/dist/util/process.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cli2api contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# cli2api
|
|
2
|
+
|
|
3
|
+
Local OpenAI- and OpenRouter-compatible gateway for coding CLIs.
|
|
4
|
+
|
|
5
|
+
Point any OpenAI SDK / benchmark at localhost and run against **Codex, OpenCode, Cursor Agent, or Claude Code** (plus the built-in mock) using the credentials/login already configured in each CLI.
|
|
6
|
+
|
|
7
|
+
> **Local only.** Binds to `127.0.0.1` by default. Not a multi-tenant product. Do not expose your CLI auth to the network.
|
|
8
|
+
|
|
9
|
+
## 30-second start
|
|
10
|
+
|
|
11
|
+
Install the published package globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --global @cochon123/cli2api
|
|
15
|
+
cli2api serve --adapter mock --port 3927
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The npm package is scoped because the unscoped `cli2api` name is already owned by another publisher. The installed executable is still named `cli2api`.
|
|
19
|
+
|
|
20
|
+
Before the first npm release, or to install the current GitHub version directly:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --global github:cochon123/cli2api
|
|
24
|
+
cli2api --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For development from a checkout:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# from the project root
|
|
31
|
+
npm install
|
|
32
|
+
npm run serve -- --adapter mock --port 3927
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The server prints a generated bearer token on stderr if you did not set one. Copy it into your client env:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
OPENAI_BASE_URL=http://127.0.0.1:3927/v1
|
|
39
|
+
OPENAI_API_KEY=<token printed by serve>
|
|
40
|
+
OPENAI_MODEL=mock/echo
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or pin the token yourself:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
CLI2API_TOKEN=dev-secret npm run serve -- --adapter mock
|
|
47
|
+
# OPENAI_API_KEY=dev-secret
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Smoke:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run test:smoke
|
|
54
|
+
npm run doctor
|
|
55
|
+
npm run dev -- completion -m mock/echo -p "hello"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Use a real CLI
|
|
59
|
+
|
|
60
|
+
Requires [`codex`](https://github.com/openai/codex) on your `PATH` and logged in.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run doctor -- --adapter codex
|
|
64
|
+
npm run serve -- --adapter codex --port 3927
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
OPENAI_BASE_URL=http://127.0.0.1:3927/v1
|
|
69
|
+
OPENAI_API_KEY=<token printed by serve>
|
|
70
|
+
OPENAI_MODEL=codex/default
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
One-shot without the server:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run dev -- completion -m codex/default -p "Reply with exactly: pong"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The other adapters use the same commands and model-prefix routing:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# OpenCode: completed JSON reasoning/text blocks, safe `plan` agent, plugins disabled
|
|
83
|
+
npm run completion -- -m opencode/deepseek-v4-flash-free -p "Reply with: pong"
|
|
84
|
+
|
|
85
|
+
# Cursor: native partial reasoning/content deltas, read-only ask mode + sandbox
|
|
86
|
+
npm run completion -- -m cursor/composer-2.5-fast -p "Reply with: pong"
|
|
87
|
+
|
|
88
|
+
# Claude Code: native partial stream-json, plan mode, all built-in/MCP tools disabled
|
|
89
|
+
npm run completion -- -m claude/default -p "Reply with: pong"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Claude Code is implemented from its official headless CLI contract but was not live-tested on the development machine because `claude` was not installed.
|
|
93
|
+
|
|
94
|
+
## CLI
|
|
95
|
+
|
|
96
|
+
| Command | Purpose |
|
|
97
|
+
|---------|---------|
|
|
98
|
+
| `cli2api serve` | Start HTTP gateway |
|
|
99
|
+
| `cli2api doctor` | Check adapters / binaries (`--json`) |
|
|
100
|
+
| `cli2api models` | List model ids |
|
|
101
|
+
| `cli2api completion` | Smoke a prompt |
|
|
102
|
+
| `cli2api adapters` | List adapters |
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx tsx src/index.ts serve --help
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
After `npm run build`, the bin is `cli2api`.
|
|
109
|
+
|
|
110
|
+
## Env vars
|
|
111
|
+
|
|
112
|
+
| Var | Meaning |
|
|
113
|
+
|-----|---------|
|
|
114
|
+
| `CLI2API_ADAPTER` | Default adapter (`mock` \| `codex` \| `opencode` \| `cursor` \| `claude`) |
|
|
115
|
+
| `CLI2API_TOKEN` | Bearer token (auto-generated for the session if unset) |
|
|
116
|
+
| `CLI2API_CODEX_BIN` | Path/name of Codex binary |
|
|
117
|
+
| `CLI2API_OPENCODE_BIN` | Path/name of OpenCode binary |
|
|
118
|
+
| `CLI2API_CURSOR_BIN` | Path/name of Cursor Agent binary |
|
|
119
|
+
| `CLI2API_CLAUDE_BIN` | Path/name of Claude Code binary |
|
|
120
|
+
| `CLI2API_CWD` | Working directory for CLI adapters |
|
|
121
|
+
| `CLI2API_CHILD_ENV_ALLOWLIST` | Comma-separated extra parent env names to explicitly pass to child CLIs |
|
|
122
|
+
| `CLI2API_CONFIG` | Explicit JSON config path |
|
|
123
|
+
| `CLI2API_OPENROUTER_CATALOG` | OpenRouter catalog mode (`runnable` or `mirror`) |
|
|
124
|
+
| `OPENROUTER_API_KEY` | Optional key used only to refresh OpenRouter model metadata |
|
|
125
|
+
|
|
126
|
+
## Config and model aliases
|
|
127
|
+
|
|
128
|
+
JSON config is merged in this order: `$XDG_CONFIG_HOME/cli2api/config.json`, project `.cli2api.json`, then `--config <path>` / `CLI2API_CONFIG`. Later values win; `modelAliases` and `binaries` merge by key.
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"defaultAdapter": "opencode",
|
|
133
|
+
"port": 3927,
|
|
134
|
+
"token": "local-secret",
|
|
135
|
+
"cwd": "/path/to/project",
|
|
136
|
+
"modelAliases": {
|
|
137
|
+
"fast": "opencode/deepseek-v4-flash-free",
|
|
138
|
+
"composer": "cursor/composer-2.5-fast"
|
|
139
|
+
},
|
|
140
|
+
"openRouter": {
|
|
141
|
+
"defaultModel": "anthropic/claude-sonnet-4",
|
|
142
|
+
"catalogMode": "runnable",
|
|
143
|
+
"annotateAvailability": true,
|
|
144
|
+
"modelRoutes": {
|
|
145
|
+
"anthropic/claude-sonnet-4": {
|
|
146
|
+
"adapter": "claude",
|
|
147
|
+
"model": "sonnet"
|
|
148
|
+
},
|
|
149
|
+
"openai/gpt-local": {
|
|
150
|
+
"adapter": "codex",
|
|
151
|
+
"model": "default"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"binaries": {
|
|
156
|
+
"codex": "codex",
|
|
157
|
+
"opencode": "opencode",
|
|
158
|
+
"cursor": "cursor-agent",
|
|
159
|
+
"claude": "claude"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Aliases appear in `/v1/models` and work anywhere a model id is accepted.
|
|
165
|
+
|
|
166
|
+
## API (subset)
|
|
167
|
+
|
|
168
|
+
- `GET /health`
|
|
169
|
+
- `GET /v1/models`
|
|
170
|
+
- `POST /v1/chat/completions` — non-stream + `stream: true` (SSE)
|
|
171
|
+
- `POST /v1/responses` — text/function input, non-stream + semantic SSE events
|
|
172
|
+
- `GET /api/v1/models` — OpenRouter-compatible model catalog
|
|
173
|
+
- `POST /api/v1/chat/completions` — OpenRouter-compatible chat, tools, reasoning, and SSE
|
|
174
|
+
- `POST /api/v1/responses` — OpenRouter path alias for the Responses subset
|
|
175
|
+
|
|
176
|
+
All endpoints require `Authorization: Bearer <token>`.
|
|
177
|
+
|
|
178
|
+
Model ids: `adapter/model` — e.g. `mock/echo`, `codex/default`, `opencode/deepseek-v4-flash-free`, `cursor/composer-2.5-fast`, `claude/sonnet`. OpenCode provider-qualified models keep the second slash, for example `opencode/openrouter/deepseek/deepseek-v4-flash`.
|
|
179
|
+
|
|
180
|
+
## OpenRouter compatibility
|
|
181
|
+
|
|
182
|
+
Point an OpenRouter-oriented client at `http://127.0.0.1:3927/api/v1` and use the same cli2api bearer token. Public OpenRouter model ids are mapped to local CLI models with `openRouter.modelRoutes`; the public id is preserved in responses while routing stays internal.
|
|
183
|
+
|
|
184
|
+
If an application relies on OpenRouter's account-level default and omits `model`, set `openRouter.defaultModel` locally.
|
|
185
|
+
|
|
186
|
+
The OpenRouter catalog has two modes:
|
|
187
|
+
|
|
188
|
+
- `runnable` (default) lists only models cli2api can execute. Configured OpenRouter ids are enriched with metadata from OpenRouter's models API.
|
|
189
|
+
- `mirror` returns the cached OpenRouter catalog so model discovery resembles production. Models without a local route remain visible but return a `model_not_available` error when called.
|
|
190
|
+
|
|
191
|
+
Select the mode in config or with `cli2api serve --openrouter-catalog runnable|mirror`.
|
|
192
|
+
|
|
193
|
+
Metadata is cached for 24 hours at `$XDG_CACHE_HOME/cli2api/openrouter-models.json` (or `~/.cache/cli2api/openrouter-models.json`). Stale data is used if refresh fails. Configure `metadataTtlSeconds`, `metadataCachePath`, or `metadataUrl` under `openRouter` when needed. Pricing fields describe OpenRouter's hosted service, not local CLI execution.
|
|
194
|
+
|
|
195
|
+
For safety, `OPENROUTER_API_KEY` is sent only to the canonical `https://openrouter.ai` origin. Custom `metadataUrl` sources never receive it. Cache entries are tied to their source URL so catalogs cannot be mixed across sources.
|
|
196
|
+
|
|
197
|
+
Set `annotateAvailability` to `true` to add a `cli2api` object to model records with local availability and routing details. Set it to `false` in `mirror` mode for the closest upstream catalog shape.
|
|
198
|
+
|
|
199
|
+
OpenRouter cloud-only routing features such as provider fallback, billing, and plugins are accepted as request metadata but are not performed locally. Adapter capabilities still determine the actual result.
|
|
200
|
+
|
|
201
|
+
Function tools are accepted in both OpenAI API shapes. Because these coding CLIs do not expose a uniform native tool protocol, cli2api supplies the function schemas in the prompt and validates a strict JSON call envelope; it returns standard `tool_calls` / `function_call` output. Tool names and JSON argument objects are always validated, and `strict: true` arguments are validated against the supplied JSON Schema. The mock adapter has native deterministic tool events for tests.
|
|
202
|
+
|
|
203
|
+
For Chat Completions, reuse a client-chosen `session_id` to resume the CLI's native conversation. For Responses, pass the prior returned `id` as `previous_response_id`; unknown, expired, restarted, or adapter-mismatched ids return HTTP 400 instead of silently losing context. Mappings are adapter-bound, in memory, limited to 1,000 entries, and expire after 24 hours; restarting cli2api clears them. On native resume, only messages after the last assistant turn are sent, avoiding duplicate history. Responses `instructions` are forwarded as a developer message.
|
|
204
|
+
|
|
205
|
+
## Phase 0 scope
|
|
206
|
+
|
|
207
|
+
- [x] OpenAI chat completions + models
|
|
208
|
+
- [x] OpenRouter-compatible paths, model routing, catalog modes, reasoning, and streaming usage
|
|
209
|
+
- [x] Mock adapter (instant env-swap proof)
|
|
210
|
+
- [x] Codex adapter via `codex exec --json`
|
|
211
|
+
- [x] Loopback-only bind + `doctor` / `completion`
|
|
212
|
+
- [x] Required bearer token (auto-generated if unset)
|
|
213
|
+
- [x] No open CORS (SDK/script clients only)
|
|
214
|
+
- [x] OpenCode adapter (`run --format json --thinking`, safe `plan` agent)
|
|
215
|
+
- [x] Cursor Agent adapter (native partial `stream-json`, read-only `ask` mode)
|
|
216
|
+
- [x] Claude Code adapter (headless partial `stream-json`, restrictive flags; not live-tested locally)
|
|
217
|
+
- [x] Real JSONL streaming from Codex (`codex exec --json` line-by-line)
|
|
218
|
+
- Enable reasoning summaries via `-c model_reasoning_summary=detailed` (+ supports / unhide)
|
|
219
|
+
- Reasoning summary items → word-by-word `delta.reasoning` / `reasoning_content`
|
|
220
|
+
- Final `agent_message` → word-by-word `delta.content`
|
|
221
|
+
- Short lifecycle crumbs still go to reasoning without fake-stream delay
|
|
222
|
+
- [x] Config files + model aliases
|
|
223
|
+
- [x] Function tool calling (validated prompt-envelope fallback)
|
|
224
|
+
- [x] Responses API subset + semantic streaming events
|
|
225
|
+
- [x] Native CLI session resume (`session_id` / `previous_response_id`)
|
|
226
|
+
- [x] Env scrubbing for spawned CLI processes with a small runtime allowlist, per-adapter auth vars, and explicit opt-in passthrough
|
|
227
|
+
|
|
228
|
+
## Project layout
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
src/
|
|
232
|
+
index.ts # CLI
|
|
233
|
+
adapters/ # mock, codex, registry
|
|
234
|
+
protocol/openai.ts # request/response shaping
|
|
235
|
+
openrouter/catalog.ts # cached metadata and runnable/mirror model views
|
|
236
|
+
server/ # Hono app + listen
|
|
237
|
+
types.ts
|
|
238
|
+
scripts/smoke.ts
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Safety
|
|
242
|
+
|
|
243
|
+
- Default bind: `127.0.0.1` only (refuse non-loopback hosts).
|
|
244
|
+
- Bearer token required on every request; generated on startup if unset.
|
|
245
|
+
- No CORS middleware — browser tabs on the same machine cannot read responses via `fetch` from arbitrary origins.
|
|
246
|
+
- Codex adapter defaults to `--sandbox read-only` (blocks writes; does **not** block reading files the agent is asked about).
|
|
247
|
+
- Spawn uses argv arrays only (`shell: false`); no prompt concatenation into a shell string.
|
|
248
|
+
- Child processes receive a scrubbed environment. Normal runtime paths/local config homes and narrowly scoped adapter auth variables are retained; unrelated parent secrets are removed. Add exceptional keys explicitly with `CLI2API_CHILD_ENV_ALLOWLIST`.
|
|
249
|
+
- OpenCode runs with its `plan` agent and external plugins disabled.
|
|
250
|
+
- Cursor runs in read-only `ask` mode with its sandbox enabled; `--trust` only acknowledges the configured working directory for headless startup.
|
|
251
|
+
- Claude runs in `plan` mode with built-in tools disabled and configured MCP tools excluded.
|
|
252
|
+
- You are responsible for complying with each CLI vendor’s terms for local use.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Adapter } from "./types.js";
|
|
2
|
+
import type { ChatCompletionResponse } from "../types.js";
|
|
3
|
+
export interface ClaudeAdapterOptions {
|
|
4
|
+
binary?: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
extraArgs?: string[];
|
|
8
|
+
}
|
|
9
|
+
export type ClaudeParsedLine = {
|
|
10
|
+
kind: "content" | "reasoning";
|
|
11
|
+
text: string;
|
|
12
|
+
partial: boolean;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "session";
|
|
15
|
+
id: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "result";
|
|
18
|
+
text?: string;
|
|
19
|
+
usage?: ChatCompletionResponse["usage"];
|
|
20
|
+
} | {
|
|
21
|
+
kind: "error";
|
|
22
|
+
message: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "ignore";
|
|
25
|
+
};
|
|
26
|
+
export declare function parseClaudeLine(line: string): ClaudeParsedLine[];
|
|
27
|
+
export declare function createClaudeAdapter(opts?: ClaudeAdapterOptions): Adapter;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { requestToPrompt } from "../protocol/openai.js";
|
|
2
|
+
import { runCommand, runCommandLines, which } from "../util/process.js";
|
|
3
|
+
const DEFAULT_MODELS = ["default", "sonnet", "opus", "haiku"];
|
|
4
|
+
function record(value) {
|
|
5
|
+
return value && typeof value === "object" ? value : null;
|
|
6
|
+
}
|
|
7
|
+
function assistantBlocks(value) {
|
|
8
|
+
if (!Array.isArray(value))
|
|
9
|
+
return [];
|
|
10
|
+
const result = [];
|
|
11
|
+
for (const raw of value) {
|
|
12
|
+
const block = record(raw);
|
|
13
|
+
if (!block)
|
|
14
|
+
continue;
|
|
15
|
+
if (block.type === "text" && typeof block.text === "string" && block.text) {
|
|
16
|
+
result.push({ kind: "content", text: block.text });
|
|
17
|
+
}
|
|
18
|
+
else if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking) {
|
|
19
|
+
result.push({ kind: "reasoning", text: block.thinking });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
export function parseClaudeLine(line) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed)
|
|
27
|
+
return [{ kind: "ignore" }];
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(trimmed);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return trimmed.startsWith("{") ? [{ kind: "ignore" }] : [{ kind: "content", text: trimmed, partial: false }];
|
|
34
|
+
}
|
|
35
|
+
const message = record(parsed);
|
|
36
|
+
if (!message)
|
|
37
|
+
return [{ kind: "ignore" }];
|
|
38
|
+
const type = typeof message.type === "string" ? message.type : "";
|
|
39
|
+
if (type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
|
|
40
|
+
return [{ kind: "session", id: message.session_id }];
|
|
41
|
+
}
|
|
42
|
+
if (type === "stream_event") {
|
|
43
|
+
const event = record(message.event);
|
|
44
|
+
const delta = record(event?.delta);
|
|
45
|
+
if (event?.type === "content_block_delta" && delta) {
|
|
46
|
+
if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
47
|
+
return [{ kind: "content", text: delta.text, partial: true }];
|
|
48
|
+
}
|
|
49
|
+
if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
|
|
50
|
+
return [{ kind: "reasoning", text: delta.thinking, partial: true }];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [{ kind: "ignore" }];
|
|
54
|
+
}
|
|
55
|
+
if (type === "assistant") {
|
|
56
|
+
const nested = record(message.message);
|
|
57
|
+
const blocks = assistantBlocks(nested?.content);
|
|
58
|
+
return blocks.length
|
|
59
|
+
? blocks.map((block) => ({ ...block, partial: false }))
|
|
60
|
+
: [{ kind: "ignore" }];
|
|
61
|
+
}
|
|
62
|
+
if (type === "result") {
|
|
63
|
+
if (message.is_error === true || message.subtype === "error") {
|
|
64
|
+
return [{ kind: "error", message: typeof message.result === "string" ? message.result : "Claude Code failed" }];
|
|
65
|
+
}
|
|
66
|
+
const rawUsage = record(message.usage);
|
|
67
|
+
let usage;
|
|
68
|
+
if (rawUsage) {
|
|
69
|
+
const prompt = typeof rawUsage.input_tokens === "number" ? rawUsage.input_tokens : 0;
|
|
70
|
+
const completion = typeof rawUsage.output_tokens === "number" ? rawUsage.output_tokens : 0;
|
|
71
|
+
usage = { prompt_tokens: prompt, completion_tokens: completion, total_tokens: prompt + completion };
|
|
72
|
+
}
|
|
73
|
+
return [{
|
|
74
|
+
kind: "result",
|
|
75
|
+
text: typeof message.result === "string" ? message.result : undefined,
|
|
76
|
+
usage,
|
|
77
|
+
}];
|
|
78
|
+
}
|
|
79
|
+
if (type === "error") {
|
|
80
|
+
return [{ kind: "error", message: typeof message.message === "string" ? message.message : "Claude Code stream error" }];
|
|
81
|
+
}
|
|
82
|
+
return [{ kind: "ignore" }];
|
|
83
|
+
}
|
|
84
|
+
export function createClaudeAdapter(opts = {}) {
|
|
85
|
+
const binary = opts.binary ?? "claude";
|
|
86
|
+
const timeoutMs = opts.timeoutMs ?? 180_000;
|
|
87
|
+
return {
|
|
88
|
+
id: "claude",
|
|
89
|
+
description: "Claude Code via headless stream-json (plan mode, tools disabled)",
|
|
90
|
+
async listModels() {
|
|
91
|
+
return DEFAULT_MODELS.map((model) => ({
|
|
92
|
+
id: `claude/${model}`,
|
|
93
|
+
object: "model",
|
|
94
|
+
created: 0,
|
|
95
|
+
owned_by: "anthropic",
|
|
96
|
+
description: model === "default" ? "Uses the Claude Code default model" : undefined,
|
|
97
|
+
}));
|
|
98
|
+
},
|
|
99
|
+
async *chat(req, signal) {
|
|
100
|
+
const path = await which(binary);
|
|
101
|
+
if (!path) {
|
|
102
|
+
yield {
|
|
103
|
+
type: "error",
|
|
104
|
+
message: `claude binary not found on PATH (looked for "${binary}"). Install Claude Code or set CLI2API_CLAUDE_BIN.`,
|
|
105
|
+
code: "binary_missing",
|
|
106
|
+
};
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Official headless flags. Empty --tools disables built-ins; strict MCP plus
|
|
110
|
+
// the deny rule prevents configured MCP tools from bypassing that restriction.
|
|
111
|
+
const args = [
|
|
112
|
+
"-p",
|
|
113
|
+
"--output-format",
|
|
114
|
+
"stream-json",
|
|
115
|
+
"--verbose",
|
|
116
|
+
"--include-partial-messages",
|
|
117
|
+
"--permission-mode",
|
|
118
|
+
"plan",
|
|
119
|
+
"--tools",
|
|
120
|
+
"",
|
|
121
|
+
"--disallowedTools",
|
|
122
|
+
"mcp__*",
|
|
123
|
+
"--strict-mcp-config",
|
|
124
|
+
];
|
|
125
|
+
if (req.nativeSessionId)
|
|
126
|
+
args.push("--resume", req.nativeSessionId);
|
|
127
|
+
if (req.modelLocal && req.modelLocal !== "default")
|
|
128
|
+
args.push("--model", req.modelLocal);
|
|
129
|
+
if (opts.extraArgs?.length)
|
|
130
|
+
args.push(...opts.extraArgs);
|
|
131
|
+
args.push(requestToPrompt(req));
|
|
132
|
+
let sawContent = false;
|
|
133
|
+
let sawPartialContent = false;
|
|
134
|
+
let sawPartialReasoning = false;
|
|
135
|
+
let fallbackResult = "";
|
|
136
|
+
let usage;
|
|
137
|
+
let exitCode = null;
|
|
138
|
+
let timedOut = false;
|
|
139
|
+
let stderr = "";
|
|
140
|
+
try {
|
|
141
|
+
for await (const event of runCommandLines(path, args, {
|
|
142
|
+
cwd: opts.cwd,
|
|
143
|
+
timeoutMs,
|
|
144
|
+
signal,
|
|
145
|
+
inheritEnv: [
|
|
146
|
+
"CLAUDE_CONFIG_DIR",
|
|
147
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
148
|
+
"ANTHROPIC_API_KEY",
|
|
149
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
150
|
+
"ANTHROPIC_BASE_URL",
|
|
151
|
+
],
|
|
152
|
+
})) {
|
|
153
|
+
if (event.type === "stdout_line") {
|
|
154
|
+
for (const parsed of parseClaudeLine(event.line)) {
|
|
155
|
+
if (parsed.kind === "content") {
|
|
156
|
+
if (parsed.partial) {
|
|
157
|
+
sawPartialContent = true;
|
|
158
|
+
sawContent = true;
|
|
159
|
+
yield { type: "delta", text: parsed.text, channel: "content" };
|
|
160
|
+
}
|
|
161
|
+
else if (!sawPartialContent) {
|
|
162
|
+
sawContent = true;
|
|
163
|
+
yield { type: "delta", text: parsed.text, channel: "content" };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (parsed.kind === "reasoning") {
|
|
167
|
+
if (parsed.partial) {
|
|
168
|
+
sawPartialReasoning = true;
|
|
169
|
+
yield { type: "delta", text: parsed.text, channel: "reasoning" };
|
|
170
|
+
}
|
|
171
|
+
else if (!sawPartialReasoning) {
|
|
172
|
+
yield { type: "delta", text: parsed.text, channel: "reasoning" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (parsed.kind === "session") {
|
|
176
|
+
yield { type: "session", id: parsed.id };
|
|
177
|
+
}
|
|
178
|
+
else if (parsed.kind === "result") {
|
|
179
|
+
fallbackResult = parsed.text ?? fallbackResult;
|
|
180
|
+
usage = parsed.usage ?? usage;
|
|
181
|
+
}
|
|
182
|
+
else if (parsed.kind === "error") {
|
|
183
|
+
yield { type: "error", message: parsed.message, code: "cli_error" };
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (event.type === "exit") {
|
|
189
|
+
exitCode = event.code;
|
|
190
|
+
timedOut = event.timedOut;
|
|
191
|
+
stderr = event.stderr;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
yield {
|
|
197
|
+
type: "error",
|
|
198
|
+
message: `Failed to spawn claude: ${error instanceof Error ? error.message : String(error)}`,
|
|
199
|
+
code: "spawn_error",
|
|
200
|
+
};
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!sawContent && fallbackResult) {
|
|
204
|
+
sawContent = true;
|
|
205
|
+
yield { type: "delta", text: fallbackResult, channel: "content" };
|
|
206
|
+
}
|
|
207
|
+
if (timedOut) {
|
|
208
|
+
yield { type: "error", message: `claude timed out after ${timeoutMs}ms`, code: "timeout" };
|
|
209
|
+
}
|
|
210
|
+
else if (signal.aborted) {
|
|
211
|
+
yield { type: "error", message: "Aborted", code: "abort" };
|
|
212
|
+
}
|
|
213
|
+
else if (exitCode !== 0) {
|
|
214
|
+
yield {
|
|
215
|
+
type: "error",
|
|
216
|
+
message: `claude exited with code ${exitCode}${stderr.trim() ? `: ${stderr.trim().slice(0, 2000)}` : ""}`,
|
|
217
|
+
code: "cli_error",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
else if (!sawContent) {
|
|
221
|
+
yield { type: "error", message: "claude returned empty output", code: "empty_output" };
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
yield { type: "done", finishReason: "stop", usage };
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
async health() {
|
|
228
|
+
const path = await which(binary);
|
|
229
|
+
if (!path)
|
|
230
|
+
return { ok: false, adapter: "claude", details: { binary }, message: "claude binary not found on PATH" };
|
|
231
|
+
const version = await runCommand(path, ["--version"], { timeoutMs: 8_000 });
|
|
232
|
+
return {
|
|
233
|
+
ok: version.code === 0,
|
|
234
|
+
adapter: "claude",
|
|
235
|
+
details: { binary: path, version: (version.stdout || version.stderr).trim(), permissionMode: "plan", tools: "disabled" },
|
|
236
|
+
message: version.code === 0 ? "claude available" : "claude --version failed",
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
async doctor() {
|
|
240
|
+
const path = await which(binary);
|
|
241
|
+
const checks = [{ name: "binary-on-path", ok: Boolean(path), detail: path ?? `missing: ${binary}` }];
|
|
242
|
+
let version;
|
|
243
|
+
if (path) {
|
|
244
|
+
const result = await runCommand(path, ["--version"], { timeoutMs: 8_000 });
|
|
245
|
+
version = (result.stdout || result.stderr).trim();
|
|
246
|
+
checks.push({ name: "version", ok: result.code === 0, detail: version || `exit ${result.code}` });
|
|
247
|
+
}
|
|
248
|
+
checks.push({ name: "restrictive-default", ok: true, detail: "permission-mode=plan; built-in and MCP tools disabled" });
|
|
249
|
+
return { adapter: "claude", ok: Boolean(path) && checks.every((check) => check.ok), binary: path ?? binary, version, checks };
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Adapter } from "./types.js";
|
|
2
|
+
import type { ChatEvent } from "../types.js";
|
|
3
|
+
export interface CodexAdapterOptions {
|
|
4
|
+
/** Binary name or path (default: codex) */
|
|
5
|
+
binary?: string;
|
|
6
|
+
/** Working directory for codex exec (default: cwd) */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Extra args appended to `codex exec` */
|
|
9
|
+
extraArgs?: string[];
|
|
10
|
+
/** Request timeout in ms */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Sandbox mode for exec.
|
|
14
|
+
* Default: read-only (safer for API/benchmark prompts that should not mutate disk).
|
|
15
|
+
*/
|
|
16
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access";
|
|
17
|
+
/** Skip git repo check (needed outside of git repos) */
|
|
18
|
+
skipGitRepoCheck?: boolean;
|
|
19
|
+
/** Word-by-word delay for content fake-stream (ms). 0 = instant chunks. */
|
|
20
|
+
contentWordDelayMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Yield text word-by-word so SSE clients see a typing effect. */
|
|
23
|
+
export declare function fakeStreamWords(text: string, delayMs: number, signal?: AbortSignal, channel?: "content" | "reasoning"): AsyncGenerator<ChatEvent>;
|
|
24
|
+
export type CodexLineKind = "content" | "reasoning" | "session" | "done" | "error" | "ignore";
|
|
25
|
+
export interface CodexParsedLine {
|
|
26
|
+
kind: CodexLineKind;
|
|
27
|
+
text?: string;
|
|
28
|
+
/** True for model reasoning summaries (fake-stream); false for short status crumbs. */
|
|
29
|
+
fakeStream?: boolean;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
error?: ChatEvent & {
|
|
32
|
+
type: "error";
|
|
33
|
+
};
|
|
34
|
+
done?: ChatEvent & {
|
|
35
|
+
type: "done";
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse one Codex JSONL line into a typed payload.
|
|
40
|
+
* Content (agent_message) is returned whole — caller fake-streams words.
|
|
41
|
+
* Everything else useful goes to reasoning.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseCodexLine(line: string): CodexParsedLine;
|
|
44
|
+
/**
|
|
45
|
+
* Map one Codex JSONL event to ChatEvents (content returned as a single delta;
|
|
46
|
+
* prefer parseCodexLine + fakeStreamWords in the live adapter path).
|
|
47
|
+
*/
|
|
48
|
+
export declare function chatEventsFromCodexLine(line: string): ChatEvent[];
|
|
49
|
+
/** Batch helper kept for tests / non-streaming inspection. */
|
|
50
|
+
export declare function parseCodexJsonl(stdout: string): string;
|
|
51
|
+
export declare function createCodexAdapter(opts?: CodexAdapterOptions): Adapter;
|