@sifwenf/cc-proxy 0.1.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.
@@ -0,0 +1,337 @@
1
+ # cc-proxy
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md) | [日本語](README.ja.md)
4
+
5
+ 轻量级 LLM 代理服务器,用于 Claude Code - 将 Anthropic API 请求路由到任意 LLM 提供商。
6
+
7
+ ## 功能特性
8
+
9
+ - **模型路由**:将 Claude 模型(haiku、sonnet、opus)映射到任意提供商的模型
10
+ - **多提供商**:同时支持多个 LLM 提供商
11
+ - **格式转换**:自动在 Anthropic 和 OpenAI 格式之间转换
12
+ - **热重载**:配置更改自动应用,无需重启
13
+ - **性能优化**:异步批量日志记录,最小化开销
14
+ - **自动启动**:Shell 集成,静默后台启动
15
+
16
+ ## 安装方式
17
+
18
+ ### 方式 1:Bun 全局安装(推荐)
19
+
20
+ ```bash
21
+ # 从 npm 安装
22
+ bun install -g cc-proxy
23
+
24
+ # 或从本地源码安装
25
+ cd cc-proxy
26
+ bun link
27
+ ```
28
+
29
+ 这将全局安装 `ccp` 命令:
30
+
31
+ ```bash
32
+ ccp start # 启动代理服务器
33
+ ccp status # 检查状态
34
+ ccp config # 在编辑器中打开配置文件
35
+ ccp logs # 查看日志
36
+ ccp help # 显示所有命令
37
+ ```
38
+
39
+ 添加到 `~/.zshrc` 或 `~/.bashrc` 以实现 shell 打开时自动启动:
40
+
41
+ ```bash
42
+ eval "$(ccp activate)"
43
+ ```
44
+
45
+ ### 方式 2:手动安装
46
+
47
+ ```bash
48
+ # 克隆仓库
49
+ git clone https://github.com/your-username/cc-proxy.git
50
+ cd cc-proxy
51
+
52
+ # 安装依赖
53
+ bun install
54
+
55
+ # 初始化配置
56
+ bun run init
57
+ ```
58
+
59
+ ## 快速开始
60
+
61
+ ### 1. 初始化配置
62
+
63
+ ```bash
64
+ bun run init
65
+ ```
66
+
67
+ 这将创建:
68
+ - `~/.claude-code-proxy/` 目录结构
69
+ - 默认配置文件
70
+ - 日志目录
71
+
72
+ ### 2. 配置提供商
73
+
74
+ 编辑配置文件:
75
+
76
+ ```bash
77
+ ~/.claude-code-proxy/config.json
78
+ ```
79
+
80
+ ### 3. 启动服务器
81
+
82
+ ```bash
83
+ # 使用 ccp 命令(如果已全局安装)
84
+ ccp start
85
+
86
+ # 或使用 bun
87
+ bun start
88
+ ```
89
+
90
+
91
+ ## 配置参考
92
+
93
+ ### 完整配置结构
94
+
95
+ ```json
96
+ {
97
+ "server": {
98
+ "port": 3457,
99
+ "host": "127.0.0.1"
100
+ },
101
+ "logging": {
102
+ "enabled": true,
103
+ "level": "verbose",
104
+ "dir": "~/.claude-code-proxy/logs"
105
+ },
106
+ "providers": [
107
+ {
108
+ "name": "zp",
109
+ "baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
110
+ "apiKey": "your-api-key",
111
+ "format": "anthropic"
112
+ }
113
+ ],
114
+ "router": {
115
+ "haiku": "zp,glm-4.7",
116
+ "sonnet": "zp,glm-4.7",
117
+ "opus": "zp,glm-4.7",
118
+ "image": "zp,glm-4.7"
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### 参数说明
124
+
125
+ #### `server`
126
+
127
+ | 参数 | 类型 | 默认值 | 说明 |
128
+ |------|------|---------|------|
129
+ | `port` | number | `3457` | 代理服务器端口号 |
130
+ | `host` | string | `"127.0.0.1"` | 绑定的主机地址 |
131
+
132
+ #### `logging`
133
+
134
+ | 参数 | 类型 | 默认值 | 说明 |
135
+ |------|------|---------|------|
136
+ | `enabled` | boolean | `true` | 启用/禁用日志 |
137
+ | `level` | string | `"verbose"` | 日志级别:`"basic"`、`"standard"` 或 `"verbose"` |
138
+ | `dir` | string | `"~/.claude-code-proxy/logs"` | 日志文件存储目录 |
139
+
140
+ #### `providers`
141
+
142
+ 提供商配置数组。每个提供商对象:
143
+
144
+ | 参数 | 类型 | 必需 | 说明 |
145
+ |------|------|--------|------|
146
+ | `name` | string | ✅ 是 | 提供商唯一标识符(用于路由) |
147
+ | `baseUrl` | string | ✅ 是 | API 端点 URL |
148
+ | `apiKey` | string | ✅ 是 | API 认证密钥 |
149
+ | `format` | string | 否 | API 格式:`"anthropic"`、`"openai"` 或省略(透传模式) |
150
+
151
+ **格式类型:**
152
+ - `"anthropic"`:使用 Anthropic 兼容头(x-api-key, anthropic-version)
153
+ - `"openai"`:转换为 OpenAI 格式(Authorization: Bearer)
154
+ - 省略:透传模式(仅替换 API 密钥,转发原始头)
155
+
156
+ #### `router`
157
+
158
+ 将 Claude 模型名映射到提供商端点。
159
+
160
+ 格式:`"<claude-model>": "<provider-name>,<actual-model-name>"`
161
+
162
+ | 参数 | 说明 | 示例 |
163
+ |------|------|------|
164
+ | `haiku` | 快速/经济模型路由 | `"zp,glm-4.7"` |
165
+ | `sonnet` | 均衡性能模型路由 | `"zp,glm-4.7"` |
166
+ | `opus` | 高性能模型路由 | `"openrouter,anthropic/claude-opus-4.5"` |
167
+ | `image` | 图像生成模型路由 | `"zp,glm-4.7"` |
168
+
169
+ ### 路由语法
170
+
171
+ ```json
172
+ "router": {
173
+ "haiku": "provider-name,model-name"
174
+ }
175
+ ```
176
+
177
+ **组成部分:**
178
+ - **提供商名称**:必须匹配提供商的 `name` 字段
179
+ - **模型名称**:向提供商请求的实际模型
180
+
181
+ **示例:**
182
+ - `"zp,glm-4.7"`:使用 `zp` 提供商,请求 `glm-4.7` 模型
183
+ - `"openrouter,anthropic/claude-opus-4.5"`:使用 OpenRouter,请求 Claude Opus 4.5
184
+
185
+ ### 环境变量覆盖
186
+
187
+ 代理可以从 `ANTHROPIC_BASE_URL` 检测端口:
188
+
189
+ ```bash
190
+ export ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
191
+ ```
192
+
193
+ 这将覆盖 `server.port` 配置并使用端口 `3456`。
194
+
195
+ 服务器将:
196
+ - 从 `~/.claude-code-proxy/` 加载配置
197
+ - 在 `~/.claude-code-proxy/logs/` 存储日志
198
+ - 监听配置文件更改并热重载
199
+
200
+ ## 配置示例
201
+
202
+ ### 智谱 AI(Anthropic 格式)
203
+
204
+ ```json
205
+ {
206
+ "name": "zp",
207
+ "baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
208
+ "apiKey": "your-key",
209
+ "format": "anthropic"
210
+ }
211
+ ```
212
+
213
+ ### OpenRouter(OpenAI 格式)
214
+
215
+ ```json
216
+ {
217
+ "name": "openrouter",
218
+ "baseUrl": "https://openrouter.ai/api/v1/chat/completions",
219
+ "apiKey": "sk-or-...",
220
+ "format": "openai"
221
+ }
222
+ ```
223
+
224
+ ### 多提供商配置
225
+
226
+ ```json
227
+ {
228
+ "providers": [
229
+ {
230
+ "name": "zp",
231
+ "baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
232
+ "apiKey": "your-zpai-key"
233
+ },
234
+ {
235
+ "name": "openrouter",
236
+ "baseUrl": "https://openrouter.ai/api/v1/chat/completions",
237
+ "apiKey": "sk-or-..."
238
+ }
239
+ ],
240
+ "router": {
241
+ "haiku": "zp,glm-4.7",
242
+ "sonnet": "zp,glm-4.7",
243
+ "opus": "openrouter,anthropic/claude-opus-4.5"
244
+ }
245
+ }
246
+ ```
247
+
248
+ ## CLI 命令
249
+
250
+ ```bash
251
+ ccp activate # 静默自动启动(用于 shell 配置)
252
+ ccp start # 强制重启并启动
253
+ ccp stop # 停止代理服务器
254
+ ccp restart # 重启代理服务器
255
+ ccp status # 显示运行状态
256
+ ccp config # 在编辑器中打开配置文件(zed > vscode > vi)
257
+ ccp logs # 实时查看服务器日志
258
+ ccp help # 显示帮助信息
259
+ ```
260
+
261
+ ## Claude Code 集成
262
+
263
+ 设置环境变量以在 Claude Code 中使用 cc-proxy:
264
+
265
+ ```bash
266
+ export ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
267
+ export ANTHROPIC_API_KEY="routing-key"
268
+ ```
269
+
270
+ 如需在 shell 打开时自动启动,添加到 `~/.zshrc`:
271
+
272
+ ```bash
273
+ eval "$(ccp activate)"
274
+ ```
275
+
276
+ ## 日志管理
277
+
278
+ ### 查看最新日志
279
+
280
+ ```bash
281
+ # 服务器日志
282
+ ccp logs
283
+
284
+ # 请求日志(JSONL 格式)
285
+ tail -f ~/.claude-code-proxy/logs/requests.jsonl | jq '.'
286
+ ```
287
+
288
+ ### 过滤日志
289
+
290
+ ```bash
291
+ # 按提供商过滤
292
+ cat ~/.claude-code-proxy/logs/requests.jsonl | jq 'select(.provider == "zp")'
293
+
294
+ # 按类型过滤
295
+ cat ~/.claude-code-proxy/logs/requests.jsonl | jq 'select(.type == "forward")'
296
+ cat ~/.claude-code-proxy/logs/requests.jsonl | jq 'select(.type == "response")'
297
+ ```
298
+
299
+ ## 性能
300
+
301
+ 代理针对高并发场景进行了优化:
302
+
303
+ - **异步批量日志**:100ms 刷新间隔,最小化 I/O 阻塞
304
+ - **无 per-chunk 日志**:流式响应绕过 per-chunk 开销
305
+ - **高效内存使用**:约 100KB 缓冲区限制
306
+ - **优雅关闭**:退出前刷新日志
307
+
308
+ 可处理来自 agent-swarm 场景的 100+ 并发请求。
309
+
310
+ ## 故障排除
311
+
312
+ ### 服务器无法启动
313
+
314
+ ```bash
315
+ # 检查是否已运行
316
+ ccp status
317
+
318
+ # 查看日志
319
+ ccp logs
320
+
321
+ # 尝试强制重启
322
+ ccp restart
323
+ ```
324
+
325
+ ### 配置未加载
326
+
327
+ ```bash
328
+ # 验证配置文件存在
329
+ cat ~/.claude-code-proxy/config.json
330
+
331
+ # 重新初始化
332
+ bun run init
333
+ ```
334
+
335
+ ## 许可证
336
+
337
+ MIT
package/TASKS.md ADDED
@@ -0,0 +1,102 @@
1
+ # CC-Proxy 遗留任务
2
+
3
+ ## 功能完善
4
+
5
+ ### 1. Image 路由功能
6
+ **优先级:** 中
7
+
8
+ **描述:** 当请求用于图像识别时,使用 `router.image` 配置的provider和模型。
9
+
10
+ **实现方案:** 参考liteLLM,检测请求内容是否包含图像(`content` 类型为 `image`),如果是则使用 `image` 路由配置。
11
+
12
+ ```json
13
+ // 当前配置
14
+ "router": {
15
+ "image": "zp,glm-4.7" // 图像识别请求路由到此
16
+ }
17
+ ```
18
+
19
+ ---
20
+
21
+ ### 2. WebSearch 路由功能
22
+ **优先级:** 中
23
+
24
+ **描述:** 当Claude Code调用web search tool时,使用 `router.webSearch` 配置的provider和模型。
25
+
26
+ **实现方案:** 检测请求中的 `tools` 字段,判断是否包含web search相关的tool(如 `web_search`、`browser` 等),如果是则使用 `webSearch` 路由配置。
27
+
28
+ ```json
29
+ // 当前配置
30
+ "router": {
31
+ "webSearch": "op,google/gemini-2.5-flash" // web search请求路由到此
32
+ }
33
+ ```
34
+
35
+ ---
36
+
37
+ ### 3. OpenAI格式Provider支持
38
+ **优先级:** 低
39
+
40
+ **描述:** 当前代码只支持Anthropic格式的provider。需要支持OpenAI兼容格式的provider(如OpenRouter)。
41
+
42
+ **实现方案:**
43
+ 1. 在 `ProviderConfig` 中添加 `format` 字段:`"anthropic"` 或 `"openai"`
44
+ 2. 根据provider格式选择不同的请求/响应处理逻辑
45
+ 3. OpenAI格式需要:
46
+ - 请求头: `Authorization: Bearer ${apiKey}`
47
+ - 请求体转换:Anthropic → OpenAI格式
48
+ - 响应体转换:OpenAI → Anthropic格式
49
+ - 流式响应转换
50
+
51
+ ```json
52
+ // 预期配置
53
+ "providers": [
54
+ {
55
+ "name": "openrouter",
56
+ "baseUrl": "https://openrouter.ai/api/v1/chat/completions",
57
+ "apiKey": "...",
58
+ "format": "openai" // 新增字段
59
+ },
60
+ {
61
+ "name": "zp",
62
+ "baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
63
+ "apiKey": "...",
64
+ "format": "anthropic"
65
+ }
66
+ ]
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 优化改进
72
+
73
+ ### 4. 配置热重载
74
+ **优先级:** 低
75
+
76
+ **描述:** 修改config.json后无需重启服务,自动加载新配置。
77
+
78
+ ---
79
+
80
+ ### 5. 健康检查增强
81
+ **优先级:** 低
82
+
83
+ **描述:** `/status` 端点增加provider连通性检测。
84
+
85
+ ---
86
+
87
+ ### 6. 日志查询工具
88
+ **优先级:** 低
89
+
90
+ **描述:** 提供 `ccp logs --filter` 等命令,方便查询特定请求的日志。
91
+
92
+ ---
93
+
94
+ ## 文档
95
+
96
+ ### 7. README更新
97
+ **优先级:** 中
98
+
99
+ **描述:** 更新README.md文档,包含:
100
+ - 新的配置格式说明
101
+ - 路由配置示例
102
+ - CCP命令使用说明
@@ -0,0 +1,29 @@
1
+ {
2
+ "server": {
3
+ "port": 3457,
4
+ "host": "127.0.0.1"
5
+ },
6
+ "logging": {
7
+ "enabled": true,
8
+ "level": "verbose",
9
+ "dir": "./logs"
10
+ },
11
+ "providers": [
12
+ {
13
+ "name": "openrouter",
14
+ "baseUrl": "https://openrouter.ai/api/v1/chat/completions",
15
+ "apiKey": ""
16
+ },
17
+ {
18
+ "name": "zp",
19
+ "baseUrl": "https://api.z.ai/api/anthropic/v1/messages",
20
+ "apiKey": "your-zpai-key-here"
21
+ }
22
+ ],
23
+ "router": {
24
+ "haiku": "zp,glm-4.7",
25
+ "sonnet": "zp,glm-4.7",
26
+ "opus": "zp,glm-4.7",
27
+ "image": "zp,glm-4.7"
28
+ }
29
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@sifwenf/cc-proxy",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight LLM proxy server for Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "ccp": "./scripts/ccp"
8
+ },
9
+ "scripts": {
10
+ "start": "bun run src/server.ts",
11
+ "dev": "bun run --watch src/server.ts",
12
+ "init": "bun run src/scripts/init.ts"
13
+ },
14
+ "dependencies": {
15
+ "chokidar": "^5.0.0",
16
+ "hono": "^4.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/bun": "^1.0.0"
20
+ }
21
+ }
package/scripts/ccp ADDED
@@ -0,0 +1,126 @@
1
+ #!/bin/bash
2
+ # cc-proxy manager script (bun global install wrapper)
3
+
4
+ # Resolve symlink to get real script path
5
+ SCRIPT_SOURCE="${BASH_SOURCE[0]}"
6
+ while [ -h "$SCRIPT_SOURCE" ]; do
7
+ SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_SOURCE")" && pwd)"
8
+ SCRIPT_SOURCE="$(readlink "$SCRIPT_SOURCE")"
9
+ [[ $SCRIPT_SOURCE != /* ]] && SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_SOURCE"
10
+ done
11
+ SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_SOURCE")" && pwd)"
12
+ CCP_DIR="$SCRIPT_DIR"
13
+
14
+ # Check if we're in the scripts directory
15
+ if [ "$(basename "$CCP_DIR")" = "scripts" ]; then
16
+ CCP_DIR="$(dirname "$CCP_DIR")"
17
+ fi
18
+
19
+ CCP_DATA="$HOME/.claude-code-proxy"
20
+ CCP_LOG="$CCP_DATA/logs/server.log"
21
+ CCP_PID="$CCP_DATA/cc-proxy.pid"
22
+
23
+ activate() {
24
+ if pgrep -f "bun.*server.ts" > /dev/null 2>&1; then return; fi
25
+ mkdir -p "$CCP_DATA/logs" 2>/dev/null
26
+ (cd "$CCP_DIR" && nohup bun run src/server.ts > "$CCP_LOG" 2>&1 &)
27
+ echo $! > "$CCP_PID"
28
+ }
29
+
30
+ stop() {
31
+ [ -f "$CCP_PID" ] && kill $(cat "$CCP_PID") 2>/dev/null
32
+ rm -f "$CCP_PID"
33
+ pkill -f "bun.*server.ts" 2>/dev/null
34
+ }
35
+
36
+ status() {
37
+ if pgrep -f "bun.*server.ts" > /dev/null 2>&1; then
38
+ echo "✅ cc-proxy is running"
39
+ echo ""
40
+
41
+ # Try common ports for status endpoint
42
+ STATUS_JSON=""
43
+ for PORT in 3456 3457; do
44
+ STATUS_JSON=$(curl -s "http://127.0.0.1:$PORT/status" 2>/dev/null)
45
+ # Validate JSON before accepting (check for "server" key)
46
+ if [ -n "$STATUS_JSON" ] && [ "$STATUS_JSON" != "" ]; then
47
+ if echo "$STATUS_JSON" | grep -q '"server"'; then
48
+ break
49
+ fi
50
+ fi
51
+ done
52
+
53
+ if [ -n "$STATUS_JSON" ]; then
54
+ # Use status.js script from fixed location
55
+ TEMP_JSON=$(mktemp)
56
+ # Use heredoc to write JSON (handles special characters)
57
+ cat > "$TEMP_JSON" << EOF
58
+ $STATUS_JSON
59
+ EOF
60
+ bun run "$CCP_DATA/status.js" "$TEMP_JSON"
61
+ rm -f "$TEMP_JSON"
62
+ else
63
+ echo "⚠️ Status endpoint unavailable"
64
+ fi
65
+ else
66
+ echo "❌ cc-proxy is not running"
67
+ fi
68
+ }
69
+
70
+ logs() { tail -f "$CCP_LOG" 2>/dev/null || echo "No logs"; }
71
+
72
+ config() {
73
+ CCP_CONFIG="$CCP_DATA/config.json"
74
+ if [ ! -f "$CCP_CONFIG" ]; then
75
+ echo "❌ Config not found: $CCP_CONFIG"
76
+ echo "Run 'bun run init' first to create config"
77
+ exit 1
78
+ fi
79
+ # Try editors in order: zed > vscode > vi
80
+ if command -v zed >/dev/null 2>&1; then
81
+ zed "$CCP_CONFIG"
82
+ elif command -v code >/dev/null 2>&1; then
83
+ code "$CCP_CONFIG"
84
+ else
85
+ vi "$CCP_CONFIG"
86
+ fi
87
+ }
88
+
89
+ help() {
90
+ cat << 'HELP'
91
+ cc-proxy - Claude Code LLM Proxy Manager
92
+
93
+ USAGE: ccp <command>
94
+
95
+ COMMANDS:
96
+ activate Silent auto-start (for .zshrc)
97
+ start Force restart and start
98
+ stop Stop server
99
+ restart Restart server
100
+ status Show status
101
+ logs Tail logs
102
+ config Open config file in editor
103
+ help Show this help
104
+
105
+ CONFIG AUTO-RELOAD:
106
+ Edit ~/.claude-code-proxy/config.json to automatically reload configuration
107
+
108
+ GLOBAL INSTALL:
109
+ bun install -g cc-proxy # From npm
110
+ bun link # From local source
111
+
112
+ CONFIG: ~/.claude-code-proxy/config.json
113
+ HELP
114
+ }
115
+
116
+ case "$1" in
117
+ activate) activate ;;
118
+ start) stop; sleep 1; activate; echo "✅ started" ;;
119
+ stop) stop; echo "🛑 stopped" ;;
120
+ restart) stop; sleep 1; activate; echo "🔄 restarted" ;;
121
+ status) status ;;
122
+ logs) logs ;;
123
+ config) config ;;
124
+ help|--help|-h) help ;;
125
+ *) echo "❌ Unknown: $1"; help; exit 1 ;;
126
+ esac
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bun
2
+ import { readFileSync } from 'fs';
3
+
4
+ async function main() {
5
+ const chunks = [];
6
+ for await (const chunk of Bun.stdin.stream()) {
7
+ chunks.push(chunk);
8
+ }
9
+
10
+ const data = JSON.parse(Buffer.concat(chunks).toString());
11
+
12
+ console.log('📊 Model Routing:');
13
+ console.log('');
14
+
15
+ const models = ['haiku', 'sonnet', 'opus', 'image'];
16
+ for (const key of models) {
17
+ if (data.router[key]) {
18
+ console.log(` ${key.toUpperCase()} → ${data.router[key]}`);
19
+ }
20
+ }
21
+
22
+ console.log('');
23
+ console.log('📡 Providers:');
24
+ for (const p of data.providers) {
25
+ console.log(` • ${p.name} - ${p.baseUrl}`);
26
+ }
27
+
28
+ console.log('');
29
+ console.log(`🔗 Endpoint: http://${data.server.host}:${data.server.port}`);
30
+ }
31
+
32
+ main().catch(console.error);