@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 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
  [![CD](https://github.com/sunflower0305/claude-proxy/actions/workflows/cd.yml/badge.svg)](https://github.com/sunflower0305/claude-proxy/actions/workflows/cd.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/sunflower0305/claude-proxy/badge.svg?branch=master)](https://coveralls.io/github/sunflower0305/claude-proxy?branch=master)
6
6
  [![npm version](https://img.shields.io/npm/v/%40sunflower0305%2Fclaude-proxy)](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
7
- [![npm downloads](https://img.shields.io/npm/dm/%40sunflower0305%2Fclaude-proxy)](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
7
+ [![npm downloads](https://img.shields.io/npm/dm/%40sunflower0305%2Fclaude-proxy?cacheSeconds=60)](https://www.npmjs.com/package/@sunflower0305/claude-proxy)
8
8
  [![GitHub stars](https://img.shields.io/github/stars/sunflower0305/claude-proxy?cacheSeconds=60)](https://github.com/sunflower0305/claude-proxy/stargazers)
9
9
  [![License](https://img.shields.io/github/license/sunflower0305/claude-proxy)](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 `kimi`.
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
- | `QWEN_ANTHROPIC_BASE_URL`, `DEEPSEEK_ANTHROPIC_BASE_URL`, `GLM_ANTHROPIC_BASE_URL`, `MINIMAX_ANTHROPIC_BASE_URL`, `KIMI_ANTHROPIC_BASE_URL` | Override the upstream Anthropic-compatible base URL for a provider. |
55
- | `QWEN_MODEL`, `DEEPSEEK_MODEL`, `GLM_MODEL`, `MINIMAX_MODEL`, `KIMI_MODEL` | Override the default upstream model for a provider. |
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) ? process.env.PROVIDER : "deepseek";
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(cors());
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=any-string-works ║
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.1.3",
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": ">=18"
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 threads",
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",