@sunflower0305/claude-proxy 1.1.3 → 1.3.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/.env.example +8 -1
- package/CHANGELOG.md +29 -0
- package/README.md +18 -6
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.js +50 -9
- package/package.json +6 -8
package/.env.example
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
# Choose your provider: qwen | deepseek | glm | minimax | kimi
|
|
1
|
+
# Choose your provider: qwen | deepseek | glm | minimax | kimi | mimo
|
|
2
2
|
PROVIDER=deepseek
|
|
3
3
|
|
|
4
4
|
# Proxy server port (default: 8080)
|
|
5
5
|
PROXY_PORT=8080
|
|
6
6
|
|
|
7
|
+
# Optional local proxy token.
|
|
8
|
+
# When set, clients must send this value as x-api-key or Authorization: Bearer.
|
|
9
|
+
# PROXY_API_KEY=your-local-proxy-token
|
|
10
|
+
|
|
7
11
|
# API keys - set the one(s) you need
|
|
8
12
|
QWEN_API_KEY=your-qwen-api-key
|
|
9
13
|
DEEPSEEK_API_KEY=your-deepseek-key
|
|
10
14
|
GLM_API_KEY=your-glm-key
|
|
11
15
|
MINIMAX_API_KEY=your-minimax-key
|
|
12
16
|
KIMI_API_KEY=your-kimi-key
|
|
17
|
+
MIMO_API_KEY=your-mimo-key
|
|
13
18
|
|
|
14
19
|
# Optional model overrides
|
|
15
20
|
QWEN_MODEL=qwen-plus
|
|
@@ -17,6 +22,7 @@ DEEPSEEK_MODEL=deepseek-v4-pro
|
|
|
17
22
|
GLM_MODEL=glm-5.1
|
|
18
23
|
MINIMAX_MODEL=MiniMax-M2.7-highspeed
|
|
19
24
|
KIMI_MODEL=kimi-k2.6
|
|
25
|
+
MIMO_MODEL=mimo-v2.5-pro
|
|
20
26
|
|
|
21
27
|
# Optional upstream Anthropic-compatible base URL overrides
|
|
22
28
|
# QWEN_ANTHROPIC_BASE_URL=https://dashscope.aliyuncs.com/apps/anthropic
|
|
@@ -24,3 +30,4 @@ KIMI_MODEL=kimi-k2.6
|
|
|
24
30
|
# GLM_ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic
|
|
25
31
|
# MINIMAX_ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic
|
|
26
32
|
# KIMI_ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic
|
|
33
|
+
# MIMO_ANTHROPIC_BASE_URL=https://api.xiaomimimo.com/anthropic
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.0] - 2026-05-06
|
|
6
|
+
|
|
7
|
+
Minor release of `@sunflower0305/claude-proxy`.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Anthropic-compatible proxy support for MIMO through the `mimo` provider
|
|
12
|
+
- `MIMO_API_KEY`, `MIMO_MODEL`, and `MIMO_ANTHROPIC_BASE_URL` configuration
|
|
13
|
+
- local integration coverage for MIMO provider switching, model mapping, and non-streaming request proxying
|
|
14
|
+
|
|
15
|
+
Detailed release notes: [docs/releases/1.3.0.md](docs/releases/1.3.0.md)
|
|
16
|
+
|
|
17
|
+
## [1.2.0] - 2026-05-01
|
|
18
|
+
|
|
19
|
+
Minor release of `@sunflower0305/claude-proxy`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `.env` loading now uses Node.js built-in `.env` file support instead of the external `dotenv` package
|
|
24
|
+
- minimum supported Node.js version is now 20.12
|
|
25
|
+
- CORS is no longer enabled by default
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
- removed runtime dependency on `dotenv`
|
|
30
|
+
- removed unused `cors` and `@types/cors` dependencies
|
|
31
|
+
|
|
32
|
+
Detailed release notes: [docs/releases/1.2.0.md](docs/releases/1.2.0.md)
|
|
33
|
+
|
|
5
34
|
## [1.1.3] - 2026-04-24
|
|
6
35
|
|
|
7
36
|
Patch release of `@sunflower0305/claude-proxy`.
|
package/README.md
CHANGED
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
[](https://github.com/sunflower0305/claude-proxy/actions/workflows/cd.yml)
|
|
5
5
|
[](https://coveralls.io/github/sunflower0305/claude-proxy?branch=master)
|
|
6
6
|
[](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
|
|
7
|
-
[](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
|
|
7
|
+
[](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
|
|
8
8
|
[](https://github.com/sunflower0305/claude-proxy/stargazers)
|
|
9
9
|
[](https://github.com/sunflower0305/claude-proxy/blob/master/LICENSE)
|
|
10
10
|
|
|
11
11
|
`claude-proxy` is published on npm as `@sunflower0305/claude-proxy`. It is a lightweight Express proxy that lets Claude Code or the Claude Agent SDK talk to domestic Chinese LLM providers through Anthropic-compatible `/v1/messages` endpoints.
|
|
12
12
|
|
|
13
|
-
It currently supports `qwen`, `deepseek`, `glm`, `minimax`, and `
|
|
13
|
+
It currently supports `qwen`, `deepseek`, `glm`, `minimax`, `kimi`, and `mimo`.
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
+
Requires Node.js 20.12 or newer.
|
|
18
|
+
|
|
17
19
|
Run without installing:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
@@ -29,13 +31,15 @@ claude-proxy
|
|
|
29
31
|
|
|
30
32
|
## Configure
|
|
31
33
|
|
|
32
|
-
The proxy reads configuration from environment variables. You can export them in your shell or create a `.env` file in the directory where you run `claude-proxy`.
|
|
34
|
+
The proxy reads configuration from environment variables. You can export them in your shell or create a `.env` file in the directory where you run `claude-proxy`; `.env` loading uses Node.js built-in `.env` file support.
|
|
33
35
|
|
|
34
36
|
Example `.env`:
|
|
35
37
|
|
|
36
38
|
```dotenv
|
|
37
39
|
PROVIDER=deepseek
|
|
38
40
|
PROXY_PORT=8080
|
|
41
|
+
# Optional: require clients to send this token to write endpoints.
|
|
42
|
+
# PROXY_API_KEY=your-local-proxy-token
|
|
39
43
|
DEEPSEEK_API_KEY=your-deepseek-api-key
|
|
40
44
|
DEEPSEEK_MODEL=deepseek-v4-pro
|
|
41
45
|
```
|
|
@@ -46,13 +50,15 @@ Available variables:
|
|
|
46
50
|
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
47
51
|
| `PROVIDER` | Active provider. Defaults to `deepseek`. |
|
|
48
52
|
| `PROXY_PORT` | Local server port. Defaults to `8080`. |
|
|
53
|
+
| `PROXY_API_KEY` | Optional local proxy token for `POST /v1/messages` and `POST /api/provider`. |
|
|
49
54
|
| `QWEN_API_KEY` | API key for Qwen. |
|
|
50
55
|
| `DEEPSEEK_API_KEY` | API key for DeepSeek. |
|
|
51
56
|
| `GLM_API_KEY` | API key for GLM. |
|
|
52
57
|
| `MINIMAX_API_KEY` | API key for MiniMax. |
|
|
53
58
|
| `KIMI_API_KEY` | API key for Kimi. |
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
59
|
+
| `MIMO_API_KEY` | API key for MIMO. |
|
|
60
|
+
| `QWEN_ANTHROPIC_BASE_URL`, `DEEPSEEK_ANTHROPIC_BASE_URL`, `GLM_ANTHROPIC_BASE_URL`, `MINIMAX_ANTHROPIC_BASE_URL`, `KIMI_ANTHROPIC_BASE_URL`, `MIMO_ANTHROPIC_BASE_URL` | Override the upstream Anthropic-compatible base URL for a provider. |
|
|
61
|
+
| `QWEN_MODEL`, `DEEPSEEK_MODEL`, `GLM_MODEL`, `MINIMAX_MODEL`, `KIMI_MODEL`, `MIMO_MODEL` | Override the default upstream model for a provider. |
|
|
56
62
|
|
|
57
63
|
Provider defaults:
|
|
58
64
|
|
|
@@ -63,6 +69,7 @@ Provider defaults:
|
|
|
63
69
|
| `glm` | `GLM_MODEL` | `glm-5.1` |
|
|
64
70
|
| `minimax` | `MINIMAX_MODEL` | `MiniMax-M2.7-highspeed` |
|
|
65
71
|
| `kimi` | `KIMI_MODEL` | `kimi-k2.6` |
|
|
72
|
+
| `mimo` | `MIMO_MODEL` | `mimo-v2.5-pro` |
|
|
66
73
|
|
|
67
74
|
You can use the bundled example as a starting point:
|
|
68
75
|
|
|
@@ -87,6 +94,11 @@ export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
|
87
94
|
export ANTHROPIC_API_KEY=any-string-works
|
|
88
95
|
```
|
|
89
96
|
|
|
97
|
+
If you set `PROXY_API_KEY`, set the client `ANTHROPIC_API_KEY` to the same
|
|
98
|
+
value. The proxy accepts it through either `x-api-key` or
|
|
99
|
+
`Authorization: Bearer`. If `PROXY_API_KEY` is not set, the local proxy does not
|
|
100
|
+
validate the client API key and any non-empty string can be used.
|
|
101
|
+
|
|
90
102
|
Example SDK usage:
|
|
91
103
|
|
|
92
104
|
```ts
|
|
@@ -94,7 +106,7 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
94
106
|
|
|
95
107
|
const client = new Anthropic({
|
|
96
108
|
baseURL: "http://localhost:8080",
|
|
97
|
-
apiKey: "any-string",
|
|
109
|
+
apiKey: process.env.PROXY_API_KEY || "any-string",
|
|
98
110
|
});
|
|
99
111
|
```
|
|
100
112
|
|
package/dist/proxy.d.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
10
10
|
* export ANTHROPIC_API_KEY=any-key-works
|
|
11
|
+
* # If PROXY_API_KEY is set, use that same value instead.
|
|
11
12
|
*/
|
|
12
|
-
import "dotenv/config";
|
|
13
13
|
import express from "express";
|
|
14
14
|
export declare function createApp(): express.Express;
|
|
15
15
|
export declare const app: express.Express;
|
package/dist/proxy.js
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
10
10
|
* export ANTHROPIC_API_KEY=any-key-works
|
|
11
|
+
* # If PROXY_API_KEY is set, use that same value instead.
|
|
11
12
|
*/
|
|
12
|
-
import "dotenv/config";
|
|
13
|
-
import cors from "cors";
|
|
14
13
|
import express from "express";
|
|
15
|
-
import { realpathSync } from "node:fs";
|
|
14
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
15
|
+
import { loadEnvFile } from "node:process";
|
|
16
16
|
import { Readable } from "node:stream";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
+
if (existsSync(".env"))
|
|
19
|
+
loadEnvFile(".env");
|
|
18
20
|
const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
19
21
|
const HOP_BY_HOP_RESPONSE_HEADERS = new Set([
|
|
20
22
|
"connection",
|
|
@@ -66,12 +68,20 @@ const PROVIDERS = {
|
|
|
66
68
|
apiKey: process.env.KIMI_API_KEY || "",
|
|
67
69
|
model: pickEnv("KIMI_MODEL") || "kimi-k2.6",
|
|
68
70
|
},
|
|
71
|
+
mimo: {
|
|
72
|
+
baseUrl: pickEnv("MIMO_ANTHROPIC_BASE_URL") ||
|
|
73
|
+
"https://api.xiaomimimo.com/anthropic",
|
|
74
|
+
apiKey: process.env.MIMO_API_KEY || "",
|
|
75
|
+
model: pickEnv("MIMO_MODEL") || "mimo-v2.5-pro",
|
|
76
|
+
},
|
|
69
77
|
};
|
|
70
78
|
function isProviderKey(value) {
|
|
71
79
|
return Boolean(value && value in PROVIDERS);
|
|
72
80
|
}
|
|
73
81
|
function getInitialProvider() {
|
|
74
|
-
return isProviderKey(process.env.PROVIDER)
|
|
82
|
+
return isProviderKey(process.env.PROVIDER)
|
|
83
|
+
? process.env.PROVIDER
|
|
84
|
+
: "deepseek";
|
|
75
85
|
}
|
|
76
86
|
function getProviderConfig(provider) {
|
|
77
87
|
return PROVIDERS[provider] || PROVIDERS.deepseek;
|
|
@@ -122,6 +132,19 @@ function createProxyError(message) {
|
|
|
122
132
|
},
|
|
123
133
|
};
|
|
124
134
|
}
|
|
135
|
+
function createAuthenticationError() {
|
|
136
|
+
return {
|
|
137
|
+
type: "error",
|
|
138
|
+
error: {
|
|
139
|
+
type: "authentication_error",
|
|
140
|
+
message: "Invalid API key",
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function getBearerToken(authorization) {
|
|
145
|
+
const match = authorization?.match(/^Bearer\s+(.+)$/i);
|
|
146
|
+
return match?.[1]?.trim() || undefined;
|
|
147
|
+
}
|
|
125
148
|
function inferProviderFromModel(model) {
|
|
126
149
|
if (!model)
|
|
127
150
|
return undefined;
|
|
@@ -148,6 +171,7 @@ function logTimingEvent(trace, phase, extra = {}) {
|
|
|
148
171
|
export function createApp() {
|
|
149
172
|
let currentProvider = getInitialProvider();
|
|
150
173
|
let requestSequence = 0;
|
|
174
|
+
const proxyApiKey = pickEnv("PROXY_API_KEY");
|
|
151
175
|
function getConfig(provider = currentProvider) {
|
|
152
176
|
return getProviderConfig(provider);
|
|
153
177
|
}
|
|
@@ -179,6 +203,21 @@ export function createApp() {
|
|
|
179
203
|
startedAt: Date.now(),
|
|
180
204
|
};
|
|
181
205
|
}
|
|
206
|
+
function requireProxyApiKey(req, res, next) {
|
|
207
|
+
if (!proxyApiKey) {
|
|
208
|
+
next();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const clientTokens = [
|
|
212
|
+
getHeaderValue(req.headers["x-api-key"])?.trim(),
|
|
213
|
+
getBearerToken(getHeaderValue(req.headers.authorization)),
|
|
214
|
+
].filter((token) => Boolean(token));
|
|
215
|
+
if (clientTokens.includes(proxyApiKey)) {
|
|
216
|
+
next();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
res.status(401).json(createAuthenticationError());
|
|
220
|
+
}
|
|
182
221
|
async function handleNonStreamingRequest(req, res) {
|
|
183
222
|
const config = getConfig();
|
|
184
223
|
const targetModel = getTargetModel(req.body?.model);
|
|
@@ -307,8 +346,7 @@ export function createApp() {
|
|
|
307
346
|
}
|
|
308
347
|
}
|
|
309
348
|
const app = express();
|
|
310
|
-
app.use(
|
|
311
|
-
app.use(express.json({ limit: "50mb" }));
|
|
349
|
+
app.use(express.json({ limit: "32mb" }));
|
|
312
350
|
app.get("/", (_req, res) => {
|
|
313
351
|
const config = getConfig();
|
|
314
352
|
res.json({
|
|
@@ -324,7 +362,7 @@ export function createApp() {
|
|
|
324
362
|
},
|
|
325
363
|
});
|
|
326
364
|
});
|
|
327
|
-
app.post("/v1/messages", async (req, res) => {
|
|
365
|
+
app.post("/v1/messages", requireProxyApiKey, async (req, res) => {
|
|
328
366
|
if (req.body?.stream) {
|
|
329
367
|
await handleStreamingRequest(req, res);
|
|
330
368
|
return;
|
|
@@ -353,7 +391,7 @@ export function createApp() {
|
|
|
353
391
|
availableProviders: Object.keys(PROVIDERS),
|
|
354
392
|
});
|
|
355
393
|
});
|
|
356
|
-
app.post("/api/provider", (req, res) => {
|
|
394
|
+
app.post("/api/provider", requireProxyApiKey, (req, res) => {
|
|
357
395
|
const { provider, model } = (req.body ?? {});
|
|
358
396
|
const targetProvider = provider ?? inferProviderFromModel(model);
|
|
359
397
|
if (!isProviderKey(targetProvider)) {
|
|
@@ -397,6 +435,9 @@ function isMainModule() {
|
|
|
397
435
|
if (isMainModule()) {
|
|
398
436
|
const initialProvider = getInitialProvider();
|
|
399
437
|
const initialConfig = getProviderConfig(initialProvider);
|
|
438
|
+
const clientApiKeyHint = pickEnv("PROXY_API_KEY")
|
|
439
|
+
? "same value as PROXY_API_KEY"
|
|
440
|
+
: "any-string-works";
|
|
400
441
|
if (!initialConfig.apiKey) {
|
|
401
442
|
console.warn(`Warning: API key not configured for provider: ${initialProvider}`);
|
|
402
443
|
console.warn("Please set the appropriate environment variable in .env");
|
|
@@ -413,7 +454,7 @@ if (isMainModule()) {
|
|
|
413
454
|
╠═══════════════════════════════════════════════════════╣
|
|
414
455
|
║ Set these env vars in your app: ║
|
|
415
456
|
║ ANTHROPIC_BASE_URL=http://localhost:${PORT}
|
|
416
|
-
║ ANTHROPIC_API_KEY
|
|
457
|
+
║ ANTHROPIC_API_KEY=${clientApiKeyHint}
|
|
417
458
|
╚═══════════════════════════════════════════════════════╝
|
|
418
459
|
`);
|
|
419
460
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunflower0305/claude-proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "A proxy that lets Claude Agent SDK use domestic Chinese LLMs (DeepSeek, Qwen, GLM, MiniMax) as backend",
|
|
5
|
+
"description": "A proxy that lets Claude Agent SDK use domestic Chinese LLMs (DeepSeek, Qwen, GLM, MiniMax, Kimi, MIMO) as backend",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./dist/proxy.js",
|
|
8
8
|
"types": "./dist/proxy.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
".env.example"
|
|
24
24
|
],
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=20.12"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"deepseek",
|
|
47
47
|
"glm",
|
|
48
48
|
"minimax",
|
|
49
|
-
"kimi"
|
|
49
|
+
"kimi",
|
|
50
|
+
"mimo"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
53
|
"dev": "tsx watch src/proxy.ts",
|
|
@@ -55,20 +56,17 @@
|
|
|
55
56
|
"prepack": "npm run build",
|
|
56
57
|
"prepublishOnly": "npm run test:proxy-local",
|
|
57
58
|
"test": "vitest run",
|
|
58
|
-
"test:coverage": "vitest run tests/integration/proxy-local.test.ts --coverage --coverage.reporter=lcov --coverage.reporter=text --pool
|
|
59
|
+
"test:coverage": "vitest run tests/integration/proxy-local.test.ts --coverage --coverage.reporter=lcov --coverage.reporter=text --pool forks",
|
|
59
60
|
"test:proxy-local": "vitest run tests/integration/proxy-local.test.ts",
|
|
60
61
|
"test:provider-anthropic": "node --experimental-strip-types tests/integration/provider-anthropic.ts",
|
|
61
62
|
"test:provider-cli-e2e": "node --experimental-strip-types tests/integration/provider-cli-e2e.ts",
|
|
62
63
|
"report:provider-cli-e2e": "node --experimental-strip-types tests/integration/report-provider-cli-e2e.ts"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
|
-
"cors": "^2.8.5",
|
|
66
|
-
"dotenv": "^16.4.5",
|
|
67
66
|
"express": "^4.21.0"
|
|
68
67
|
},
|
|
69
68
|
"devDependencies": {
|
|
70
69
|
"@vitest/coverage-v8": "^3.2.4",
|
|
71
|
-
"@types/cors": "^2.8.17",
|
|
72
70
|
"@types/express": "^4.17.21",
|
|
73
71
|
"@types/node": "^22.0.0",
|
|
74
72
|
"tsx": "^4.19.0",
|