@sunflower0305/claude-proxy 1.2.0 → 1.3.1
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 +10 -3
- package/CHANGELOG.md +24 -0
- package/README.md +32 -22
- package/dist/proxy.d.ts +6 -13
- package/dist/proxy.js +364 -369
- package/package.json +48 -39
package/.env.example
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
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
|
-
QWEN_MODEL=
|
|
20
|
+
QWEN_MODEL=qwen3.7-plus
|
|
16
21
|
DEEPSEEK_MODEL=deepseek-v4-pro
|
|
17
22
|
GLM_MODEL=glm-5.1
|
|
18
|
-
MINIMAX_MODEL=
|
|
23
|
+
MINIMAX_MODEL=minimax-m3
|
|
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,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.1] - 2026-06-03
|
|
6
|
+
|
|
7
|
+
Patch release of `@sunflower0305/claude-proxy`.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- default Qwen upstream model changed from `qwen-plus` to `qwen3.7-plus`
|
|
12
|
+
- default MiniMax upstream model changed from `minimax-m2.7-highspeed` to `minimax-m3`
|
|
13
|
+
- README and `.env.example` now document the upgraded provider defaults
|
|
14
|
+
|
|
15
|
+
Detailed release notes: [docs/releases/1.3.1.md](docs/releases/1.3.1.md)
|
|
16
|
+
|
|
17
|
+
## [1.3.0] - 2026-05-06
|
|
18
|
+
|
|
19
|
+
Minor release of `@sunflower0305/claude-proxy`.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Anthropic-compatible proxy support for MIMO through the `mimo` provider
|
|
24
|
+
- `MIMO_API_KEY`, `MIMO_MODEL`, and `MIMO_ANTHROPIC_BASE_URL` configuration
|
|
25
|
+
- local integration coverage for MIMO provider switching, model mapping, and non-streaming request proxying
|
|
26
|
+
|
|
27
|
+
Detailed release notes: [docs/releases/1.3.0.md](docs/releases/1.3.0.md)
|
|
28
|
+
|
|
5
29
|
## [1.2.0] - 2026-05-01
|
|
6
30
|
|
|
7
31
|
Minor release of `@sunflower0305/claude-proxy`.
|
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
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)
|
|
8
8
|
[](https://github.com/sunflower0305/claude-proxy/stargazers)
|
|
9
|
-
[](https://zread.ai/sunflower0305/claude-proxy)
|
|
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
|
|
|
@@ -38,33 +38,38 @@ Example `.env`:
|
|
|
38
38
|
```dotenv
|
|
39
39
|
PROVIDER=deepseek
|
|
40
40
|
PROXY_PORT=8080
|
|
41
|
+
# Optional: require clients to send this token to write endpoints.
|
|
42
|
+
# PROXY_API_KEY=your-local-proxy-token
|
|
41
43
|
DEEPSEEK_API_KEY=your-deepseek-api-key
|
|
42
44
|
DEEPSEEK_MODEL=deepseek-v4-pro
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
Available variables:
|
|
46
48
|
|
|
47
|
-
| Variable
|
|
48
|
-
|
|
|
49
|
-
| `PROVIDER`
|
|
50
|
-
| `PROXY_PORT`
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
49
|
+
| Variable | Purpose |
|
|
50
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
51
|
+
| `PROVIDER` | Active provider. Defaults to `deepseek`. |
|
|
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`. |
|
|
54
|
+
| `QWEN_API_KEY` | API key for Qwen. |
|
|
55
|
+
| `DEEPSEEK_API_KEY` | API key for DeepSeek. |
|
|
56
|
+
| `GLM_API_KEY` | API key for GLM. |
|
|
57
|
+
| `MINIMAX_API_KEY` | API key for MiniMax. |
|
|
58
|
+
| `KIMI_API_KEY` | API key for Kimi. |
|
|
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. |
|
|
58
62
|
|
|
59
63
|
Provider defaults:
|
|
60
64
|
|
|
61
|
-
| Provider
|
|
62
|
-
|
|
|
63
|
-
| **`deepseek` (default)** | `DEEPSEEK_MODEL` | **`deepseek-v4-pro`**
|
|
64
|
-
| `qwen`
|
|
65
|
-
| `glm`
|
|
66
|
-
| `minimax`
|
|
67
|
-
| `kimi`
|
|
65
|
+
| Provider | Model env | Default model |
|
|
66
|
+
| ------------------------ | ---------------- | ------------------------ |
|
|
67
|
+
| **`deepseek` (default)** | `DEEPSEEK_MODEL` | **`deepseek-v4-pro`** |
|
|
68
|
+
| `qwen` | `QWEN_MODEL` | `qwen3.7-plus` |
|
|
69
|
+
| `glm` | `GLM_MODEL` | `glm-5.1` |
|
|
70
|
+
| `minimax` | `MINIMAX_MODEL` | `minimax-m3` |
|
|
71
|
+
| `kimi` | `KIMI_MODEL` | `kimi-k2.6` |
|
|
72
|
+
| `mimo` | `MIMO_MODEL` | `mimo-v2.5-pro` |
|
|
68
73
|
|
|
69
74
|
You can use the bundled example as a starting point:
|
|
70
75
|
|
|
@@ -89,6 +94,11 @@ export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
|
89
94
|
export ANTHROPIC_API_KEY=any-string-works
|
|
90
95
|
```
|
|
91
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
|
+
|
|
92
102
|
Example SDK usage:
|
|
93
103
|
|
|
94
104
|
```ts
|
|
@@ -96,7 +106,7 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
96
106
|
|
|
97
107
|
const client = new Anthropic({
|
|
98
108
|
baseURL: "http://localhost:8080",
|
|
99
|
-
apiKey: "any-string",
|
|
109
|
+
apiKey: process.env.PROXY_API_KEY || "any-string",
|
|
100
110
|
});
|
|
101
111
|
```
|
|
102
112
|
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Claude Proxy
|
|
4
|
-
*
|
|
5
|
-
* Proxies Anthropic Messages API requests to provider-native
|
|
6
|
-
* Anthropic-compatible endpoints without translating protocols.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
10
|
-
* export ANTHROPIC_API_KEY=any-key-works
|
|
11
|
-
*/
|
|
12
1
|
import express from "express";
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
|
|
3
|
+
//#region src/proxy.d.ts
|
|
4
|
+
declare function createApp(): express.Express;
|
|
5
|
+
declare const app: express.Express;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { app, createApp };
|
package/dist/proxy.js
CHANGED
|
@@ -1,410 +1,403 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Claude Proxy
|
|
4
|
-
*
|
|
5
|
-
* Proxies Anthropic Messages API requests to provider-native
|
|
6
|
-
* Anthropic-compatible endpoints without translating protocols.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
10
|
-
* export ANTHROPIC_API_KEY=any-key-works
|
|
11
|
-
*/
|
|
12
2
|
import express from "express";
|
|
13
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
14
4
|
import { loadEnvFile } from "node:process";
|
|
15
5
|
import { Readable } from "node:stream";
|
|
16
6
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
//#region src/proxy.ts
|
|
8
|
+
/**
|
|
9
|
+
* Claude Proxy
|
|
10
|
+
*
|
|
11
|
+
* Proxies Anthropic Messages API requests to provider-native
|
|
12
|
+
* Anthropic-compatible endpoints without translating protocols.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* export ANTHROPIC_BASE_URL=http://localhost:8080
|
|
16
|
+
* export ANTHROPIC_API_KEY=any-key-works
|
|
17
|
+
* # If PROXY_API_KEY is set, use that same value instead.
|
|
18
|
+
*/
|
|
19
|
+
if (existsSync(".env")) loadEnvFile(".env");
|
|
19
20
|
const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
20
21
|
const HOP_BY_HOP_RESPONSE_HEADERS = new Set([
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
"connection",
|
|
23
|
+
"content-encoding",
|
|
24
|
+
"content-length",
|
|
25
|
+
"keep-alive",
|
|
26
|
+
"proxy-authenticate",
|
|
27
|
+
"proxy-authorization",
|
|
28
|
+
"te",
|
|
29
|
+
"trailer",
|
|
30
|
+
"transfer-encoding",
|
|
31
|
+
"upgrade"
|
|
31
32
|
]);
|
|
32
33
|
function pickEnv(...keys) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
return undefined;
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const value = process.env[key]?.trim();
|
|
36
|
+
if (value) return value;
|
|
37
|
+
}
|
|
39
38
|
}
|
|
40
39
|
const PROVIDERS = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
deepseek: {
|
|
41
|
+
baseUrl: pickEnv("DEEPSEEK_ANTHROPIC_BASE_URL") || "https://api.deepseek.com/anthropic",
|
|
42
|
+
apiKey: process.env.DEEPSEEK_API_KEY || "",
|
|
43
|
+
model: pickEnv("DEEPSEEK_MODEL") || "deepseek-v4-pro"
|
|
44
|
+
},
|
|
45
|
+
qwen: {
|
|
46
|
+
baseUrl: pickEnv("QWEN_ANTHROPIC_BASE_URL") || "https://dashscope.aliyuncs.com/apps/anthropic",
|
|
47
|
+
apiKey: process.env.QWEN_API_KEY || "",
|
|
48
|
+
model: pickEnv("QWEN_MODEL") || "qwen3.7-plus"
|
|
49
|
+
},
|
|
50
|
+
glm: {
|
|
51
|
+
baseUrl: pickEnv("GLM_ANTHROPIC_BASE_URL") || "https://open.bigmodel.cn/api/anthropic",
|
|
52
|
+
apiKey: process.env.GLM_API_KEY || "",
|
|
53
|
+
model: pickEnv("GLM_MODEL") || "glm-5.1"
|
|
54
|
+
},
|
|
55
|
+
minimax: {
|
|
56
|
+
baseUrl: pickEnv("MINIMAX_ANTHROPIC_BASE_URL") || "https://api.minimaxi.com/anthropic",
|
|
57
|
+
apiKey: process.env.MINIMAX_API_KEY || "",
|
|
58
|
+
model: pickEnv("MINIMAX_MODEL") || "minimax-m3"
|
|
59
|
+
},
|
|
60
|
+
kimi: {
|
|
61
|
+
baseUrl: pickEnv("KIMI_ANTHROPIC_BASE_URL") || "https://api.moonshot.cn/anthropic",
|
|
62
|
+
apiKey: process.env.KIMI_API_KEY || "",
|
|
63
|
+
model: pickEnv("KIMI_MODEL") || "kimi-k2.6"
|
|
64
|
+
},
|
|
65
|
+
mimo: {
|
|
66
|
+
baseUrl: pickEnv("MIMO_ANTHROPIC_BASE_URL") || "https://api.xiaomimimo.com/anthropic",
|
|
67
|
+
apiKey: process.env.MIMO_API_KEY || "",
|
|
68
|
+
model: pickEnv("MIMO_MODEL") || "mimo-v2.5-pro"
|
|
69
|
+
}
|
|
70
70
|
};
|
|
71
71
|
function isProviderKey(value) {
|
|
72
|
-
|
|
72
|
+
return Boolean(value && value in PROVIDERS);
|
|
73
73
|
}
|
|
74
74
|
function getInitialProvider() {
|
|
75
|
-
|
|
75
|
+
return isProviderKey(process.env.PROVIDER) ? process.env.PROVIDER : "deepseek";
|
|
76
76
|
}
|
|
77
77
|
function getProviderConfig(provider) {
|
|
78
|
-
|
|
78
|
+
return PROVIDERS[provider] || PROVIDERS.deepseek;
|
|
79
79
|
}
|
|
80
80
|
function getHeaderValue(value) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return value;
|
|
81
|
+
if (Array.isArray(value)) return value.join(",");
|
|
82
|
+
return value;
|
|
84
83
|
}
|
|
85
84
|
function buildUpstreamHeaders(req, stream, apiKey) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (anthropicBeta) {
|
|
96
|
-
headers["anthropic-beta"] = anthropicBeta;
|
|
97
|
-
}
|
|
98
|
-
return headers;
|
|
85
|
+
const headers = {
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
"x-api-key": apiKey,
|
|
88
|
+
"anthropic-version": getHeaderValue(req.headers["anthropic-version"]) || DEFAULT_ANTHROPIC_VERSION,
|
|
89
|
+
accept: getHeaderValue(req.headers.accept) || (stream ? "text/event-stream" : "application/json")
|
|
90
|
+
};
|
|
91
|
+
const anthropicBeta = getHeaderValue(req.headers["anthropic-beta"]);
|
|
92
|
+
if (anthropicBeta) headers["anthropic-beta"] = anthropicBeta;
|
|
93
|
+
return headers;
|
|
99
94
|
}
|
|
100
95
|
function getUpstreamUrl(baseUrl) {
|
|
101
|
-
|
|
96
|
+
return `${baseUrl.replace(/\/$/, "")}/v1/messages`;
|
|
102
97
|
}
|
|
103
98
|
function buildUpstreamBody(body, targetModel) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
normalized.model = targetModel;
|
|
108
|
-
return normalized;
|
|
99
|
+
const normalized = typeof body === "object" && body !== null ? { ...body } : {};
|
|
100
|
+
normalized.model = targetModel;
|
|
101
|
+
return normalized;
|
|
109
102
|
}
|
|
110
103
|
function copyUpstreamHeaders(upstream, res) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
104
|
+
for (const [key, value] of upstream.headers.entries()) {
|
|
105
|
+
if (HOP_BY_HOP_RESPONSE_HEADERS.has(key.toLowerCase())) continue;
|
|
106
|
+
res.setHeader(key, value);
|
|
107
|
+
}
|
|
116
108
|
}
|
|
117
109
|
function createProxyError(message) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
110
|
+
return {
|
|
111
|
+
type: "error",
|
|
112
|
+
error: {
|
|
113
|
+
type: "internal_error",
|
|
114
|
+
message
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createAuthenticationError() {
|
|
119
|
+
return {
|
|
120
|
+
type: "error",
|
|
121
|
+
error: {
|
|
122
|
+
type: "authentication_error",
|
|
123
|
+
message: "Invalid API key"
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function getBearerToken(authorization) {
|
|
128
|
+
return (authorization?.match(/^Bearer\s+(.+)$/i))?.[1]?.trim() || void 0;
|
|
125
129
|
}
|
|
126
130
|
function inferProviderFromModel(model) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const key of Object.keys(PROVIDERS)) {
|
|
131
|
-
if (normalizedModel.includes(key))
|
|
132
|
-
return key;
|
|
133
|
-
}
|
|
134
|
-
return undefined;
|
|
131
|
+
if (!model) return void 0;
|
|
132
|
+
const normalizedModel = model.toLowerCase();
|
|
133
|
+
for (const key of Object.keys(PROVIDERS)) if (normalizedModel.includes(key)) return key;
|
|
135
134
|
}
|
|
136
135
|
function logTimingEvent(trace, phase, extra = {}) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
console.log(`[ProxyTiming] ${JSON.stringify({
|
|
137
|
+
request_id: trace.requestId,
|
|
138
|
+
provider: trace.provider,
|
|
139
|
+
requested_model: trace.requestedModel,
|
|
140
|
+
target_model: trace.targetModel,
|
|
141
|
+
stream: trace.stream,
|
|
142
|
+
phase,
|
|
143
|
+
elapsed_ms: Date.now() - trace.startedAt,
|
|
144
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
145
|
+
...extra
|
|
146
|
+
})}`);
|
|
148
147
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
provider: currentProvider,
|
|
379
|
-
model: targetConfig.model,
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
return app;
|
|
148
|
+
function createApp() {
|
|
149
|
+
let currentProvider = getInitialProvider();
|
|
150
|
+
let requestSequence = 0;
|
|
151
|
+
const proxyApiKey = pickEnv("PROXY_API_KEY");
|
|
152
|
+
function getConfig(provider = currentProvider) {
|
|
153
|
+
return getProviderConfig(provider);
|
|
154
|
+
}
|
|
155
|
+
function getTargetModel(requestedModel) {
|
|
156
|
+
if (typeof requestedModel !== "string" || !requestedModel) return getConfig().model;
|
|
157
|
+
const normalizedModel = requestedModel.toLowerCase();
|
|
158
|
+
if (normalizedModel === "opus" || normalizedModel === "sonnet" || normalizedModel === "haiku") return getConfig().model;
|
|
159
|
+
if (normalizedModel.startsWith("claude-") && (normalizedModel.includes("-opus") || normalizedModel.includes("-sonnet") || normalizedModel.includes("-haiku"))) return getConfig().model;
|
|
160
|
+
return requestedModel;
|
|
161
|
+
}
|
|
162
|
+
function createRequestTrace(requestedModel, targetModel, stream) {
|
|
163
|
+
return {
|
|
164
|
+
requestId: `req-${++requestSequence}`,
|
|
165
|
+
provider: currentProvider,
|
|
166
|
+
requestedModel: typeof requestedModel === "string" && requestedModel ? requestedModel : targetModel,
|
|
167
|
+
targetModel,
|
|
168
|
+
stream,
|
|
169
|
+
startedAt: Date.now()
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function requireProxyApiKey(req, res, next) {
|
|
173
|
+
if (!proxyApiKey) {
|
|
174
|
+
next();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if ([getHeaderValue(req.headers["x-api-key"])?.trim(), getBearerToken(getHeaderValue(req.headers.authorization))].filter((token) => Boolean(token)).includes(proxyApiKey)) {
|
|
178
|
+
next();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
res.status(401).json(createAuthenticationError());
|
|
182
|
+
}
|
|
183
|
+
async function handleNonStreamingRequest(req, res) {
|
|
184
|
+
const config = getConfig();
|
|
185
|
+
const targetModel = getTargetModel(req.body?.model);
|
|
186
|
+
const requestBody = buildUpstreamBody(req.body, targetModel);
|
|
187
|
+
const trace = createRequestTrace(req.body?.model, targetModel, false);
|
|
188
|
+
logTimingEvent(trace, "start");
|
|
189
|
+
try {
|
|
190
|
+
const upstream = await fetch(getUpstreamUrl(config.baseUrl), {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: buildUpstreamHeaders(req, false, config.apiKey),
|
|
193
|
+
body: JSON.stringify(requestBody)
|
|
194
|
+
});
|
|
195
|
+
logTimingEvent(trace, "upstream_headers", {
|
|
196
|
+
status: upstream.status,
|
|
197
|
+
content_type: upstream.headers.get("content-type") || ""
|
|
198
|
+
});
|
|
199
|
+
const payload = Buffer.from(await upstream.arrayBuffer());
|
|
200
|
+
copyUpstreamHeaders(upstream, res);
|
|
201
|
+
res.status(upstream.status).send(payload);
|
|
202
|
+
logTimingEvent(trace, "completed", {
|
|
203
|
+
status: upstream.status,
|
|
204
|
+
bytes: payload.byteLength
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error("Request error:", error);
|
|
208
|
+
logTimingEvent(trace, "error", { message: error?.message || String(error) });
|
|
209
|
+
res.status(500).json(createProxyError(error.message));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function handleStreamingRequest(req, res) {
|
|
213
|
+
const config = getConfig();
|
|
214
|
+
const targetModel = getTargetModel(req.body?.model);
|
|
215
|
+
const requestBody = buildUpstreamBody(req.body, targetModel);
|
|
216
|
+
const trace = createRequestTrace(req.body?.model, targetModel, true);
|
|
217
|
+
const abortController = new AbortController();
|
|
218
|
+
let clientClosed = false;
|
|
219
|
+
let streamCompleted = false;
|
|
220
|
+
let sawFirstChunk = false;
|
|
221
|
+
logTimingEvent(trace, "start");
|
|
222
|
+
res.on("close", () => {
|
|
223
|
+
if (streamCompleted) return;
|
|
224
|
+
clientClosed = true;
|
|
225
|
+
abortController.abort();
|
|
226
|
+
logTimingEvent(trace, "client_aborted");
|
|
227
|
+
});
|
|
228
|
+
try {
|
|
229
|
+
const upstream = await fetch(getUpstreamUrl(config.baseUrl), {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: buildUpstreamHeaders(req, true, config.apiKey),
|
|
232
|
+
body: JSON.stringify(requestBody),
|
|
233
|
+
signal: abortController.signal
|
|
234
|
+
});
|
|
235
|
+
logTimingEvent(trace, "upstream_headers", {
|
|
236
|
+
status: upstream.status,
|
|
237
|
+
content_type: upstream.headers.get("content-type") || ""
|
|
238
|
+
});
|
|
239
|
+
copyUpstreamHeaders(upstream, res);
|
|
240
|
+
res.status(upstream.status);
|
|
241
|
+
if (!upstream.body) {
|
|
242
|
+
streamCompleted = true;
|
|
243
|
+
res.end();
|
|
244
|
+
logTimingEvent(trace, "completed", {
|
|
245
|
+
status: upstream.status,
|
|
246
|
+
bytes: 0,
|
|
247
|
+
no_body: true
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const upstreamStream = Readable.fromWeb(upstream.body);
|
|
252
|
+
upstreamStream.on("data", (chunk) => {
|
|
253
|
+
if (sawFirstChunk) return;
|
|
254
|
+
sawFirstChunk = true;
|
|
255
|
+
const chunkSize = Buffer.isBuffer(chunk) ? chunk.byteLength : Buffer.byteLength(String(chunk));
|
|
256
|
+
logTimingEvent(trace, "first_chunk", {
|
|
257
|
+
status: upstream.status,
|
|
258
|
+
chunk_bytes: chunkSize
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
upstreamStream.on("error", (error) => {
|
|
262
|
+
if (clientClosed) return;
|
|
263
|
+
console.error("Upstream stream error:", error);
|
|
264
|
+
logTimingEvent(trace, "error", {
|
|
265
|
+
status: upstream.status,
|
|
266
|
+
message: error?.message || String(error)
|
|
267
|
+
});
|
|
268
|
+
if (!res.writableEnded) res.end();
|
|
269
|
+
});
|
|
270
|
+
upstreamStream.pipe(res);
|
|
271
|
+
await new Promise((resolve, reject) => {
|
|
272
|
+
upstreamStream.on("end", () => {
|
|
273
|
+
streamCompleted = true;
|
|
274
|
+
logTimingEvent(trace, "completed", { status: upstream.status });
|
|
275
|
+
resolve();
|
|
276
|
+
});
|
|
277
|
+
upstreamStream.on("error", reject);
|
|
278
|
+
res.on("close", () => resolve());
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const wasAborted = error?.name === "AbortError" || abortController.signal.aborted;
|
|
282
|
+
if (clientClosed || wasAborted) {
|
|
283
|
+
console.warn("[Proxy] Client disconnected, streaming aborted");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
console.error("Request error:", error);
|
|
287
|
+
logTimingEvent(trace, "error", { message: error?.message || String(error) });
|
|
288
|
+
if (!res.headersSent) {
|
|
289
|
+
res.status(500).json(createProxyError(error.message));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!res.writableEnded) res.end();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const app = express();
|
|
296
|
+
app.use(express.json({ limit: "32mb" }));
|
|
297
|
+
app.get("/", (_req, res) => {
|
|
298
|
+
const config = getConfig();
|
|
299
|
+
res.json({
|
|
300
|
+
name: "claude-proxy",
|
|
301
|
+
status: "running",
|
|
302
|
+
provider: currentProvider,
|
|
303
|
+
model: config.model,
|
|
304
|
+
endpoints: {
|
|
305
|
+
messages: "POST /v1/messages",
|
|
306
|
+
health: "GET /health",
|
|
307
|
+
models: "GET /v1/models",
|
|
308
|
+
provider: "GET|POST /api/provider"
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
app.post("/v1/messages", requireProxyApiKey, async (req, res) => {
|
|
313
|
+
if (req.body?.stream) {
|
|
314
|
+
await handleStreamingRequest(req, res);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
await handleNonStreamingRequest(req, res);
|
|
318
|
+
});
|
|
319
|
+
app.get("/health", (_req, res) => {
|
|
320
|
+
const config = getConfig();
|
|
321
|
+
res.json({
|
|
322
|
+
status: "ok",
|
|
323
|
+
provider: currentProvider,
|
|
324
|
+
model: config.model
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
app.get("/v1/models", (_req, res) => {
|
|
328
|
+
res.json({ data: [
|
|
329
|
+
{
|
|
330
|
+
id: "claude-opus-4-7",
|
|
331
|
+
object: "model"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: "claude-sonnet-4-6",
|
|
335
|
+
object: "model"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: "claude-haiku-4-5",
|
|
339
|
+
object: "model"
|
|
340
|
+
}
|
|
341
|
+
] });
|
|
342
|
+
});
|
|
343
|
+
app.get("/api/provider", (_req, res) => {
|
|
344
|
+
const config = getConfig();
|
|
345
|
+
res.json({
|
|
346
|
+
provider: currentProvider,
|
|
347
|
+
model: config.model,
|
|
348
|
+
baseUrl: config.baseUrl,
|
|
349
|
+
availableProviders: Object.keys(PROVIDERS)
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
app.post("/api/provider", requireProxyApiKey, (req, res) => {
|
|
353
|
+
const { provider, model } = req.body ?? {};
|
|
354
|
+
const targetProvider = provider ?? inferProviderFromModel(model);
|
|
355
|
+
if (!isProviderKey(targetProvider)) {
|
|
356
|
+
res.status(400).json({
|
|
357
|
+
error: `Unknown provider: ${targetProvider}`,
|
|
358
|
+
available: Object.keys(PROVIDERS)
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const targetConfig = getConfig(targetProvider);
|
|
363
|
+
if (!targetConfig.apiKey) {
|
|
364
|
+
res.status(400).json({ error: `API key not set for: ${targetProvider}` });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const oldProvider = currentProvider;
|
|
368
|
+
currentProvider = targetProvider;
|
|
369
|
+
console.log(`Provider: ${oldProvider} -> ${currentProvider}`);
|
|
370
|
+
res.json({
|
|
371
|
+
success: true,
|
|
372
|
+
provider: currentProvider,
|
|
373
|
+
model: targetConfig.model
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
return app;
|
|
383
377
|
}
|
|
384
|
-
|
|
378
|
+
const app = createApp();
|
|
385
379
|
const PORT = parseInt(process.env.PROXY_PORT || "8080", 10);
|
|
386
380
|
function isMainModule() {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
381
|
+
const entryPath = process.argv[1];
|
|
382
|
+
if (!entryPath) return false;
|
|
383
|
+
try {
|
|
384
|
+
return realpathSync(entryPath) === realpathSync(fileURLToPath(import.meta.url));
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
396
388
|
}
|
|
397
389
|
if (isMainModule()) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
390
|
+
const initialProvider = getInitialProvider();
|
|
391
|
+
const initialConfig = getProviderConfig(initialProvider);
|
|
392
|
+
const clientApiKeyHint = pickEnv("PROXY_API_KEY") ? "same value as PROXY_API_KEY" : "any-string-works";
|
|
393
|
+
if (!initialConfig.apiKey) {
|
|
394
|
+
console.warn(`Warning: API key not configured for provider: ${initialProvider}`);
|
|
395
|
+
console.warn("Please set the appropriate environment variable in .env");
|
|
396
|
+
}
|
|
397
|
+
console.log(`Using ${initialProvider} as backend`);
|
|
398
|
+
console.log(`Model: ${initialConfig.model}`);
|
|
399
|
+
app.listen(PORT, () => {
|
|
400
|
+
console.log(`
|
|
408
401
|
╔═══════════════════════════════════════════════════════╗
|
|
409
402
|
║ claude-proxy ║
|
|
410
403
|
╠═══════════════════════════════════════════════════════╣
|
|
@@ -413,8 +406,10 @@ if (isMainModule()) {
|
|
|
413
406
|
╠═══════════════════════════════════════════════════════╣
|
|
414
407
|
║ Set these env vars in your app: ║
|
|
415
408
|
║ ANTHROPIC_BASE_URL=http://localhost:${PORT}
|
|
416
|
-
║ ANTHROPIC_API_KEY
|
|
409
|
+
║ ANTHROPIC_API_KEY=${clientApiKeyHint}
|
|
417
410
|
╚═══════════════════════════════════════════════════════╝
|
|
418
411
|
`);
|
|
419
|
-
|
|
412
|
+
});
|
|
420
413
|
}
|
|
414
|
+
//#endregion
|
|
415
|
+
export { app, createApp };
|
package/package.json
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunflower0305/claude-proxy",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "A proxy that lets Claude Agent SDK use domestic Chinese LLMs (DeepSeek, Qwen, GLM, MiniMax, Kimi, MIMO) as backend",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"anthropic",
|
|
7
|
+
"claude",
|
|
8
|
+
"deepseek",
|
|
9
|
+
"express",
|
|
10
|
+
"glm",
|
|
11
|
+
"kimi",
|
|
12
|
+
"llm",
|
|
13
|
+
"mimo",
|
|
14
|
+
"minimax",
|
|
15
|
+
"proxy",
|
|
16
|
+
"qwen"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/sunflower0305/claude-proxy#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/sunflower0305/claude-proxy/issues"
|
|
21
|
+
},
|
|
6
22
|
"license": "MIT",
|
|
7
|
-
"
|
|
8
|
-
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/sunflower0305/claude-proxy.git"
|
|
26
|
+
},
|
|
9
27
|
"bin": {
|
|
10
28
|
"claude-proxy": "dist/proxy.js"
|
|
11
29
|
},
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"types": "./dist/proxy.d.ts",
|
|
15
|
-
"import": "./dist/proxy.js"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
30
|
"files": [
|
|
19
31
|
"dist/",
|
|
20
32
|
"README.md",
|
|
@@ -22,41 +34,33 @@
|
|
|
22
34
|
"LICENSE",
|
|
23
35
|
".env.example"
|
|
24
36
|
],
|
|
25
|
-
"
|
|
26
|
-
|
|
37
|
+
"type": "module",
|
|
38
|
+
"main": "./dist/proxy.js",
|
|
39
|
+
"types": "./dist/proxy.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/proxy.d.ts",
|
|
43
|
+
"import": "./dist/proxy.js"
|
|
44
|
+
}
|
|
27
45
|
},
|
|
28
46
|
"publishConfig": {
|
|
29
47
|
"access": "public"
|
|
30
48
|
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/sunflower0305/claude-proxy.git"
|
|
34
|
-
},
|
|
35
|
-
"homepage": "https://github.com/sunflower0305/claude-proxy#readme",
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/sunflower0305/claude-proxy/issues"
|
|
38
|
-
},
|
|
39
|
-
"keywords": [
|
|
40
|
-
"anthropic",
|
|
41
|
-
"claude",
|
|
42
|
-
"proxy",
|
|
43
|
-
"express",
|
|
44
|
-
"llm",
|
|
45
|
-
"qwen",
|
|
46
|
-
"deepseek",
|
|
47
|
-
"glm",
|
|
48
|
-
"minimax",
|
|
49
|
-
"kimi"
|
|
50
|
-
],
|
|
51
49
|
"scripts": {
|
|
52
50
|
"dev": "tsx watch src/proxy.ts",
|
|
53
|
-
"build": "
|
|
51
|
+
"build": "vp pack",
|
|
54
52
|
"start": "node dist/proxy.js",
|
|
55
53
|
"prepack": "npm run build",
|
|
56
54
|
"prepublishOnly": "npm run test:proxy-local",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
55
|
+
"check": "vp check",
|
|
56
|
+
"check:fix": "vp check --fix",
|
|
57
|
+
"lint": "vp lint",
|
|
58
|
+
"lint:fix": "vp lint --fix",
|
|
59
|
+
"format": "vp fmt . --write",
|
|
60
|
+
"format:check": "vp fmt --check",
|
|
61
|
+
"test": "vp test",
|
|
62
|
+
"test:coverage": "vp test run tests/integration/proxy-local.test.ts --coverage --coverage.reporter=lcov --coverage.reporter=text --pool forks",
|
|
63
|
+
"test:proxy-local": "vp test run tests/integration/proxy-local.test.ts",
|
|
60
64
|
"test:provider-anthropic": "node --experimental-strip-types tests/integration/provider-anthropic.ts",
|
|
61
65
|
"test:provider-cli-e2e": "node --experimental-strip-types tests/integration/provider-cli-e2e.ts",
|
|
62
66
|
"report:provider-cli-e2e": "node --experimental-strip-types tests/integration/report-provider-cli-e2e.ts"
|
|
@@ -65,11 +69,16 @@
|
|
|
65
69
|
"express": "^4.21.0"
|
|
66
70
|
},
|
|
67
71
|
"devDependencies": {
|
|
68
|
-
"@vitest/coverage-v8": "^3.2.4",
|
|
69
72
|
"@types/express": "^4.17.21",
|
|
70
73
|
"@types/node": "^22.0.0",
|
|
74
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
71
75
|
"tsx": "^4.19.0",
|
|
72
76
|
"typescript": "^5.5.0",
|
|
73
|
-
"
|
|
77
|
+
"vite": "^8.0.11",
|
|
78
|
+
"vite-plus": "^0.1.20",
|
|
79
|
+
"vitest": "^4.1.5"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=20.12"
|
|
74
83
|
}
|
|
75
84
|
}
|