将 CatPaw IDE 的 AI 能力反向代理为 OpenAI 兼容 API 服务,并自带额度自动管理。
catpaw2api 是一个 Go 编写的反向代理服务,它:
- 逆向了 CatPaw IDE 的内部 API(RSA+AES 加密、SSE 流式协议)
- 自动从
state.vscdb读取认证 Token,无需手动复制 - 对外暴露标准 OpenAI API,可直接对接 ChatGPT 客户端、Cursor、Cherry Studio 等工具
- 模拟函数调用(Simulated Function Calling),让不支持 function calling 的后端也能完整支持工具调用
- 额度自动管理:定时查询剩余 credits,低于阈值时自动申请提额,无人值守持续运行
简而言之:你在 CatPaw IDE 里的 AI 额度 → 变成一个标准 OpenAI API → 任何支持 OpenAI API 的工具都能用,而且额度用完了会自动加。
- ✅ OpenAI 兼容:
/v1/chat/completions+/v1/models - ✅ 流式 & 非流式:同时支持
stream: true/false - ✅ 模拟函数调用:多层解析器,让 CatPaw 后端支持完整的 OpenAI function calling
- ✅ 自动 Token 管理:从
state.vscdb读取,每 5 分钟自动刷新 - ✅ RSA+AES 加密:完整逆向 CatPaw 的混合加密机制(同时持有公钥和私钥,请求加密 + 响应解密)
- ✅ 额度看门狗:后台定时查询余额,剩余 ≤ 阈值时自动调用
POST /api/user/addQuota提额 - ✅ 额度 Dashboard:可视化展示剩余/总额度、进度条、手动查询/提额按钮
- ✅ 系统提示词覆盖:通过环境变量或
system消息注入 - ✅ CatPaw 格式中和:自动注入规则覆盖 CatPaw 专用的
{{ edit_1 }}差异格式和文件路径代码块,确保第三方 IDE 兼容 - ✅ 原生 SSE 解析:直接使用 CatPaw 返回的 OpenAI 格式
choices[].delta,无需手动计算增量 - ✅ Token 用量传递:
usage和finish_reason从 CatPaw SSE 原样透传 - ✅ 多模型支持:glm-5.2, deepseek-v3.2, kimi-k2.6, LongCat-2.0 等 11 个模型
- ✅ Request ID 追踪:每个请求注入
X-Request-ID - ✅ CORS 支持:前端直连无障碍
go build -o catpaw2api.exe .# 方式 1: 自动从 CatPaw IDE 的 state.vscdb 读取 Token(推荐)
./catpaw2api.exe
# 方式 2: 手动指定 Token
CATPAW_ACCESS_TOKEN=xxx CATPAW_MIS_ID=xxx ./catpaw2api.exe
# 方式 3: 指定 DB 路径 + 系统提示词 + API Key + 额度管理
CATPAW_DB_PATH=/path/to/state.vscdb \
SYSTEM_PROMPT="你是一个代码助手" \
API_KEY=mykey \
QUOTA_AUTO_APPLY=true \
QUOTA_WATCH_INTERVAL=120 \
QUOTA_THRESHOLD=50 \
./catpaw2api.exe| 变量 | 默认值 | 说明 |
|---|---|---|
PORT |
8787 |
服务端口 |
API_KEY |
(空) | API 密钥,为空则不鉴权 |
CATPAW_DB_PATH |
(自动检测) | state.vscdb 路径 |
CATPAW_ACCESS_TOKEN |
(空) | 手动指定 Token |
CATPAW_MIS_ID |
(空) | 手动指定 MIS ID |
SYSTEM_PROMPT |
(空) | 自定义系统提示词,自动注入为第一条 system 消息 |
LOG_LEVEL |
info |
日志级别 (info/debug) |
QUOTA_AUTO_APPLY |
true |
是否启用额度自动提额 |
QUOTA_WATCH_INTERVAL |
300 |
额度检查间隔(秒),默认 5 分钟 |
QUOTA_THRESHOLD |
50 |
余额低于此值时自动提额 |
CatPaw IDE 的免费额度是有限的,跑大量 API 请求时会很快耗尽。本服务内置了额度看门狗,自动管理额度:
- 定时查询:后台 goroutine 每隔
QUOTA_WATCH_INTERVAL秒调用GET /api/user/limit查询剩余额度 - 自动提额:当
modelRemaingCount ≤ QUOTA_THRESHOLD时,自动调用POST /api/user/addQuota申请提额 - 结果展示:提额结果实时反映在 Dashboard 上
| 接口 | 方法 | 用途 |
|---|---|---|
/api/user/limit |
GET |
查询剩余/总额度 |
/api/user/addQuota |
POST |
申请提额(剩余 ≤ 50 时可用) |
这两个接口同样走 CatPaw 的 RSA+AES 加密通道,请求需加密、响应需解密。本服务通过持有的 RSA 私钥完成完整闭环。
打开 http://localhost:8787/ 即可看到:
- 剩余 / 总额 大字显示(低于阈值时变红)
- 进度条 可视化额度消耗
- 已用额度和百分比
- 「查询额度」按钮 — 手动触发一次查询
- 「申请提额」按钮 — 手动触发一次提额
# 查询当前额度
curl http://localhost:8787/api/quota
# 手动申请提额
curl -X POST http://localhost:8787/api/quota/apply# 流式请求
curl http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "glm-5.2",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'
# 非流式请求
curl http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "deepseek-v3.2",
"messages": [
{"role": "system", "content": "你是一个专业翻译"},
{"role": "user", "content": "翻译: Hello World"}
],
"stream": false
}'
# 查看可用模型
curl http://localhost:8787/v1/modelsfrom openai import OpenAI
client = OpenAI(
base_url="http://localhost:8787/v1",
api_key="YOUR_API_KEY" # 如果未设置 API_KEY 则随意填
)
# 流式
stream = client.chat.completions.create(
model="glm-5.2",
messages=[{"role": "user", "content": "写一个快排"}],
stream=True
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
# 非流式
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": "你是代码专家"},
{"role": "user", "content": "解释闭包"}
],
stream=False
)
print(response.choices[0].message.content)| 设置项 | 值 |
|---|---|
| API 地址 (Base URL) | http://localhost:8787/v1 |
| API Key | 你设置的 API_KEY(未设则随意填) |
| 模型名称 | glm-5.2 / deepseek-v3.2 / kimi-k2.6 等 |
Settings → Models → Override OpenAI Base URL → http://localhost:8787/v1
两种方式:
方式 A: 环境变量全局覆盖
SYSTEM_PROMPT="你是一个 Rust 专家,所有回答必须用 Rust 实现" ./catpaw2api.exe方式 B: 请求中直接传 system 消息
{
"model": "glm-5.2",
"messages": [
{"role": "system", "content": "你的自定义系统提示词"},
{"role": "user", "content": "你好"}
]
}如果同时设置了
SYSTEM_PROMPT环境变量和请求中的 system 消息,环境变量的会排在最前面。
CatPaw 后端原生不支持 OpenAI function calling,本代理通过提示词注入 + 多层解析器实现了完整的模拟函数调用。
- 工具指令注入:当请求携带
tools参数时,将工具定义作为 system 消息注入到messages数组开头(不放在mrulesContent中,避免 CatPaw 后端干扰) - 多层解析器(按优先级 fallback):
| 层级 | 格式 | 示例 |
|---|---|---|
| Layer 0 | <function=Name> XML 风格 |
<function=read><parameter=file>test.py</parameter></function> |
| Layer 1 | <tool_call> 标签 |
<tool_call>{"name":"read","arguments":{"file":"test.py"}}</tool_call> |
| Layer 2 | <ToolName attr="val" /> 直接标签 |
<read file="test.py" /> |
| Layer 2b | <ToolName><param>value</param></ToolName> XML子标签 |
<read><file>test.py</file></read> |
| Layer 3 | 裸 JSON | {"name":"read","arguments":{"file":"test.py"}} |
| Layer 4 | bash 代码块 → 命令映射 | ```bash\ncat test.py\n``` |
- 幻觉截断:检测到工具调用后,丢弃之后的所有文本(OpenAI 规范要求工具调用后不应有额外文本)
- 思考块处理:移除
<think></think>标签但保留内容(模型有时把工具调用放在思考块内)
response = client.chat.completions.create(
model="glm-5.2",
messages=[{"role": "user", "content": "读取 hello.py 的内容"}],
tools=[{
"type": "function",
"function": {
"name": "read_file",
"description": "读取文件内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"}
},
"required": ["path"]
}
}
}]
)
# response.choices[0].message.tool_calls 将包含解析出的工具调用由于后端模型不支持原生 Function Calling,以下问题为模拟方案的固有限制:
1. 模型提前终止对话
模型输出前导语(如"让我查看这个文件")后直接 finish_reason=stop,未输出工具调用 JSON。对话中断,Agent 无法继续。对话超过 10 轮后高频出现。
2. 长对话返回空响应
对话累积超过 40 条消息后,模型返回完全空内容(content="",finish_reason 为空),所有重试均失败。疑似上下文超限或后端推理异常。
3. 模型编造工具结果(幻觉)
模型不输出工具调用 JSON,而是直接编造完整的工具执行结果(<toolcall_status>done</toolcall_result> + 假文件内容)。单次幻觉输出可达 14000+ 字符,耗时 100+ 秒。代理已实现早期检测+中断,但根因在后端。
4. 输出格式不统一
同一模型不同轮次输出的工具调用格式完全不同(裸 JSON / XML 标签 / bash 代码块 / 自然语言混合),解析器需覆盖所有变体,无法保证 100% 覆盖率。
5. 缓解措施(已实现)
- 重试机制:解析失败时截断历史至最后 2 条 + 注入强制 JSON 指令,最多重试 3 次
- 早期幻觉检测:SSE 累积过程中检测到幻觉标签立即中断,不等完整生成
- 消息滑动窗口:保留第一条 system + 最近 11 条对话,单条超 4000 字符截断
- 前导语清洗:检测到工具调用后丢弃其后所有幻觉文本
💡 建议:纯对话场景(无 tools)不受上述限制影响,体验等同于直接使用 CatPaw IDE。
| 模型名 | modelType | 思考链 | 图片 |
|---|---|---|---|
glm-5.2 |
75 | ✅ | ❌ |
glm-5.1 |
59 | ❌ | ❌ |
glm-5 |
46 | ❌ | ❌ |
glm-5v-turbo |
60 | ❌ | ✅ |
deepseek-v3.2 |
9 | ❌ | ❌ |
kimi-k2.6 |
62 | ❌ | ✅ |
kimi-k2.5 |
41 | ❌ | ✅ |
LongCat-2.0 |
77 | ✅ | ❌ |
longcat-flash |
22 | ❌ | ❌ |
MiniMax-M2.7 |
56 | ✅ | ❌ |
MiniMax-M2.5 |
48 | ✅ | ❌ |
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/v1/chat/completions |
OpenAI 对话(流式 + 非流式 + 工具调用) |
GET |
/v1/models |
获取模型列表 |
GET |
/api/status |
服务状态 JSON |
GET |
/health |
健康检查 |
GET |
/ |
Dashboard 状态面板 |
CatPaw IDE 使用 RSA+AES 混合加密请求体:
- 生成随机 AES-128 密钥
- 用 AES-128-ECB (PKCS7) 加密请求体
- 将 AES 密钥转成 base64 字符串,用 RSA-OAEP (SHA1) 加密
- 发送加密体 +
encrypted-key头
- Token 存储:VS Code 的
globalState(state.vscdb) - Cookie 格式:
1d47d6ff96_passportid=<token>; f32a546874_ssoid=<token> - 认证头:
Catpaw-Auth: <token> - 租户:
5282fa6645 - 客户端环境:
LOCAL_IDE
CatPaw 的 SSE 同时返回两种内容表示:
choices[].delta(优先使用):已是 OpenAI 格式的增量内容,直接透传content(fallback):累积全文,需要手动计算增量 最后一条数据带finishReason和usage字段,代理原样透传为 OpenAI 的finish_reason和usage。
CatPaw 后端注入的系统提示词包含 IDE 专用格式指令({{ edit_1 }} 占位符、代码块带文件路径等),
这些格式在 Cursor / Cherry Studio 等第三方 IDE 中无法解析,导致对话异常结束。
代理通过 mrulesContent 字段自动注入覆盖规则,强制模型使用标准 Markdown 代码块格式,
确保所有 OpenAI 兼容客户端都能正常解析输出。
工具定义通过 messages 数组中的 system 消息注入(而非 mrulesContent),原因:
- CatPaw 后端对
mrulesContent中的工具相关内容有特殊处理,可能导致 SSE 流提前终止 - 注入到
messages中可以绕过后端干扰,让模型完整输出工具调用 JSON
catpaw2api/
├── main.go # 入口
├── config/constants.go # CatPaw 协议常量、模型映射、请求头
├── auth/
│ ├── token_manager.go # 从 state.vscdb 读取 + 自动刷新
│ └── http.go # HTTP 客户端
├── types/types.go # OpenAI / CatPaw 数据结构
├── converter/
│ ├── openai.go # OpenAI ↔ CatPaw 格式转换
│ ├── toolcall.go # 模拟函数调用解析器(五层 fallback)
│ ├── toolcall_test.go # 单元测试
│ └── toolcall_e2e_test.go # 端到端测试
├── server/
│ ├── server.go # HTTP 服务器 + 路由 + Dashboard
│ └── handlers.go # 请求处理(流式/非流式/工具调用)
├── crypto/crypto.go # RSA+AES 加密
├── catpaw_system_prompt.txt # CatPaw 原始系统提示词(逆向参考)
├── go.mod / go.sum # Go 模块定义
└── README.md # 你正在看的这个
MIT