open-claude-code-proxy 1.1.2 → 1.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 +51 -164
- package/README_CN.md +199 -0
- package/package.json +4 -1
- package/server.js +188 -20
package/README.md
CHANGED
|
@@ -4,28 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
**Route API requests through the official Claude Code client**
|
|
6
6
|
|
|
7
|
-
[English](
|
|
7
|
+
[English](./README.md) | [中文](./README_CN.md)
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/open-claude-code-proxy)
|
|
10
10
|
[](https://www.npmjs.com/package/open-claude-code-proxy)
|
|
11
11
|
[](https://opensource.org/licenses/MIT)
|
|
12
12
|
[](https://nodejs.org/)
|
|
13
13
|
|
|
14
|
-
<img src="https://api.star-history.com/svg?repos=lkyxuan/open-claude-code-proxy&type=Date" alt="Star History Chart" width="600">
|
|
15
|
-
|
|
16
14
|
</div>
|
|
17
15
|
|
|
18
16
|
---
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
## What is this?
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
A local proxy server that forwards Anthropic API requests through the **official Claude Code CLI**. This allows you to use Claude in any app that supports the Anthropic API (like [OpenCode](https://opencode.ai), Cursor, etc.) while leveraging your existing Claude Code authentication.
|
|
23
21
|
|
|
24
|
-
###
|
|
22
|
+
### Features
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
- **Tool Compatibility**: Automatic tool name mapping (Read→read, WebSearch→websearch_exa_*, etc.)
|
|
25
|
+
- **Parameter Translation**: Converts parameter names (file_path→filePath, old_string→oldString, etc.)
|
|
26
|
+
- **Multi-turn Conversations**: Full conversation history support including tool_use/tool_result
|
|
27
|
+
- **Cache Optimization**: Handles cache_control to avoid conflicts with Claude Code
|
|
28
|
+
- **OpenCode Integration**: Auto-configures OpenCode with backup support
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
## How it works
|
|
29
31
|
|
|
30
32
|
```
|
|
31
33
|
Your App (OpenCode/Cursor/etc.)
|
|
@@ -37,16 +39,33 @@ Claude Code CLI (Official Client)
|
|
|
37
39
|
Anthropic API
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### Option 1: Let AI install for you (Recommended)
|
|
45
|
+
|
|
46
|
+
Copy and paste this prompt to your AI assistant (Claude, ChatGPT, etc.):
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Help me install and configure open-claude-code-proxy:
|
|
50
|
+
|
|
51
|
+
1. Install Claude Code CLI globally: npm install -g @anthropic-ai/claude-code
|
|
52
|
+
2. Login to Claude Code: claude auth login
|
|
53
|
+
3. Install the proxy: npm install -g open-claude-code-proxy
|
|
54
|
+
4. Start the proxy: claude-local-proxy
|
|
55
|
+
|
|
56
|
+
After installation, configure my AI coding tool (like OpenCode, Cursor) to use:
|
|
57
|
+
- baseURL: http://localhost:12346
|
|
58
|
+
- apiKey: any-string-works
|
|
59
|
+
```
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
### Option 2: Install globally (Manual)
|
|
43
62
|
|
|
44
63
|
```bash
|
|
45
64
|
npm install -g open-claude-code-proxy
|
|
46
65
|
claude-local-proxy
|
|
47
66
|
```
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
### Option 3: Clone and run
|
|
50
69
|
|
|
51
70
|
```bash
|
|
52
71
|
git clone https://github.com/lkyxuan/open-claude-code-proxy.git
|
|
@@ -54,7 +73,7 @@ cd open-claude-code-proxy
|
|
|
54
73
|
node cli.js
|
|
55
74
|
```
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
## Prerequisites
|
|
58
77
|
|
|
59
78
|
1. **Install Claude Code CLI**
|
|
60
79
|
```bash
|
|
@@ -74,7 +93,7 @@ node cli.js
|
|
|
74
93
|
source ~/.zshrc
|
|
75
94
|
```
|
|
76
95
|
|
|
77
|
-
|
|
96
|
+
## Port Configuration
|
|
78
97
|
|
|
79
98
|
The proxy supports flexible port configuration:
|
|
80
99
|
|
|
@@ -94,7 +113,7 @@ claude-local-proxy
|
|
|
94
113
|
|
|
95
114
|
On first run, you'll be prompted to customize the port. Your choice is saved for future use.
|
|
96
115
|
|
|
97
|
-
|
|
116
|
+
## OpenCode Auto-Configuration
|
|
98
117
|
|
|
99
118
|
The proxy can automatically configure [OpenCode](https://opencode.ai) for you:
|
|
100
119
|
|
|
@@ -111,7 +130,7 @@ claude-local-proxy -p 8080
|
|
|
111
130
|
claude-local-proxy -p 8080 --skip-opencode
|
|
112
131
|
```
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
## Configure Your Client
|
|
115
134
|
|
|
116
135
|
Point your app to the local proxy:
|
|
117
136
|
|
|
@@ -124,10 +143,14 @@ Point your app to the local proxy:
|
|
|
124
143
|
|
|
125
144
|
> **Note**: The API key can be any string - authentication is handled by your Claude Code session.
|
|
126
145
|
|
|
127
|
-
|
|
146
|
+
## Commands
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
|
|
148
|
+
This package provides two commands:
|
|
149
|
+
|
|
150
|
+
### `claude-local-proxy` - Interactive Mode
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
claude-local-proxy [options]
|
|
131
154
|
|
|
132
155
|
Options:
|
|
133
156
|
-p, --port <port> Server port (1024-65535)
|
|
@@ -136,7 +159,7 @@ Options:
|
|
|
136
159
|
-v, --version Show version
|
|
137
160
|
```
|
|
138
161
|
|
|
139
|
-
###
|
|
162
|
+
### `claude-proxy` - Background Mode
|
|
140
163
|
|
|
141
164
|
| Command | Description |
|
|
142
165
|
|---------|-------------|
|
|
@@ -147,155 +170,19 @@ Options:
|
|
|
147
170
|
| `claude-proxy logs -f` | View logs (live) |
|
|
148
171
|
| `claude-proxy test` | Test connectivity |
|
|
149
172
|
|
|
150
|
-
|
|
173
|
+
## API Endpoints
|
|
151
174
|
|
|
152
175
|
- `GET /health` - Health check
|
|
153
176
|
- `POST /v1/messages` - Messages API (Anthropic-compatible)
|
|
154
177
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<a name="中文"></a>
|
|
158
|
-
|
|
159
|
-
## 中文
|
|
160
|
-
|
|
161
|
-
### 这是什么?
|
|
162
|
-
|
|
163
|
-
一个本地代理服务器,将 Anthropic API 请求通过**官方 Claude Code CLI** 转发。这允许你在任何支持 Anthropic API 的应用(如 [OpenCode](https://opencode.ai)、Cursor 等)中使用 Claude,同时利用现有的 Claude Code 登录会话。
|
|
164
|
-
|
|
165
|
-
### 工作原理
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
你的应用 (OpenCode/Cursor/等)
|
|
169
|
-
↓ POST /v1/messages
|
|
170
|
-
localhost:12346 (本代理)
|
|
171
|
-
↓ 调用 `claude --print`
|
|
172
|
-
Claude Code CLI (官方客户端)
|
|
173
|
-
↓ 使用你的登录会话
|
|
174
|
-
Anthropic API
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### 快速开始
|
|
178
|
-
|
|
179
|
-
#### 方式 1: 全局安装(推荐)
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
npm install -g open-claude-code-proxy
|
|
183
|
-
claude-local-proxy
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
#### 方式 2: 克隆运行
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
git clone https://github.com/lkyxuan/open-claude-code-proxy.git
|
|
190
|
-
cd open-claude-code-proxy
|
|
191
|
-
node cli.js
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### 前置条件
|
|
195
|
-
|
|
196
|
-
1. **安装 Claude Code CLI**
|
|
197
|
-
```bash
|
|
198
|
-
npm install -g @anthropic-ai/claude-code
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
2. **登录 Claude Code**
|
|
202
|
-
```bash
|
|
203
|
-
claude auth login
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
3. **配置 Claude Code 环境变量**(如使用中转服务)
|
|
207
|
-
```bash
|
|
208
|
-
# 添加到 ~/.zshrc 或 ~/.bashrc
|
|
209
|
-
export ANTHROPIC_BASE_URL="https://your-relay-service.com/v1"
|
|
210
|
-
export ANTHROPIC_API_KEY="your-api-key"
|
|
211
|
-
source ~/.zshrc
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### 端口配置
|
|
215
|
-
|
|
216
|
-
代理支持灵活的端口配置方式:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
# 使用命令行参数
|
|
220
|
-
claude-local-proxy --port 8080
|
|
221
|
-
claude-local-proxy -p 8080
|
|
222
|
-
|
|
223
|
-
# 或使用环境变量
|
|
224
|
-
PORT=8080 claude-local-proxy
|
|
225
|
-
|
|
226
|
-
# 或使用已保存的配置 (~/.claude-proxy/config.json)
|
|
227
|
-
claude-local-proxy
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
**端口优先级**:命令行参数 > 配置文件 > 环境变量 > 默认值 (12346)
|
|
231
|
-
|
|
232
|
-
首次运行时会提示你自定义端口,你的选择会被保存供后续使用。
|
|
233
|
-
|
|
234
|
-
### OpenCode 自动配置
|
|
235
|
-
|
|
236
|
-
代理可以自动为 [OpenCode](https://opencode.ai) 配置连接:
|
|
237
|
-
|
|
238
|
-
- 自动检测 `~/.config/opencode/opencode.json` 配置文件
|
|
239
|
-
- 更新 `provider.anthropic.options.baseURL` 为代理端口
|
|
240
|
-
- 修改前自动备份(保留最近 3 个备份)
|
|
241
|
-
- 修改前会询问确认(使用 `--skip-opencode` 跳过)
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
# 自动配置 OpenCode
|
|
245
|
-
claude-local-proxy -p 8080
|
|
246
|
-
|
|
247
|
-
# 跳过 OpenCode 配置
|
|
248
|
-
claude-local-proxy -p 8080 --skip-opencode
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### 配置客户端
|
|
252
|
-
|
|
253
|
-
将你的应用指向本地代理:
|
|
254
|
-
|
|
255
|
-
```json
|
|
256
|
-
{
|
|
257
|
-
"baseURL": "http://localhost:12346",
|
|
258
|
-
"apiKey": "任意字符串"
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
> **注意**:API Key 可以是任意字符串,实际认证由 Claude Code 会话处理。
|
|
263
|
-
|
|
264
|
-
### CLI 选项
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
用法: claude-local-proxy [选项]
|
|
268
|
-
|
|
269
|
-
选项:
|
|
270
|
-
-p, --port <port> 服务器端口 (1024-65535)
|
|
271
|
-
--skip-opencode 跳过 OpenCode 自动配置
|
|
272
|
-
-h, --help 显示帮助
|
|
273
|
-
-v, --version 显示版本
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### 命令说明
|
|
277
|
-
|
|
278
|
-
| 命令 | 说明 |
|
|
279
|
-
|------|------|
|
|
280
|
-
| `claude-proxy start` | 启动服务(后台运行) |
|
|
281
|
-
| `claude-proxy stop` | 停止服务 |
|
|
282
|
-
| `claude-proxy restart` | 重启服务 |
|
|
283
|
-
| `claude-proxy status` | 查看状态 |
|
|
284
|
-
| `claude-proxy logs -f` | 查看日志(实时) |
|
|
285
|
-
| `claude-proxy test` | 测试连接 |
|
|
286
|
-
|
|
287
|
-
### API 端点
|
|
288
|
-
|
|
289
|
-
- `GET /health` - 健康检查
|
|
290
|
-
- `POST /v1/messages` - 消息接口(兼容 Anthropic API)
|
|
291
|
-
|
|
292
|
-
### 常见问题
|
|
178
|
+
## FAQ
|
|
293
179
|
|
|
294
|
-
|
|
|
295
|
-
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
180
|
+
| Issue | Solution |
|
|
181
|
+
|-------|----------|
|
|
182
|
+
| Login required | Run `claude auth login` to re-authenticate |
|
|
183
|
+
| Connection failed | Check environment variable: `echo $ANTHROPIC_BASE_URL` |
|
|
184
|
+
| Port in use | Use a different port: `claude-local-proxy -p 12347` |
|
|
185
|
+
| Tool name mismatch | Proxy auto-maps tool names (e.g., Read→read) |
|
|
299
186
|
|
|
300
187
|
---
|
|
301
188
|
|
|
@@ -307,6 +194,6 @@ PRs and issues are welcome!
|
|
|
307
194
|
|
|
308
195
|
### License
|
|
309
196
|
|
|
310
|
-
[MIT](./LICENSE)
|
|
197
|
+
[MIT](./LICENSE)
|
|
311
198
|
|
|
312
199
|
</div>
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Open Claude Code Proxy
|
|
4
|
+
|
|
5
|
+
**通过官方 Claude Code 客户端转发 API 请求**
|
|
6
|
+
|
|
7
|
+
[English](./README.md) | [中文](./README_CN.md)
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/open-claude-code-proxy)
|
|
10
|
+
[](https://www.npmjs.com/package/open-claude-code-proxy)
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[](https://nodejs.org/)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 这是什么?
|
|
19
|
+
|
|
20
|
+
一个本地代理服务器,将 Anthropic API 请求通过**官方 Claude Code CLI** 转发。这允许你在任何支持 Anthropic API 的应用(如 [OpenCode](https://opencode.ai)、Cursor 等)中使用 Claude,同时利用现有的 Claude Code 登录会话。
|
|
21
|
+
|
|
22
|
+
### 功能特性
|
|
23
|
+
|
|
24
|
+
- **工具兼容**:自动工具名映射(Read→read, WebSearch→websearch_exa_* 等)
|
|
25
|
+
- **参数转换**:自动参数名转换(file_path→filePath, old_string→oldString 等)
|
|
26
|
+
- **多轮对话**:完整对话历史支持,包括 tool_use/tool_result
|
|
27
|
+
- **缓存优化**:处理 cache_control 避免与 Claude Code 冲突
|
|
28
|
+
- **OpenCode 集成**:自动配置 OpenCode,支持备份
|
|
29
|
+
|
|
30
|
+
## 工作原理
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
你的应用 (OpenCode/Cursor/等)
|
|
34
|
+
↓ POST /v1/messages
|
|
35
|
+
localhost:12346 (本代理)
|
|
36
|
+
↓ 调用 `claude --print`
|
|
37
|
+
Claude Code CLI (官方客户端)
|
|
38
|
+
↓ 使用你的登录会话
|
|
39
|
+
Anthropic API
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
### 方式 1: 让 AI 帮你安装(推荐)
|
|
45
|
+
|
|
46
|
+
复制以下提示词发送给你的 AI 助手(Claude、ChatGPT 等):
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
帮我安装和配置 open-claude-code-proxy:
|
|
50
|
+
|
|
51
|
+
1. 全局安装 Claude Code CLI:npm install -g @anthropic-ai/claude-code
|
|
52
|
+
2. 登录 Claude Code:claude auth login
|
|
53
|
+
3. 安装代理:npm install -g open-claude-code-proxy
|
|
54
|
+
4. 启动代理:claude-local-proxy
|
|
55
|
+
|
|
56
|
+
安装完成后,配置我的 AI 编程工具(如 OpenCode、Cursor)使用以下设置:
|
|
57
|
+
- baseURL: http://localhost:12346
|
|
58
|
+
- apiKey: 任意字符串
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 方式 2: 全局安装(手动)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g open-claude-code-proxy
|
|
65
|
+
claude-local-proxy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 方式 3: 克隆运行
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/lkyxuan/open-claude-code-proxy.git
|
|
72
|
+
cd open-claude-code-proxy
|
|
73
|
+
node cli.js
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 前置条件
|
|
77
|
+
|
|
78
|
+
1. **安装 Claude Code CLI**
|
|
79
|
+
```bash
|
|
80
|
+
npm install -g @anthropic-ai/claude-code
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
2. **登录 Claude Code**
|
|
84
|
+
```bash
|
|
85
|
+
claude auth login
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
3. **配置 Claude Code 环境变量**(如使用中转服务)
|
|
89
|
+
```bash
|
|
90
|
+
# 添加到 ~/.zshrc 或 ~/.bashrc
|
|
91
|
+
export ANTHROPIC_BASE_URL="https://your-relay-service.com/v1"
|
|
92
|
+
export ANTHROPIC_API_KEY="your-api-key"
|
|
93
|
+
source ~/.zshrc
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 端口配置
|
|
97
|
+
|
|
98
|
+
代理支持灵活的端口配置方式:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 使用命令行参数
|
|
102
|
+
claude-local-proxy --port 8080
|
|
103
|
+
claude-local-proxy -p 8080
|
|
104
|
+
|
|
105
|
+
# 或使用环境变量
|
|
106
|
+
PORT=8080 claude-local-proxy
|
|
107
|
+
|
|
108
|
+
# 或使用已保存的配置 (~/.claude-proxy/config.json)
|
|
109
|
+
claude-local-proxy
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**端口优先级**:命令行参数 > 配置文件 > 环境变量 > 默认值 (12346)
|
|
113
|
+
|
|
114
|
+
首次运行时会提示你自定义端口,你的选择会被保存供后续使用。
|
|
115
|
+
|
|
116
|
+
## OpenCode 自动配置
|
|
117
|
+
|
|
118
|
+
代理可以自动为 [OpenCode](https://opencode.ai) 配置连接:
|
|
119
|
+
|
|
120
|
+
- 自动检测 `~/.config/opencode/opencode.json` 配置文件
|
|
121
|
+
- 更新 `provider.anthropic.options.baseURL` 为代理端口
|
|
122
|
+
- 修改前自动备份(保留最近 3 个备份)
|
|
123
|
+
- 修改前会询问确认(使用 `--skip-opencode` 跳过)
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# 自动配置 OpenCode
|
|
127
|
+
claude-local-proxy -p 8080
|
|
128
|
+
|
|
129
|
+
# 跳过 OpenCode 配置
|
|
130
|
+
claude-local-proxy -p 8080 --skip-opencode
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 配置客户端
|
|
134
|
+
|
|
135
|
+
将你的应用指向本地代理:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"baseURL": "http://localhost:12346",
|
|
140
|
+
"apiKey": "任意字符串"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> **注意**:API Key 可以是任意字符串,实际认证由 Claude Code 会话处理。
|
|
145
|
+
|
|
146
|
+
## 命令说明
|
|
147
|
+
|
|
148
|
+
本项目提供两个命令:
|
|
149
|
+
|
|
150
|
+
### `claude-local-proxy` - 交互模式
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
claude-local-proxy [选项]
|
|
154
|
+
|
|
155
|
+
选项:
|
|
156
|
+
-p, --port <port> 服务器端口 (1024-65535)
|
|
157
|
+
--skip-opencode 跳过 OpenCode 自动配置
|
|
158
|
+
-h, --help 显示帮助
|
|
159
|
+
-v, --version 显示版本
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `claude-proxy` - 后台模式
|
|
163
|
+
|
|
164
|
+
| 命令 | 说明 |
|
|
165
|
+
|------|------|
|
|
166
|
+
| `claude-proxy start` | 启动服务(后台运行) |
|
|
167
|
+
| `claude-proxy stop` | 停止服务 |
|
|
168
|
+
| `claude-proxy restart` | 重启服务 |
|
|
169
|
+
| `claude-proxy status` | 查看状态 |
|
|
170
|
+
| `claude-proxy logs -f` | 查看日志(实时) |
|
|
171
|
+
| `claude-proxy test` | 测试连接 |
|
|
172
|
+
|
|
173
|
+
## API 端点
|
|
174
|
+
|
|
175
|
+
- `GET /health` - 健康检查
|
|
176
|
+
- `POST /v1/messages` - 消息接口(兼容 Anthropic API)
|
|
177
|
+
|
|
178
|
+
## 常见问题
|
|
179
|
+
|
|
180
|
+
| 问题 | 解决方案 |
|
|
181
|
+
|------|----------|
|
|
182
|
+
| 提示需要登录 | 运行 `claude auth login` 重新登录 |
|
|
183
|
+
| 连接失败 | 检查环境变量 `echo $ANTHROPIC_BASE_URL` |
|
|
184
|
+
| 端口被占用 | 使用其他端口 `claude-local-proxy -p 12347` |
|
|
185
|
+
| 工具名不匹配 | 代理自动映射工具名(如 Read→read) |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
<div align="center">
|
|
190
|
+
|
|
191
|
+
### 贡献
|
|
192
|
+
|
|
193
|
+
欢迎提交 PR 和 Issue!
|
|
194
|
+
|
|
195
|
+
### 许可证
|
|
196
|
+
|
|
197
|
+
[MIT](./LICENSE)
|
|
198
|
+
|
|
199
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-claude-code-proxy",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Local proxy that forwards API requests through the official Claude Code CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,5 +33,8 @@
|
|
|
33
33
|
"homepage": "https://github.com/lkyxuan/open-claude-code-proxy#readme",
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.1"
|
|
36
39
|
}
|
|
37
40
|
}
|
package/server.js
CHANGED
|
@@ -6,7 +6,13 @@ const fs = require('fs');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
const PORT = process.env.PORT || 12346;
|
|
9
|
-
const
|
|
9
|
+
const LOG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.claude-proxy');
|
|
10
|
+
const LOG_FILE = path.join(LOG_DIR, 'proxy.log');
|
|
11
|
+
|
|
12
|
+
// 确保日志目录存在
|
|
13
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
14
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
// 日志函数 - 同时输出到控制台和文件
|
|
12
18
|
function log(message, data = null) {
|
|
@@ -19,6 +25,96 @@ function log(message, data = null) {
|
|
|
19
25
|
fs.appendFileSync(LOG_FILE, logLine + '\n');
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
// Claude Code → OpenCode 参数名映射(snake_case → camelCase)
|
|
29
|
+
const PARAM_MAPPING = {
|
|
30
|
+
'file_path': 'filePath',
|
|
31
|
+
'old_string': 'oldString',
|
|
32
|
+
'new_string': 'newString',
|
|
33
|
+
'replace_all': 'replaceAll'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Claude Code → OpenCode 工具名映射
|
|
37
|
+
const TOOL_NAME_MAPPING = {
|
|
38
|
+
// 大写 → 小写
|
|
39
|
+
'Read': 'read',
|
|
40
|
+
'Write': 'write',
|
|
41
|
+
'Edit': 'edit',
|
|
42
|
+
'Bash': 'bash',
|
|
43
|
+
'Glob': 'glob',
|
|
44
|
+
'Grep': 'grep',
|
|
45
|
+
'Task': 'task',
|
|
46
|
+
'TodoWrite': 'todowrite',
|
|
47
|
+
// 特殊映射
|
|
48
|
+
'WebSearch': 'websearch_exa_web_search_exa',
|
|
49
|
+
'WebFetch': 'webfetch'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 转换 tool_use 中的工具名和参数名(Claude Code 格式 → OpenCode 格式)
|
|
53
|
+
function convertToolUse(content) {
|
|
54
|
+
if (!Array.isArray(content)) return content;
|
|
55
|
+
|
|
56
|
+
return content.map(block => {
|
|
57
|
+
if (block.type !== 'tool_use') return block;
|
|
58
|
+
|
|
59
|
+
// 转换工具名
|
|
60
|
+
const convertedName = TOOL_NAME_MAPPING[block.name] || block.name;
|
|
61
|
+
|
|
62
|
+
// 转换参数名
|
|
63
|
+
const convertedInput = {};
|
|
64
|
+
if (block.input) {
|
|
65
|
+
for (const [key, value] of Object.entries(block.input)) {
|
|
66
|
+
const newKey = PARAM_MAPPING[key] || key;
|
|
67
|
+
convertedInput[newKey] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { ...block, name: convertedName, input: convertedInput };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 清理所有 cache_control - 因为 Claude Code CLI 会加自己的,我们不能再加
|
|
76
|
+
function sanitizeCacheControl(requestData) {
|
|
77
|
+
let removedCount = 0;
|
|
78
|
+
|
|
79
|
+
// 清理 system 中的 cache_control
|
|
80
|
+
if (Array.isArray(requestData.system)) {
|
|
81
|
+
for (const block of requestData.system) {
|
|
82
|
+
if (block.cache_control) {
|
|
83
|
+
delete block.cache_control;
|
|
84
|
+
removedCount++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 清理 messages 中的 cache_control
|
|
90
|
+
if (Array.isArray(requestData.messages)) {
|
|
91
|
+
for (const message of requestData.messages) {
|
|
92
|
+
if (Array.isArray(message.content)) {
|
|
93
|
+
for (const block of message.content) {
|
|
94
|
+
if (block.cache_control) {
|
|
95
|
+
delete block.cache_control;
|
|
96
|
+
removedCount++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 清理 tools 中的 cache_control
|
|
104
|
+
if (Array.isArray(requestData.tools)) {
|
|
105
|
+
for (const tool of requestData.tools) {
|
|
106
|
+
if (tool.cache_control) {
|
|
107
|
+
delete tool.cache_control;
|
|
108
|
+
removedCount++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (removedCount > 0) {
|
|
114
|
+
log(`清理了 ${removedCount} 个 cache_control(让 Claude Code 使用自己的缓存策略)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
22
118
|
// 启动时清空日志
|
|
23
119
|
fs.writeFileSync(LOG_FILE, `=== Claude Local Proxy 启动于 ${new Date().toISOString()} ===\n`);
|
|
24
120
|
|
|
@@ -60,6 +156,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
60
156
|
|
|
61
157
|
const requestData = JSON.parse(body);
|
|
62
158
|
|
|
159
|
+
// 清理 cache_control - Anthropic API 最多允许 4 个
|
|
160
|
+
sanitizeCacheControl(requestData);
|
|
161
|
+
|
|
63
162
|
// 限制日志大小,保留最近的请求(超过 100KB 截断)
|
|
64
163
|
try {
|
|
65
164
|
const stats = fs.statSync(LOG_FILE);
|
|
@@ -110,6 +209,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
110
209
|
: '[complex]'
|
|
111
210
|
}))
|
|
112
211
|
});
|
|
212
|
+
log('7️⃣ Tools', {
|
|
213
|
+
toolsCount: requestData.tools?.length || 0,
|
|
214
|
+
toolNames: requestData.tools?.map(t => t.name) || []
|
|
215
|
+
});
|
|
216
|
+
// 记录完整的工具定义(用于研究映射)
|
|
217
|
+
if (requestData.tools && requestData.tools.length > 0) {
|
|
218
|
+
log('完整 Tools 定义', JSON.stringify(requestData.tools, null, 2));
|
|
219
|
+
}
|
|
113
220
|
log('其他信息', {
|
|
114
221
|
apiKey: apiKey.substring(0, 20) + '...',
|
|
115
222
|
model: requestData.model,
|
|
@@ -145,15 +252,30 @@ async function handleNormalRequest(requestData, res) {
|
|
|
145
252
|
const messages = requestData.messages || [];
|
|
146
253
|
|
|
147
254
|
// 构建 prompt:把 messages 数组转成对话格式
|
|
148
|
-
|
|
255
|
+
let prompt = buildPromptFromMessages(messages);
|
|
256
|
+
|
|
257
|
+
// 如果有工具定义,在用户消息前面加上指令(而不是在 system prompt 中)
|
|
258
|
+
if (requestData.tools && requestData.tools.length > 0) {
|
|
259
|
+
const toolInstruction = `[PROXY MODE] You are being accessed through a proxy. For this request, use ONLY the following client-defined tools instead of any built-in tools:
|
|
260
|
+
|
|
261
|
+
${JSON.stringify(requestData.tools, null, 2)}
|
|
262
|
+
|
|
263
|
+
When you want to use one of these tools, respond with tool_use in your content array. Now here is the user's request:
|
|
264
|
+
|
|
265
|
+
`;
|
|
266
|
+
prompt = toolInstruction + prompt;
|
|
267
|
+
}
|
|
149
268
|
|
|
150
269
|
return new Promise((resolve, reject) => {
|
|
151
270
|
// 使用 JSON 输出格式,支持 tool_use
|
|
152
|
-
const
|
|
271
|
+
const args = [
|
|
153
272
|
'--print',
|
|
154
|
-
'--output-format', 'json'
|
|
155
|
-
|
|
156
|
-
|
|
273
|
+
'--output-format', 'json'
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
args.push(prompt);
|
|
277
|
+
|
|
278
|
+
const claude = spawn('claude', args, {
|
|
157
279
|
env: { ...process.env },
|
|
158
280
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
159
281
|
});
|
|
@@ -178,12 +300,15 @@ async function handleNormalRequest(requestData, res) {
|
|
|
178
300
|
// 解析 Claude 的 JSON 输出
|
|
179
301
|
const claudeResponse = JSON.parse(output.trim());
|
|
180
302
|
|
|
303
|
+
// 转换 tool_use 参数名(Claude Code → OpenCode 格式)
|
|
304
|
+
const convertedContent = convertToolUse(claudeResponse.content);
|
|
305
|
+
|
|
181
306
|
// 构建 Anthropic API 格式的响应
|
|
182
307
|
const response = {
|
|
183
308
|
id: claudeResponse.id || `msg_${Date.now()}`,
|
|
184
309
|
type: 'message',
|
|
185
310
|
role: 'assistant',
|
|
186
|
-
content:
|
|
311
|
+
content: convertedContent || [{ type: 'text', text: output.trim() }],
|
|
187
312
|
model: requestData.model || 'claude-sonnet-4-20250514',
|
|
188
313
|
stop_reason: claudeResponse.stop_reason || 'end_turn',
|
|
189
314
|
stop_sequence: claudeResponse.stop_sequence || null,
|
|
@@ -233,14 +358,27 @@ async function handleNormalRequest(requestData, res) {
|
|
|
233
358
|
async function handleStreamRequest(requestData, res) {
|
|
234
359
|
const messages = requestData.messages || [];
|
|
235
360
|
|
|
361
|
+
// 如果有工具定义,构建工具指令前缀
|
|
362
|
+
const toolInstruction = requestData.tools && requestData.tools.length > 0
|
|
363
|
+
? `[PROXY MODE] You are being accessed through a proxy. For this request, use ONLY the following client-defined tools instead of any built-in tools:
|
|
364
|
+
|
|
365
|
+
${JSON.stringify(requestData.tools, null, 2)}
|
|
366
|
+
|
|
367
|
+
When you want to use one of these tools, respond with tool_use in your content array. Now here is the user's request:
|
|
368
|
+
|
|
369
|
+
`
|
|
370
|
+
: '';
|
|
371
|
+
|
|
236
372
|
// 使用 stream-json 格式与 Claude Code 交互
|
|
237
373
|
return new Promise((resolve, reject) => {
|
|
238
|
-
const
|
|
374
|
+
const args = [
|
|
239
375
|
'--print',
|
|
240
376
|
'--input-format', 'stream-json',
|
|
241
377
|
'--output-format', 'stream-json',
|
|
242
378
|
'--verbose'
|
|
243
|
-
]
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
const claude = spawn('claude', args, {
|
|
244
382
|
env: { ...process.env },
|
|
245
383
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
246
384
|
});
|
|
@@ -320,6 +458,18 @@ async function handleStreamRequest(requestData, res) {
|
|
|
320
458
|
contentBlockIndex++;
|
|
321
459
|
}
|
|
322
460
|
|
|
461
|
+
// 转换工具名(Claude Code → OpenCode 格式)
|
|
462
|
+
const convertedName = TOOL_NAME_MAPPING[content.name] || content.name;
|
|
463
|
+
|
|
464
|
+
// 转换参数名(Claude Code → OpenCode 格式)
|
|
465
|
+
const convertedInput = {};
|
|
466
|
+
if (content.input) {
|
|
467
|
+
for (const [key, value] of Object.entries(content.input)) {
|
|
468
|
+
const newKey = PARAM_MAPPING[key] || key;
|
|
469
|
+
convertedInput[newKey] = value;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
323
473
|
// 发送新的 tool_use block 开始
|
|
324
474
|
sendSSE(res, 'content_block_start', {
|
|
325
475
|
type: 'content_block_start',
|
|
@@ -327,18 +477,18 @@ async function handleStreamRequest(requestData, res) {
|
|
|
327
477
|
content_block: {
|
|
328
478
|
type: 'tool_use',
|
|
329
479
|
id: content.id,
|
|
330
|
-
name:
|
|
480
|
+
name: convertedName,
|
|
331
481
|
input: {}
|
|
332
482
|
}
|
|
333
483
|
});
|
|
334
484
|
|
|
335
|
-
//
|
|
485
|
+
// 发送工具输入(使用转换后的参数名)
|
|
336
486
|
sendSSE(res, 'content_block_delta', {
|
|
337
487
|
type: 'content_block_delta',
|
|
338
488
|
index: contentBlockIndex,
|
|
339
489
|
delta: {
|
|
340
490
|
type: 'input_json_delta',
|
|
341
|
-
partial_json: JSON.stringify(
|
|
491
|
+
partial_json: JSON.stringify(convertedInput)
|
|
342
492
|
}
|
|
343
493
|
});
|
|
344
494
|
|
|
@@ -360,7 +510,7 @@ async function handleStreamRequest(requestData, res) {
|
|
|
360
510
|
// 发送结束事件
|
|
361
511
|
sendSSE(res, 'content_block_stop', {
|
|
362
512
|
type: 'content_block_stop',
|
|
363
|
-
index:
|
|
513
|
+
index: contentBlockIndex
|
|
364
514
|
});
|
|
365
515
|
|
|
366
516
|
sendSSE(res, 'message_delta', {
|
|
@@ -381,12 +531,18 @@ async function handleStreamRequest(requestData, res) {
|
|
|
381
531
|
reject(error);
|
|
382
532
|
});
|
|
383
533
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
534
|
+
// 构建完整的对话历史作为 prompt(包括 tool_use 和 tool_result)
|
|
535
|
+
let fullPrompt = buildPromptFromMessages(messages);
|
|
536
|
+
|
|
537
|
+
// 在 prompt 前加上工具指令(如果有)
|
|
538
|
+
if (toolInstruction) {
|
|
539
|
+
fullPrompt = toolInstruction + fullPrompt;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (fullPrompt) {
|
|
387
543
|
const input = JSON.stringify({
|
|
388
544
|
type: 'user',
|
|
389
|
-
message:
|
|
545
|
+
message: { role: 'user', content: fullPrompt }
|
|
390
546
|
}) + '\n';
|
|
391
547
|
|
|
392
548
|
claude.stdin.write(input);
|
|
@@ -424,7 +580,7 @@ function buildPromptFromMessages(messages) {
|
|
|
424
580
|
return prompt;
|
|
425
581
|
}
|
|
426
582
|
|
|
427
|
-
// 获取消息内容(支持 string 和 array
|
|
583
|
+
// 获取消息内容(支持 string 和 array 格式,包括 tool_use 和 tool_result)
|
|
428
584
|
function getMessageContent(message) {
|
|
429
585
|
if (typeof message.content === 'string') {
|
|
430
586
|
return message.content;
|
|
@@ -432,8 +588,20 @@ function getMessageContent(message) {
|
|
|
432
588
|
|
|
433
589
|
if (Array.isArray(message.content)) {
|
|
434
590
|
return message.content
|
|
435
|
-
.
|
|
436
|
-
|
|
591
|
+
.map(c => {
|
|
592
|
+
if (c.type === 'text') {
|
|
593
|
+
return c.text;
|
|
594
|
+
}
|
|
595
|
+
if (c.type === 'tool_use') {
|
|
596
|
+
return `[调用工具 ${c.name},参数: ${JSON.stringify(c.input)}]`;
|
|
597
|
+
}
|
|
598
|
+
if (c.type === 'tool_result') {
|
|
599
|
+
const content = typeof c.content === 'string' ? c.content : JSON.stringify(c.content);
|
|
600
|
+
return `[工具 ${c.tool_use_id} 返回: ${content.substring(0, 500)}${content.length > 500 ? '...' : ''}]`;
|
|
601
|
+
}
|
|
602
|
+
return '';
|
|
603
|
+
})
|
|
604
|
+
.filter(Boolean)
|
|
437
605
|
.join('\n');
|
|
438
606
|
}
|
|
439
607
|
|