commandcode-api-proxy 0.1.2 → 0.1.4

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.
Files changed (44) hide show
  1. package/README.md +96 -83
  2. package/dist/models.json +7 -1
  3. package/dist/proxy.js +8 -0
  4. package/dist/proxy.js.map +1 -1
  5. package/dist/server.d.ts.map +1 -1
  6. package/dist/server.js +184 -21
  7. package/dist/server.js.map +1 -1
  8. package/dist/setup/claude-code.d.ts +2 -0
  9. package/dist/setup/claude-code.d.ts.map +1 -0
  10. package/dist/setup/claude-code.js +37 -0
  11. package/dist/setup/claude-code.js.map +1 -0
  12. package/dist/setup/opencode.d.ts.map +1 -1
  13. package/dist/setup/opencode.js.map +1 -1
  14. package/dist/stream.d.ts +1 -0
  15. package/dist/stream.d.ts.map +1 -1
  16. package/dist/stream.js +6 -0
  17. package/dist/stream.js.map +1 -1
  18. package/dist/translate/anthropic-models.d.ts +2 -0
  19. package/dist/translate/anthropic-models.d.ts.map +1 -0
  20. package/dist/translate/anthropic-models.js +11 -0
  21. package/dist/translate/anthropic-models.js.map +1 -0
  22. package/dist/translate/anthropic-types.d.ts +168 -0
  23. package/dist/translate/anthropic-types.d.ts.map +1 -0
  24. package/dist/translate/anthropic-types.js +4 -0
  25. package/dist/translate/anthropic-types.js.map +1 -0
  26. package/dist/translate/anthropic.d.ts +24 -0
  27. package/dist/translate/anthropic.d.ts.map +1 -0
  28. package/dist/translate/anthropic.js +458 -0
  29. package/dist/translate/anthropic.js.map +1 -0
  30. package/dist/translate/openai.d.ts +10 -10
  31. package/dist/translate/openai.d.ts.map +1 -1
  32. package/dist/translate/openai.js +153 -208
  33. package/dist/translate/openai.js.map +1 -1
  34. package/dist/translate/util.d.ts +1 -2
  35. package/dist/translate/util.d.ts.map +1 -1
  36. package/dist/translate/util.js +33 -12
  37. package/dist/translate/util.js.map +1 -1
  38. package/dist/translate/validation.d.ts +2 -0
  39. package/dist/translate/validation.d.ts.map +1 -1
  40. package/dist/translate/validation.js +74 -0
  41. package/dist/translate/validation.js.map +1 -1
  42. package/dist/upstream.d.ts.map +1 -1
  43. package/dist/upstream.js.map +1 -1
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
+ <a href="#"><img src="./.github/assets/banner.webp" alt="Banner"></a>
2
+
1
3
  # Command Code API Proxy
2
4
 
3
- **OpenAI-compatible API proxy for [Command Code](https://commandcode.ai).**
4
- Use your Command Code subscription from **any** OpenAI-compatible client — OpenCode, Continue, the OpenAI SDK, or plain `curl`.
5
+ [![npm version](https://img.shields.io/npm/v/commandcode-api-proxy)](https://www.npmjs.com/package/commandcode-api-proxy)
6
+ [![downloads/month](https://img.shields.io/npm/dm/commandcode-api-proxy)](https://www.npmjs.com/package/commandcode-api-proxy)
5
7
 
6
- ```
7
- npx commandcode-api-proxy
8
- ```
8
+ **OpenAI-compatible API proxy for [Command Code](https://commandcode.ai).**
9
+ Use your Command Code subscription from **any** OpenAI-compatible client — OpenCode, Claude Code, or plain `curl`.
9
10
 
10
11
  ## Why?
11
12
 
@@ -18,10 +19,16 @@ Command Code exposes two API surfaces:
18
19
 
19
20
  This proxy talks `/alpha/generate` upstream and standard OpenAI downstream — so your existing plan works from any tool.
20
21
 
21
- ## Quick start
22
+ ## Run the proxy
22
23
 
23
24
  ```bash
24
- # Run directly (no install)
25
+ # Clone & run from source
26
+ git clone https://github.com/thaolaptrinh/commandcode-api-proxy.git
27
+ cd commandcode-api-proxy
28
+ npm install
29
+ npm run build && npm start
30
+
31
+ # Or run directly (no install)
25
32
  npx commandcode-api-proxy
26
33
 
27
34
  # Or install globally
@@ -29,17 +36,19 @@ npm install -g commandcode-api-proxy
29
36
  commandcode-api-proxy
30
37
  ```
31
38
 
32
- ### Authentication
39
+ On first run, the proxy prompts for your Command Code API key (get it from
40
+ https://commandcode.ai/settings). Other ways to provide it are in
41
+ [Authentication](#authentication).
33
42
 
34
- The proxy loads your API key from (in order of priority):
43
+ ## Authentication
35
44
 
36
- 1. `--api-key` CLI flag: `npx commandcode-api-proxy --api-key user_xxx`
37
- 2. `CC_API_KEY` environment variable
38
- 3. `~/.config/commandcode-api-proxy/auth.json` (saved via `auth login`)
45
+ Provide your API key via `--api-key`, the `CC_API_KEY` env var, or save it
46
+ with `auth login` (stored at `~/.config/commandcode-api-proxy/auth.json`).
39
47
 
40
- On first run without a key, the proxy prompts you to enter one and persists it.
48
+ ### CLI auth commands
41
49
 
42
- #### CLI auth commands
50
+ > From source, replace `commandcode-api-proxy` with `npm run auth --`
51
+ > (e.g. `npm run auth -- login`).
43
52
 
44
53
  ```bash
45
54
  # Save a new API key
@@ -52,43 +61,54 @@ commandcode-api-proxy auth login --force
52
61
  commandcode-api-proxy auth logout
53
62
  ```
54
63
 
55
- Get your API key from https://commandcode.ai/settings.
64
+ ## CLI options
65
+
66
+ | Option | Description | Default |
67
+ | --------------------- | --------------------------------- | ----------- |
68
+ | `--host` | Bind address | `127.0.0.1` |
69
+ | `--port` | Port | `8787` |
70
+ | `--api-key` | Command Code API key | — |
71
+ | `--setup-opencode` | Generate OpenCode provider config | — |
72
+ | `--setup-claude-code` | Generate Claude Code model config | — |
56
73
 
57
- ### CLI options
74
+ Equivalent env vars (lower priority than CLI flags):
58
75
 
59
- | Option | Description | Default |
60
- | ----------- | -------------------- | ----------- |
61
- | `--host` | Bind address | `127.0.0.1` |
62
- | `--port` | Port | `8787` |
63
- | `--api-key` | Command Code API key | — |
64
- | `--setup-opencode` | Generate OpenCode provider config | — |
76
+ | Env var | Description |
77
+ | ---------------- | -------------------------------------------- |
78
+ | `HOST` | Bind address |
79
+ | `PORT` | Port |
80
+ | `CC_API_KEY` | Command Code API key |
81
+ | `CC_API_BASE` | Upstream API base URL |
82
+ | `CC_CLI_VERSION` | CLI version sent upstream |
83
+ | `LOG_LEVEL` | Log level (`info`, `debug`, etc.) |
65
84
 
66
85
  ## Endpoints
67
86
 
68
- ### `POST /v1/chat/completions` (OpenAI)
87
+ | Endpoint | Protocol |
88
+ | -------------------------------- | --------- |
89
+ | `GET /health` | — |
90
+ | `GET /v1/models` | OpenAI |
91
+ | `POST /v1/chat/completions` | OpenAI |
92
+ | `POST /v1/messages` | Anthropic |
93
+ | `POST /v1/messages/count_tokens` | Anthropic |
69
94
 
70
- ```bash
71
- curl http://127.0.0.1:8787/v1/chat/completions \
72
- -H "Authorization: Bearer proxy-managed" \
73
- -H "Content-Type: application/json" \
74
- -d '{
75
- "model": "deepseek/deepseek-v4-pro",
76
- "messages": [{"role": "user", "content": "Hello!"}],
77
- "stream": true
78
- }'
79
- ```
95
+ All endpoints accept any auth token (use `proxy-managed`) — the proxy injects
96
+ your real Command Code key upstream.
80
97
 
81
98
  ## Client configuration
82
99
 
83
100
  ### OpenCode
84
101
 
85
- Run setup (writes to `~/.config/opencode/opencode.json`):
102
+ Run setup to auto-generate the provider config at `~/.config/opencode/opencode.json`:
86
103
 
87
104
  ```bash
88
105
  npx commandcode-api-proxy --setup-opencode
89
106
  ```
90
107
 
91
- Or add manually:
108
+ All CC models are listed directly — pick the one you want from the model selector.
109
+
110
+ Or add a `commandcode` provider manually — point `baseURL` at the proxy and
111
+ use any model ID from the [Model aliases](#model-aliases) table:
92
112
 
93
113
  ```json
94
114
  {
@@ -102,72 +122,65 @@ Or add manually:
102
122
  "apiKey": "proxy-managed"
103
123
  },
104
124
  "models": {
105
- "deepseek-v4-pro": { "name": "DeepSeek V4 Pro", "limit": { "context": 1048576, "output": 393216 } },
106
- "deepseek-v4-flash": { "name": "DeepSeek V4 Flash", "limit": { "context": 1048576, "output": 393216 } },
107
- "MiniMax-M2.7": { "name": "MiniMax M2.7", "limit": { "context": 204800, "output": 32768 } },
108
- "MiniMax-M2.5": { "name": "MiniMax M2.5", "limit": { "context": 204800, "output": 32768 } },
109
- "GLM-5.1": { "name": "GLM-5.1", "limit": { "context": 200000, "output": 131072 } },
110
- "GLM-5": { "name": "GLM-5", "limit": { "context": 200000, "output": 131072 } },
111
- "Kimi-K2.6": { "name": "Kimi K2.6", "limit": { "context": 262144, "output": 98304 } },
112
- "Kimi-K2.5": { "name": "Kimi K2.5", "limit": { "context": 262144, "output": 98304 } },
113
- "Qwen3.6-Max-Preview": { "name": "Qwen 3.6 Max Preview", "limit": { "context": 262144, "output": 65536 } },
114
- "Qwen3.6-Plus": { "name": "Qwen 3.6 Plus", "limit": { "context": 1048576, "output": 65536 } },
115
- "Qwen3.7-Max": { "name": "Qwen 3.7 Max", "limit": { "context": 1048576, "output": 65536 } },
116
- "Qwen3.7-Plus": { "name": "Qwen 3.7 Plus", "limit": { "context": 1048576, "output": 65536 } },
117
- "Step-3.5-Flash": { "name": "Step 3.5 Flash", "limit": { "context": 262144, "output": 65536 } },
118
- "mimo-v2.5": { "name": "MiMo V2.5", "limit": { "context": 1048576, "output": 131072 } }
125
+ "deepseek-v4-pro": { "name": "DeepSeek V4 Pro" }
119
126
  }
120
127
  }
121
128
  }
122
129
  }
123
130
  ```
124
131
 
125
- ### OpenAI Python SDK
132
+ ### Claude Code
126
133
 
127
- ```python
128
- from openai import OpenAI
134
+ Claude Code only offers three tiers — `sonnet`, `opus`, `haiku`. The setup
135
+ maps each tier to a Command Code model via env vars in `claude-settings.json`:
129
136
 
130
- client = OpenAI(
131
- base_url="http://127.0.0.1:8787/v1",
132
- api_key="proxy-managed",
133
- )
137
+ ```bash
138
+ npx commandcode-api-proxy --setup-claude-code
139
+ ```
140
+
141
+ If a settings file already exists, re-run with `--force` to overwrite it.
134
142
 
135
- response = client.chat.completions.create(
136
- model="deepseek/deepseek-v4-pro",
137
- messages=[{"role": "user", "content": "Hello!"}],
138
- )
143
+ | Claude Code tier | Env var | Maps to |
144
+ | ---------------- | -------------------------------- | --------------------------- |
145
+ | `sonnet` | `ANTHROPIC_DEFAULT_SONNET_MODEL` | `deepseek/deepseek-v4-pro` |
146
+ | `opus` | `ANTHROPIC_DEFAULT_OPUS_MODEL` | `deepseek/deepseek-v4-pro` |
147
+ | `haiku` | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | `deepseek/deepseek-v4-flash`|
148
+
149
+ Edit those env vars in the settings file to point at other CC models. To force
150
+ every `claude-*` request to a single model regardless of tier, set
151
+ `ANTHROPIC_DEFAULT_MODEL`. Then run:
152
+
153
+ ```bash
154
+ claude --settings ~/.config/commandcode-api-proxy/claude-settings.json
139
155
  ```
140
156
 
141
- ### Continue
157
+ Or set an alias:
142
158
 
143
- ```json
144
- {
145
- "models": [
146
- {
147
- "title": "Command Code",
148
- "provider": "openai",
149
- "model": "deepseek/deepseek-v4-pro",
150
- "apiKey": "proxy-managed",
151
- "apiBase": "http://127.0.0.1:8787/v1"
152
- }
153
- ]
154
- }
159
+ ```bash
160
+ alias claude-proxy="claude --settings ~/.config/commandcode-api-proxy/claude-settings.json"
161
+ claude-proxy
155
162
  ```
156
163
 
157
164
  ## Model aliases
158
165
 
159
166
  Short names work in addition to full model IDs:
160
167
 
161
- | Alias | Maps to |
162
- | -------------------------------------- | ---------------------------- |
163
- | `deepseek-v4-pro`, `deepseek-v4` | `deepseek/deepseek-v4-pro` |
164
- | `deepseek-v4-flash`, `deepseek-flash` | `deepseek/deepseek-v4-flash` |
165
- | `minimax-m2.7`, `minimax-m2.5` | `MiniMaxAI/MiniMax-*` |
166
- | `glm-5.1`, `glm-5` | `zai-org/GLM-*` |
167
- | `kimi-k2.6`, `kimi-k2.5` | `moonshotai/Kimi-*` |
168
- | `qwen3.6-max`, `qwen3.6-plus` | `Qwen/Qwen3.6-*` |
169
- | `step3.5` | `stepfun/Step-3.5-Flash` |
170
- | `mimo-v2.5` | `xiaomi/mimo-v2.5` |
168
+ | Alias | Maps to |
169
+ | -------------------------------------------------- | ------------------------------ |
170
+ | `deepseek-v4-pro`, `deepseek-v4`, `deepseek-pro` | `deepseek/deepseek-v4-pro` |
171
+ | `deepseek-v4-flash`, `deepseek-flash` | `deepseek/deepseek-v4-flash` |
172
+ | `kimi-k2.6`, `kimi2.6` | `moonshotai/Kimi-K2.6` |
173
+ | `kimi-k2.5`, `kimi2.5` | `moonshotai/Kimi-K2.5` |
174
+ | `glm-5.1` | `zai-org/GLM-5.1` |
175
+ | `glm-5` | `zai-org/GLM-5` |
176
+ | `minimax-m2.7`, `minimax2.7` | `MiniMaxAI/MiniMax-M2.7` |
177
+ | `minimax-m2.5`, `minimax2.5` | `MiniMaxAI/MiniMax-M2.5` |
178
+ | `qwen3.6-max`, `qwen-3.6-max` | `Qwen/Qwen3.6-Max-Preview` |
179
+ | `qwen3.6-plus`, `qwen-3.6-plus` | `Qwen/Qwen3.6-Plus` |
180
+ | `qwen3.7-max`, `qwen-3.7-max` | `Qwen/Qwen3.7-Max` |
181
+ | `qwen3.7-plus`, `qwen-3.7-plus` | `Qwen/Qwen3.7-Plus` |
182
+ | `step3.5`, `step-3.5-flash` | `stepfun/Step-3.5-Flash` |
183
+ | `mimo-v2.5`, `mimo2.5` | `xiaomi/mimo-v2.5` |
171
184
 
172
185
  Any model ID is passed through as-is — the proxy does not validate against a fixed list.
173
186
 
package/dist/models.json CHANGED
@@ -36,7 +36,13 @@
36
36
  "qwen-3.6-plus": "Qwen/Qwen3.6-Plus",
37
37
  "qwen3.6-plus": "Qwen/Qwen3.6-Plus",
38
38
  "step-3.5-flash": "stepfun/Step-3.5-Flash",
39
- "step3.5": "stepfun/Step-3.5-Flash"
39
+ "step3.5": "stepfun/Step-3.5-Flash",
40
+ "mimo-v2.5": "xiaomi/mimo-v2.5",
41
+ "mimo2.5": "xiaomi/mimo-v2.5",
42
+ "qwen3.7-max": "Qwen/Qwen3.7-Max",
43
+ "qwen-3.7-max": "Qwen/Qwen3.7-Max",
44
+ "qwen3.7-plus": "Qwen/Qwen3.7-Plus",
45
+ "qwen-3.7-plus": "Qwen/Qwen3.7-Plus"
40
46
  },
41
47
  "contextWindows": {
42
48
  "deepseek/deepseek-v4-pro": 1048576,
package/dist/proxy.js CHANGED
@@ -3,6 +3,7 @@ import { loadConfig, fetchLatestCliVersion } from "./config.js";
3
3
  import { createServer } from "./server.js";
4
4
  import { saveApiKey, promptForApiKey, readAuthKey, deleteAuth } from "./auth.js";
5
5
  import { setupOpenCodeConfig } from "./setup/opencode.js";
6
+ import { setupClaudeCodeConfig } from "./setup/claude-code.js";
6
7
  import { logger, initLogger } from "./logger.js";
7
8
  const args = process.argv.slice(2);
8
9
  if (args[0] === "auth") {
@@ -36,6 +37,11 @@ if (args.includes("--setup-opencode")) {
36
37
  await setupOpenCodeConfig();
37
38
  process.exit(0);
38
39
  }
40
+ if (args.includes("--setup-claude-code")) {
41
+ const force = args.includes("--force");
42
+ await setupClaudeCodeConfig(force);
43
+ process.exit(0);
44
+ }
39
45
  const config = loadConfig();
40
46
  initLogger(config.logLevel);
41
47
  logger.info(`API key source: ${process.env.CC_API_KEY ? "env CC_API_KEY" : config.apiKey ? "auth.json" : "none"} (length: ${config.apiKey?.length ?? 0})`);
@@ -67,6 +73,8 @@ server.listen(config.port, config.host, () => {
67
73
  console.log(" GET /health");
68
74
  console.log(" GET /v1/models");
69
75
  console.log(" POST /v1/chat/completions (OpenAI format)");
76
+ console.log(" POST /v1/messages (Anthropic format)");
77
+ console.log(" POST /v1/messages/count_tokens (Anthropic format)");
70
78
  console.log("");
71
79
  console.log(" Press Ctrl+C to stop\n");
72
80
  });
package/dist/proxy.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiQ1C,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAsDrD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAoc1C,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CA4DrD"}
package/dist/server.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import http from "node:http";
2
2
  import { URL } from "node:url";
3
- import { toCCRequest, toOpenAIStreamChunk, toOpenAIErrorChunk, buildNonStreamingResponse, } from "./translate/openai.js";
3
+ import { toCCRequest, OpenAIStreamEncoder, buildNonStreamingResponse } from "./translate/openai.js";
4
+ import { toCCRequest as anToCCRequest, AnthropicStreamEncoder, buildAnthropicResponse, } from "./translate/anthropic.js";
4
5
  import { getDefaultModels, fetchModelList } from "./translate/models.js";
5
- import { resetMessageId } from "./translate/util.js";
6
- import { formatSSE, formatSSEDone } from "./stream.js";
6
+ import { formatSSE, formatSSEDone, formatAnthropicSSE } from "./stream.js";
7
7
  import { sendToCC, collectEvents, UpstreamError } from "./upstream.js";
8
8
  import { logger } from "./logger.js";
9
- import { validateOpenAIChatRequest, ValidationError } from "./translate/validation.js";
9
+ import { validateOpenAIChatRequest, validateAnthropicRequest, ValidationError, } from "./translate/validation.js";
10
10
  // ──────────────────────────────────────────
11
11
  // Mutable server state
12
12
  // ──────────────────────────────────────────
@@ -71,6 +71,22 @@ function sendJson(res, status, data) {
71
71
  });
72
72
  res.end(JSON.stringify(data));
73
73
  }
74
+ function sendOpenAIError(res, status, message) {
75
+ sendJson(res, status, { error: { message, type: "proxy_error" } });
76
+ }
77
+ const ANTHROPIC_STATUS_ERROR_MAP = {
78
+ 400: "invalid_request_error",
79
+ 401: "authentication_error",
80
+ 403: "permission_error",
81
+ 404: "not_found_error",
82
+ 429: "rate_limit_error",
83
+ 500: "api_error",
84
+ 529: "overloaded_error",
85
+ };
86
+ function sendAnthropicError(res, status, type, message) {
87
+ res.writeHead(status, { "Content-Type": "application/json", ...corsHeaders() });
88
+ res.end(JSON.stringify({ type: "error", error: { type, message } }));
89
+ }
74
90
  function corsHeaders() {
75
91
  return {
76
92
  "Access-Control-Allow-Origin": "*",
@@ -101,7 +117,27 @@ function handleHealth(_req, res) {
101
117
  version: process.env.npm_package_version ?? "0.1.0",
102
118
  });
103
119
  }
104
- function handleModels(_req, res) {
120
+ function handleModels(req, res) {
121
+ const isAnthropic = req.headers["anthropic-version"] !== undefined;
122
+ if (isAnthropic) {
123
+ const items = modelList;
124
+ const data = {
125
+ data: items.map((id) => ({
126
+ id,
127
+ type: "model",
128
+ display_name: id,
129
+ created_at: new Date().toISOString(),
130
+ max_input_tokens: null,
131
+ max_tokens: null,
132
+ capabilities: null,
133
+ })),
134
+ has_more: false,
135
+ first_id: items.length > 0 ? items[0] : null,
136
+ last_id: items.length > 0 ? items[items.length - 1] : null,
137
+ };
138
+ sendJson(res, 200, data);
139
+ return;
140
+ }
105
141
  const data = {
106
142
  object: "list",
107
143
  data: modelList.map((id) => ({
@@ -119,7 +155,7 @@ async function handleChatCompletions(req, res) {
119
155
  rawBody = await parseBody(req);
120
156
  }
121
157
  catch {
122
- return sendJson(res, 400, { error: "Invalid JSON body" });
158
+ return sendOpenAIError(res, 400, "Invalid JSON body");
123
159
  }
124
160
  let openAIReq;
125
161
  try {
@@ -127,16 +163,17 @@ async function handleChatCompletions(req, res) {
127
163
  }
128
164
  catch (err) {
129
165
  if (err instanceof ValidationError) {
130
- return sendJson(res, 400, { error: err.message });
166
+ return sendOpenAIError(res, 400, err.message);
131
167
  }
132
- return sendJson(res, 400, { error: "Invalid request body" });
168
+ return sendOpenAIError(res, 400, "Invalid request body");
133
169
  }
134
170
  const apiKey = extractApiKey(req);
135
171
  if (!apiKey) {
136
- return sendJson(res, 401, { error: "Unauthorized" });
172
+ return sendOpenAIError(res, 401, "Unauthorized");
137
173
  }
138
174
  const isStream = openAIReq.stream === true;
139
175
  const model = openAIReq.model ?? "default";
176
+ const encoder = new OpenAIStreamEncoder(model);
140
177
  logger.info(`[Incoming Request] Model: ${model}`);
141
178
  logger.info(`[Incoming Request] Tools count: ${openAIReq.tools ? openAIReq.tools.length : 0}`);
142
179
  if (openAIReq.tools && openAIReq.tools.length > 0) {
@@ -145,7 +182,6 @@ async function handleChatCompletions(req, res) {
145
182
  else {
146
183
  logger.info(`[Incoming Request] No tools were sent by the client!`);
147
184
  }
148
- resetMessageId();
149
185
  const ccBody = toCCRequest(openAIReq);
150
186
  const abort = abortOnClientDisconnect(req, res);
151
187
  try {
@@ -163,7 +199,7 @@ async function handleChatCompletions(req, res) {
163
199
  ...corsHeaders(),
164
200
  });
165
201
  stream.on("data", (event) => {
166
- for (const chunk of toOpenAIStreamChunk(event, model)) {
202
+ for (const chunk of encoder.emit(event)) {
167
203
  res.write(formatSSE(chunk));
168
204
  }
169
205
  });
@@ -174,7 +210,7 @@ async function handleChatCompletions(req, res) {
174
210
  stream.on("error", (err) => {
175
211
  logger.error("[stream] OpenAI streaming error:", err.message);
176
212
  if (!res.destroyed) {
177
- res.write(formatSSE(toOpenAIErrorChunk(err)));
213
+ res.write(formatSSE(encoder.errorChunk(err)));
178
214
  res.write(formatSSEDone());
179
215
  res.end();
180
216
  }
@@ -183,28 +219,149 @@ async function handleChatCompletions(req, res) {
183
219
  }
184
220
  else {
185
221
  const events = await collectEvents(stream);
186
- const response = buildNonStreamingResponse(events, model);
222
+ const response = buildNonStreamingResponse(events, model, encoder.id);
187
223
  sendJson(res, 200, response);
188
224
  }
189
225
  }
190
226
  catch (err) {
191
- handleUpstreamError(res, err);
227
+ handleUpstreamError(res, err, "openai");
192
228
  }
193
229
  }
230
+ async function handleMessages(req, res) {
231
+ let rawBody;
232
+ try {
233
+ rawBody = await parseBody(req);
234
+ }
235
+ catch {
236
+ return sendAnthropicError(res, 400, "invalid_request_error", "Invalid JSON body");
237
+ }
238
+ let anthropicReq;
239
+ try {
240
+ anthropicReq = validateAnthropicRequest(rawBody);
241
+ }
242
+ catch (err) {
243
+ if (err instanceof ValidationError) {
244
+ return sendAnthropicError(res, 400, "invalid_request_error", err.message);
245
+ }
246
+ return sendAnthropicError(res, 400, "invalid_request_error", "Invalid request body");
247
+ }
248
+ const apiKey = extractApiKey(req);
249
+ if (!apiKey) {
250
+ return sendAnthropicError(res, 401, "authentication_error", "Missing API key");
251
+ }
252
+ const isStream = anthropicReq.stream === true;
253
+ const model = anthropicReq.model;
254
+ const encoder = new AnthropicStreamEncoder(model);
255
+ const ccBody = anToCCRequest(anthropicReq);
256
+ const abort = abortOnClientDisconnect(req, res);
257
+ try {
258
+ const result = await sendToCC(ccBody, { apiBase: config.ccApiBase, apiKey, ccVersion: config.ccVersion }, abort.signal);
259
+ const stream = result.stream;
260
+ if (isStream) {
261
+ res.writeHead(200, {
262
+ "Content-Type": "text/event-stream",
263
+ "Cache-Control": "no-cache",
264
+ Connection: "keep-alive",
265
+ ...corsHeaders(),
266
+ });
267
+ stream.on("data", (event) => {
268
+ for (const record of encoder.emit(event)) {
269
+ res.write(formatAnthropicSSE(record.event, record.data));
270
+ }
271
+ });
272
+ stream.on("end", () => res.end());
273
+ stream.on("error", (err) => {
274
+ logger.error("[stream] Anthropic streaming error:", err.message);
275
+ if (!res.destroyed) {
276
+ res.write(formatAnthropicSSE("error", {
277
+ type: "error",
278
+ error: { type: "api_error", message: err.message },
279
+ }));
280
+ res.end();
281
+ }
282
+ });
283
+ destroyStreamOnClientDisconnect(req, stream);
284
+ }
285
+ else {
286
+ const events = await collectEvents(stream);
287
+ const response = buildAnthropicResponse(events, model, encoder.messageId);
288
+ res.writeHead(200, { "Content-Type": "application/json", ...corsHeaders() });
289
+ res.end(JSON.stringify(response));
290
+ }
291
+ }
292
+ catch (err) {
293
+ handleUpstreamError(res, err, "anthropic");
294
+ }
295
+ }
296
+ async function handleCountTokens(req, res) {
297
+ let rawBody;
298
+ try {
299
+ rawBody = await parseBody(req);
300
+ }
301
+ catch {
302
+ return sendAnthropicError(res, 400, "invalid_request_error", "Invalid JSON body");
303
+ }
304
+ const body = rawBody;
305
+ const parts = [];
306
+ if (typeof body.system === "string")
307
+ parts.push(body.system);
308
+ else if (Array.isArray(body.system)) {
309
+ for (const b of body.system) {
310
+ if (b.text)
311
+ parts.push(b.text);
312
+ }
313
+ }
314
+ const msgs = body.messages;
315
+ if (msgs) {
316
+ for (const msg of msgs) {
317
+ parts.push(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content));
318
+ }
319
+ }
320
+ const tools = body.tools;
321
+ if (tools) {
322
+ for (const t of tools) {
323
+ parts.push(t.name ?? "", t.description ?? "", JSON.stringify(t.input_schema ?? {}));
324
+ }
325
+ }
326
+ const allText = parts.join("");
327
+ let cjk = 0;
328
+ let nonCjk = 0;
329
+ for (const ch of allText) {
330
+ const code = ch.codePointAt(0) ?? 0;
331
+ if ((code >= 0x4e00 && code <= 0x9fff) ||
332
+ (code >= 0x3040 && code <= 0x309f) ||
333
+ (code >= 0x30a0 && code <= 0x30ff) ||
334
+ (code >= 0xac00 && code <= 0xd7af)) {
335
+ cjk++;
336
+ }
337
+ else {
338
+ nonCjk++;
339
+ }
340
+ }
341
+ const estimated = Math.ceil(cjk + nonCjk / 4);
342
+ sendJson(res, 200, { input_tokens: estimated });
343
+ }
194
344
  // ──────────────────────────────────────────
195
345
  // Error handling
196
346
  // ──────────────────────────────────────────
197
- function handleUpstreamError(res, err) {
347
+ function handleUpstreamError(res, err, format) {
348
+ if (format === "anthropic") {
349
+ if (err instanceof UpstreamError) {
350
+ const status = err.statusCode >= 400 && err.statusCode < 500 ? err.statusCode : 502;
351
+ const type = ANTHROPIC_STATUS_ERROR_MAP[status] ?? "api_error";
352
+ sendAnthropicError(res, status, type, err.message);
353
+ }
354
+ else {
355
+ sendAnthropicError(res, 502, "api_error", err.message);
356
+ }
357
+ return;
358
+ }
198
359
  if (err instanceof UpstreamError) {
199
360
  const status = err.statusCode >= 400 && err.statusCode < 500 ? err.statusCode : 502;
200
- sendJson(res, status, {
201
- error: { message: err.message, type: "upstream_error", code: err.statusCode },
202
- });
361
+ sendOpenAIError(res, status, err.message);
203
362
  }
204
363
  else {
205
- sendJson(res, 502, {
206
- error: { message: err.message, type: "proxy_error" },
207
- });
364
+ sendOpenAIError(res, 502, err.message);
208
365
  }
209
366
  }
210
367
  export function createServer(cfg) {
@@ -224,6 +381,8 @@ export function createServer(cfg) {
224
381
  { method: "GET", path: "/health", handler: handleHealth },
225
382
  { method: "GET", path: "/v1/models", handler: handleModels },
226
383
  { method: "POST", path: "/v1/chat/completions", handler: handleChatCompletions },
384
+ { method: "POST", path: "/v1/messages", handler: handleMessages },
385
+ { method: "POST", path: "/v1/messages/count_tokens", handler: handleCountTokens },
227
386
  ];
228
387
  const server = http.createServer((req, res) => {
229
388
  if (req.method === "OPTIONS") {
@@ -234,6 +393,10 @@ export function createServer(cfg) {
234
393
  const pathname = parsedUrl.pathname;
235
394
  const route = routes.find((r) => r.method === req.method && r.path === pathname);
236
395
  if (!route) {
396
+ const isAnthropic = req.headers["anthropic-version"] !== undefined;
397
+ if (isAnthropic) {
398
+ return sendAnthropicError(res, 404, "not_found_error", "Not found");
399
+ }
237
400
  return sendJson(res, 404, { error: "Not found" });
238
401
  }
239
402
  try {