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.
- package/README.md +96 -83
- package/dist/models.json +7 -1
- package/dist/proxy.js +8 -0
- package/dist/proxy.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +184 -21
- package/dist/server.js.map +1 -1
- package/dist/setup/claude-code.d.ts +2 -0
- package/dist/setup/claude-code.d.ts.map +1 -0
- package/dist/setup/claude-code.js +37 -0
- package/dist/setup/claude-code.js.map +1 -0
- package/dist/setup/opencode.d.ts.map +1 -1
- package/dist/setup/opencode.js.map +1 -1
- package/dist/stream.d.ts +1 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +6 -0
- package/dist/stream.js.map +1 -1
- package/dist/translate/anthropic-models.d.ts +2 -0
- package/dist/translate/anthropic-models.d.ts.map +1 -0
- package/dist/translate/anthropic-models.js +11 -0
- package/dist/translate/anthropic-models.js.map +1 -0
- package/dist/translate/anthropic-types.d.ts +168 -0
- package/dist/translate/anthropic-types.d.ts.map +1 -0
- package/dist/translate/anthropic-types.js +4 -0
- package/dist/translate/anthropic-types.js.map +1 -0
- package/dist/translate/anthropic.d.ts +24 -0
- package/dist/translate/anthropic.d.ts.map +1 -0
- package/dist/translate/anthropic.js +458 -0
- package/dist/translate/anthropic.js.map +1 -0
- package/dist/translate/openai.d.ts +10 -10
- package/dist/translate/openai.d.ts.map +1 -1
- package/dist/translate/openai.js +153 -208
- package/dist/translate/openai.js.map +1 -1
- package/dist/translate/util.d.ts +1 -2
- package/dist/translate/util.d.ts.map +1 -1
- package/dist/translate/util.js +33 -12
- package/dist/translate/util.js.map +1 -1
- package/dist/translate/validation.d.ts +2 -0
- package/dist/translate/validation.d.ts.map +1 -1
- package/dist/translate/validation.js +74 -0
- package/dist/translate/validation.js.map +1 -1
- package/dist/upstream.d.ts.map +1 -1
- package/dist/upstream.js.map +1 -1
- 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
|
-
|
|
4
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/commandcode-api-proxy)
|
|
6
|
+
[](https://www.npmjs.com/package/commandcode-api-proxy)
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
##
|
|
22
|
+
## Run the proxy
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
## Authentication
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
48
|
+
### CLI auth commands
|
|
41
49
|
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
+
Equivalent env vars (lower priority than CLI flags):
|
|
58
75
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
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
|
-
###
|
|
132
|
+
### Claude Code
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
157
|
+
Or set an alias:
|
|
142
158
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
162
|
-
|
|
|
163
|
-
| `deepseek-v4-pro`, `deepseek-v4`
|
|
164
|
-
| `deepseek-v4-flash`, `deepseek-flash`
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
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":";AAEA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,IAAI,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;YACvF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;IAClF,CAAC;SAAM,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;IACtC,MAAM,mBAAmB,EAAE,CAAC;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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 {
|
|
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(
|
|
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
|
|
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
|
|
166
|
+
return sendOpenAIError(res, 400, err.message);
|
|
131
167
|
}
|
|
132
|
-
return
|
|
168
|
+
return sendOpenAIError(res, 400, "Invalid request body");
|
|
133
169
|
}
|
|
134
170
|
const apiKey = extractApiKey(req);
|
|
135
171
|
if (!apiKey) {
|
|
136
|
-
return
|
|
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
|
|
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(
|
|
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
|
-
|
|
201
|
-
error: { message: err.message, type: "upstream_error", code: err.statusCode },
|
|
202
|
-
});
|
|
361
|
+
sendOpenAIError(res, status, err.message);
|
|
203
362
|
}
|
|
204
363
|
else {
|
|
205
|
-
|
|
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 {
|