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.
Files changed (4) hide show
  1. package/README.md +51 -164
  2. package/README_CN.md +199 -0
  3. package/package.json +4 -1
  4. 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](#english) | [中文](#中文)
7
+ [English](./README.md) | [中文](./README_CN.md)
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/open-claude-code-proxy.svg?style=flat-square)](https://www.npmjs.com/package/open-claude-code-proxy)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/open-claude-code-proxy.svg?style=flat-square)](https://www.npmjs.com/package/open-claude-code-proxy)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
12
12
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen?style=flat-square)](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
- <a name="english"></a>
18
+ ## What is this?
21
19
 
22
- ## English
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
- ### What is this?
22
+ ### Features
25
23
 
26
- 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.
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
- ### How it works
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
- ### Quick Start
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
- #### Option 1: Install globally (Recommended)
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
- #### Option 2: Clone and run
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
- ### Prerequisites
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
- ### Port Configuration
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
- ### OpenCode Auto-Configuration
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
- ### Configure Your Client
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
- ### CLI Options
146
+ ## Commands
128
147
 
129
- ```
130
- Usage: claude-local-proxy [options]
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
- ### Commands
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
- ### API Endpoints
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
- | 提示需要登录 | 运行 `claude auth login` 重新登录 |
297
- | 连接失败 | 检查环境变量 `echo $ANTHROPIC_BASE_URL` |
298
- | 端口被占用 | 修改环境变量 `export PORT=12347` |
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) © 2025
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
+ [![npm version](https://img.shields.io/npm/v/open-claude-code-proxy.svg?style=flat-square)](https://www.npmjs.com/package/open-claude-code-proxy)
10
+ [![npm downloads](https://img.shields.io/npm/dm/open-claude-code-proxy.svg?style=flat-square)](https://www.npmjs.com/package/open-claude-code-proxy)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
12
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen?style=flat-square)](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.2",
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 LOG_FILE = path.join(__dirname, 'proxy.log');
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
- const prompt = buildPromptFromMessages(messages);
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 claude = spawn('claude', [
271
+ const args = [
153
272
  '--print',
154
- '--output-format', 'json',
155
- prompt
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: claudeResponse.content || [{ type: 'text', text: output.trim() }],
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 claude = spawn('claude', [
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: content.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(content.input)
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: 0
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
- // 发送用户消息给 Claude
385
- const lastUserMessage = messages.filter(m => m.role === 'user').pop();
386
- if (lastUserMessage) {
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: lastUserMessage
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
- .filter(c => c.type === 'text')
436
- .map(c => c.text)
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