Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ serve.pid
.sg_*
.sg-js/
.sg_login.html
.zcode/
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# zcode2api (zcode-hub)

**ZCode 账号运营 + 双协议网关一体机**:把池内 ZCode Coding Plan / Start Plan / API Key 账号额度,统一转换为标准的
**Anthropic Messages API**(`/v1/messages`)与 **OpenAI Chat Completions API**(`/v1/chat/completions`),向 Claude Code、Cline、Codex CLI、Chatbox、NextChat 等各类 Agent 与客户端提供高可用 API 服务。
**ZCode 账号运营 + 三协议网关一体机**:把池内 ZCode Coding Plan / Start Plan / API Key 账号额度,统一转换为标准的
**Anthropic Messages API**(`/v1/messages`)、**OpenAI Chat Completions API**(`/v1/chat/completions`)与 **OpenAI Responses API**(`/v1/responses`),向 Claude Code、Cline、Codex CLI(原生走 `/v1/responses`)、Chatbox、NextChat 等各类 Agent 与客户端提供高可用 API 服务。

自带一套完整的账号运营控制台:多账号池轮询、高可信成套桌面指纹仿真、官方首启安装序仿真、单账号并发控制、429/5xx/3012 阶梯容灾、请求全景监控、限时套餐自动与手动领取、OAuth 免密登录入池,以及免浏览器的阿里云无痕验证求解——开箱即用,自托管部署。

---

## 核心能力

- **双协议兼容网关**:原生支持 `POST /v1/messages`(Anthropic Messages 协议,支持流式 SSE)与 `POST /v1/chat/completions`(OpenAI 格式双向翻译,支持流式与非流式),并提供 `GET /v1/models` 模型列表。
- **三协议兼容网关**:原生支持 `POST /v1/messages`(Anthropic Messages 协议,支持流式 SSE)、`POST /v1/chat/completions`(OpenAI 格式双向翻译,支持流式与非流式)与 `POST /v1/responses`(OpenAI Responses 协议双向翻译,支持流式与非流式,Codex CLI 原生直连),并提供 `GET /v1/models` 模型列表。
- **高可信桌面指纹池(一号一台)**:入池自动生成加权桌面成套 SKU(Mac Sequoia/Tahoe、Win11 主流分辨率等),每账号独立全新 UUIDv4 `device_mid`,杜绝宿主机云 Linux 内核共享导致的账号集群关联风险。
- **官方首启安装序仿真**:入池与指纹轮换自动执行 `client/configs` 与激活事件上报(`app_launch` / `app_daily_active`),持久化 `install_id` 与 `installed_at`。
- **反代与 CDN 基础设施头深度清洗**:自动剔除 `x-forwarded-*`、`forwarded`、`cf-*`、`cdn-loop`、`x-real-ip`、`via`、`cookie`、`referer`、`origin`、`accept-language` 等网络中间件头部,杜绝部署网络拓扑泄露与客户端伪装冲突。
Expand Down Expand Up @@ -54,6 +54,7 @@ cp .env.example .env # 按需修改密钥、端口等
- 对话端点:
- Anthropic 协议:`POST http://127.0.0.1:3000/v1/messages`(兼容 Claude Code、Cline)
- OpenAI 协议:`POST http://127.0.0.1:3000/v1/chat/completions`(兼容 OpenAI 客户端)
- OpenAI Responses 协议:`POST http://127.0.0.1:3000/v1/responses`(Codex CLI 原生直连)
- 模型列表:`GET http://127.0.0.1:3000/v1/models`
- 探活端点:`GET http://127.0.0.1:3000/meta`

Expand Down Expand Up @@ -155,7 +156,7 @@ JWT 账号调用上游时需携带阿里云无痕验证参数(请求头 `X-Ali
## 鉴权

- **后台鉴权**:所有 `/admin/api/*` 需 `Authorization: Bearer <后台密码>`(也支持 `?app_key=`),内置 IP 失败节流保护。
- **网关鉴权(可选)**:在「设置」配置「网关 API Key」后,`/v1/messages` 与 `/v1/chat/completions` 须携带
- **网关鉴权(可选)**:在「设置」配置「网关 API Key」后,`/v1/messages`、`/v1/chat/completions` 与 `/v1/responses` 须携带
`Authorization: Bearer <key>` 或 `x-api-key: <key>`;留空则不校验。

## 环境变量
Expand Down
37 changes: 36 additions & 1 deletion docs/development/04-api-spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 04 — API 规范

状态:与 2.5.11 实现对齐。对外网关只有 Anthropic Messages、OpenAI Chat Completions、`/v1/models`;**没有** `/v1/responses`。
状态:与 2.5.11 实现对齐。对外网关为 Anthropic Messages、OpenAI Chat Completions、OpenAI Responses 三协议网关 + `/v1/models`,均已实现。

所有管理端点挂 `/admin/api/*`,需 `Authorization: Bearer <后台密码>`(连续失败达上限后 429 锁 5 分钟);网关端点按「网关 Key」配置可选鉴权(`Authorization: Bearer` 或 `x-api-key`,未配置即放行——生产必须配置)。

Expand All @@ -26,6 +26,30 @@
- 入站 OpenAI Chat 格式 → 翻译为 Anthropic 上游 → 翻译回 OpenAI 响应;`stream:true` 逐块翻译。
- usage/tool_calls 映射规则以移植对照表为准(`openai_compat.py`)。

### 1.4 `POST /v1/responses`(OpenAI Responses 协议)

- 入站 OpenAI Responses 格式 → 翻译为 Anthropic 上游(`responses_to_anthropic`)→ 翻译回 Responses 对象(`anthropic_to_responses`);`stream:true` 经 `ResponsesStreamConverter` 逐事件翻译。翻译器实现见 `app/responses_compat.py`,会话状态见 `app/response_store.py`。
- 请求字段映射:
- `instructions` → `system`;`input` 条目 `message` / `reasoning` / `function_call` / `function_call_output` → Anthropic 对应消息与块(`reasoning` 回放为 thinking,工具调用对 → `tool_use` / `tool_result`)。
- `tools` 仅接受 `function` 类型(`web_search` 等其他工具类型跳过);`tool_choice` 映射同 chat/completions。
- `reasoning.effort` → 上游思考档位:`minimal` / `low` → `low`,`high` → `high`,`xhigh` → `max`,`medium`(或缺省)→ 不携带该字段(上游默认档)。
- `max_output_tokens` → `max_tokens`;`model` 规范化同 1.1。
- 未支持字段(`store` / `include` / `metadata` / `prompt_cache_key` / `text` 等)安静忽略。
- 会话续接:`previous_response_id` 命中 → 上一轮完整输入输出拼入上游上下文;存储为进程内存 LRU(256 条,重启清零),未命中返回 400。
- 响应映射:thinking 思考以 `reasoning` 条目(`summary` 内 `summary_text`)回传;`stop_reason=max_tokens` → `status: "incomplete"`,其余 → `"completed"`。

```json
{ "id": "resp_…", "object": "response", "status": "completed", "model": "GLM-5.3-Flash", "output": [ { "type": "reasoning", "summary": [ { "type": "summary_text", "text": "…" } ] }, { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "…" } ] } ] }
```

- 流式为 Responses SSE(`event:` + `data:` 帧,帧内含 `sequence_number`),无 `[DONE]` 哨兵,以 `response.completed` 收尾。事件序:
- `response.created` → `response.in_progress`
- 思考:`response.output_item.added` → `response.reasoning_summary_part.added` → `response.reasoning_summary_text.delta`
- 正文:`response.output_item.added` → `response.content_part.added` → `response.output_text.delta` → `response.output_text.done`
- 工具:`response.function_call_arguments.delta` → `response.function_call_arguments.done`
- 收尾:`response.completed`
- 网关 Key 鉴权、故障转移与错误处理与 `/v1/messages` 一致(错误格式见 §1.6)。

### 1.6 错误格式

```json
Expand All @@ -35,6 +59,7 @@
| HTTP | type | 触发 |
|------|------|------|
| 400 | invalid_request / invalid_request_error | JSON 非法,或请求体不是对象 |
| 400 | invalid_request | `previous_response_id` 未命中(会话 LRU 无该记录) |
| 401/403 | (FastAPI HTTPException) | 网关 key 缺失/不符 |
| 500 | captcha_error | 验证码求解失败 |
| 500 | internal_error | 调度层未捕获异常(监控条目会收口) |
Expand Down Expand Up @@ -117,4 +142,14 @@ npx claude
curl http://127.0.0.1:3000/v1/chat/completions \
-H "Authorization: Bearer <gateway_key>" -H "Content-Type: application/json" \
-d '{"model":"glm-5.3","messages":[{"role":"user","content":"hi"}],"stream":true}'

# OpenAI Responses 兼容(非流式)
curl http://127.0.0.1:3000/v1/responses \
-H "Authorization: Bearer <gateway_key>" -H "Content-Type: application/json" \
-d '{"model":"glm-5.3","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"hi"}]}]}'

# OpenAI Responses 兼容(流式)
curl http://127.0.0.1:3000/v1/responses \
-H "Authorization: Bearer <gateway_key>" -H "Content-Type: application/json" \
-d '{"model":"glm-5.3","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"hi"}]}],"stream":true}'
```
2 changes: 1 addition & 1 deletion docs/development/07-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ M0 骨架 ──▶ M1 账号运营层(P1) ──▶ M2 网关扩展(P2)
- [ ] `translator/`:OpenAI↔Anthropic 请求/响应/usage/tool_calls 映射(对照表先写测试用例)
- [ ] `gateway/openai.py`:`/v1/chat/completions`(流式 + 批量)
- [ ] `translator/sse.py`:SSE 逐块双向翻译
- [ ] `gateway/responses.py`:`/v1/responses` + previous_response_id LRU
- [x] `gateway/responses.py`:`/v1/responses` + previous_response_id LRU
- [ ] 网关鉴权接入新端点;模型白名单统一

**出口标准**:Claude Code(Anthropic 透传)、OpenAI SDK(chat/completions 流式+批量)、Codex CLI(/v1/responses)三客户端在 Mock 上游回归全绿,真实上游冒烟各 1 次。
Expand Down
20 changes: 20 additions & 0 deletions docs/testing/02-unit-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,23 @@
| ZC-007 | 切换前保全 | 未入库的当前登录自动先快照 → 不丢号 | P0 |
| ZC-008 | 路径穿越防护 | id 含 `../`/非法字符 → 拒绝 | P0 |
| ZC-009 | ZCode 运行中热切换保护 | 检测到客户端运行 → 按 behavior 配置拒绝/杀进程 | P2 |

## 10. RS — Responses 协议翻译(responses_compat.py / response_store.py)

| ID | 用例 | 输入 → 预期 | 优先级 |
|----|------|-------------|--------|
| RS-001 | 基本请求映射 | `instructions` → system;input message 条目 → user/assistant 消息(input_text 块);model 规范化同网关 | P0 |
| RS-002 | reasoning.effort 映射 | 参数化 minimal/low/medium/high/xhigh → `low`/`low`/不带(上游默认档)/`high`/`max` | P0 |
| RS-003 | tools/tool_choice 映射 | function 工具 → Anthropic tools;tool_choice 各档同 chat/completions;非 function 类型(web_search 等)跳过 | P0 |
| RS-004 | function_call 条目映射 | input `function_call` / `function_call_output` 条目 → assistant `tool_use` / user `tool_result` 块 | P0 |
| RS-005 | reasoning 条目回放 | input `reasoning` 条目(summary_text)→ 上游 thinking 块 | P0 |
| RS-006 | max_output_tokens 映射 | `max_output_tokens: 512` → 上游 `max_tokens: 512` | P1 |
| RS-007 | 未支持字段安静忽略 | `store`/`include`/`metadata`/`prompt_cache_key`/`text` 等 → 不报错、不透传上游 | P1 |
| RS-008 | 非流式响应映射 | Anthropic content → output 数组:thinking → `reasoning`(summary_text)、text → `message`(output_text);`stop_reason=max_tokens` → `status:"incomplete"`,end_turn → `"completed"` | P0 |
| RS-009 | response_id 生成 | 非流式 envelope 生成新 `resp_` id;流式沿用上游 message id(`resp_<id>`)。入库由网关路由层完成(集成 INT-042 覆盖) | P0 |
| RS-010 | previous_response_id 链上下文 | store 命中 → 上一轮输入输出拼入上游 messages | P0 |
| RS-011 | previous_response_id 未命中 | 不存在 id → 400 invalid_request(见 api-spec §1.6 错误表) | P0 |
| RS-012 | 流式事件序 | 上游 message_start/thinking/text/tool_use 全链 → `response.created` → `response.in_progress` → 思考(`output_item.added` / `reasoning_summary_part.added` / `reasoning_summary_text.delta`)→ 正文(`output_item.added` / `content_part.added` / `output_text.delta` / `output_text.done`)→ 工具(`function_call_arguments.delta` / `.done`)→ `response.completed`;无 `[DONE]` 哨兵 | P0 |
| RS-013 | sequence_number 连续 | 全部 SSE 帧 `sequence_number` 严格递增、无跳变 | P1 |
| RS-014 | LRU 容量 256 | 连续写入 257 条 → 最早一条被淘汰,按其 id 再取 → `get` 返回 None | P0 |
| RS-015 | LRU 清零语义 | `clear()` 清空后旧 id 全部未命中;模块级存储无持久化,进程重启即失 | P1 |
13 changes: 12 additions & 1 deletion docs/testing/04-integration-e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@
| INT-030 | 真实 solver 冒烟 | (可选,`@pytest.mark.real_captcha`)真实 jsdom 子进程求解一次 | 输出 verifyParam 结构合法(base64(JSON{certifyId,...}));默认 CI 跳过 |
| INT-031 | solver 缺失报错 | 未 npm install → 明确报错指引用,而非悬死 | 用户体验 |

### INT-E Responses 协议(/v1/responses,用例实现 `tests/integration/test_gateway_responses.py`)

| ID | 场景 | 步骤 | 断言 |
|----|------|------|------|
| INT-040 | 非流式翻译 | `/v1/responses` 非流式一条(scenario=ok) | 200 且 Responses 对象形状完整(output 数组 / status / usage),文本与 /v1/messages 同源一致 |
| INT-041 | 流式事件序 | stream=true → Responses SSE | 帧序符合事件序(created → in_progress → … → completed),sequence_number 连续,无 `[DONE]` 哨兵 |
| INT-042 | previous_response_id 链 | 首请求取 `response_id` → 二次请求携带 → 200 | 第二轮上游请求含首轮上下文;response_store 命中 |
| INT-043 | previous_response_id 未命中 | 携带不存在的 id | 400 invalid_request(api-spec §1.6 错误表) |
| INT-044 | 503 全池耗尽 | 全部账号 402(`x-mock-sequence`)→ 请求 /v1/responses | 503 no_available_account,错误格式同 /v1/messages |
| INT-045 | 401/403 网关鉴权 | 配置网关 key 后缺 key / 错 key | 401/403,行为同 /v1/messages |

## 2. 故障注入矩阵(Mock 上游 `x-mock-scenario`)

| scenario | 行为 | 主要消费者 |
Expand Down Expand Up @@ -67,7 +78,7 @@

| ID | 场景 | 步骤 | 断言 |
|----|------|------|------|
| E2E-001 | 三客户端协议 | Claude Code 形态(Anthropic SSE)、OpenAI SDK 形态(chat/completions,Phase 2)、Codex 形态(/v1/responses,Phase 2)各发 1 流式请求 | 三协议均 200 且内容一致(同一 Mock 模型输出) |
| E2E-001 | 三客户端协议 | Claude Code 形态(Anthropic SSE)、OpenAI SDK 形态(chat/completions)、Codex 形态(/v1/responses,已落地,见 `tests/integration/test_gateway_responses.py`)各发 1 流式请求 | 三协议均 200 且内容一致(同一 Mock 模型输出) |
| E2E-002 | 额度用尽自动降级 | 种子把号1/号2 置 exhausted → 请求自动走号3 → Mock 记录命中 | 故障转移跨进程 |
| E2E-003 | 后台操作流 | UI 登录 → 加号 → 看额度面板 → 手动 claim → 导出 .zsb → 清库导入 | 管理面闭环 |
| E2E-004 | 容器重建恢复 | compose down → up(卷保留)→ 池/设置/历史完好 | 持久化 |
Expand Down
13 changes: 10 additions & 3 deletions frontend/admin/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</div>
<div class="settings-row">
<label>网关 API Key(可选)</label>
<div class="hint">设置后,调用 <code>/v1/messages</code> 须携带 <code>Authorization: Bearer &lt;key&gt;</code> 或 <code>x-api-key</code>。清空并保存可关闭校验(生产必须配置)。输入框显示掩码,未改动保存不会覆盖原值。</div>
<div class="hint">设置后,调用 <code>/v1/messages</code>、<code>/v1/chat/completions</code> 或 <code>/v1/responses</code> 须携带 <code>Authorization: Bearer &lt;key&gt;</code> 或 <code>x-api-key</code>。清空并保存可关闭校验(生产必须配置)。输入框显示掩码,未改动保存不会覆盖原值。</div>
<input class="input" id="gateway-key" type="password" placeholder="留空表示不启用" autocomplete="new-password">
</div>
<div class="settings-row">
Expand Down Expand Up @@ -57,7 +57,10 @@
· 在「账号池」粘贴 Coding Plan JWT 或 API Key 即可加入轮询。<br>
· 请求按 round-robin 分发;某账号额度用完会自动切到下一个账号。<br>
· 账号额度、状态在「账号池」页实时刷新展示。<br>
· 对话端点:<code id="endpoint">/v1/messages</code>(兼容 Anthropic Messages 协议)。
· 对话端点(Anthropic 协议):<code id="endpoint-anthropic">/v1/messages</code><br>
· 对话端点(OpenAI 协议):<code id="endpoint-openai">/v1/chat/completions</code><br>
· 对话端点(OpenAI Responses 协议):<code id="endpoint-responses">/v1/responses</code>;Codex CLI 可原生直连。<br>
· 模型列表:<code id="endpoint-models">/v1/models</code>
</div>
</div>
</main>
Expand All @@ -70,7 +73,11 @@
await renderAdminHeader();
const key=await adminKey.get();
if(!key||!await verifyKey(ADMIN_API+'/verify',key).catch(()=>false))return location.href='/admin/login';
document.getElementById('endpoint').textContent=location.origin+'/v1/messages';
const o=location.origin;
document.getElementById('endpoint-anthropic').textContent=o+'/v1/messages';
document.getElementById('endpoint-openai').textContent=o+'/v1/chat/completions';
document.getElementById('endpoint-models').textContent=o+'/v1/models';
document.getElementById('endpoint-responses').textContent=o+'/v1/responses';
load();
})();

Expand Down
3 changes: 2 additions & 1 deletion frontend/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ select.input option{background:var(--panel);color:var(--ink)}
.dialog-btn-danger:hover{background:rgba(224,82,82,.2)}

/* Settings card */
.settings-card{background:var(--panel);border:1px solid var(--hairline-soft);border-radius:14px;padding:22px;margin-bottom:16px;max-width:640px}
/* Settings card:与 admin-main 同宽(设置页表单/说明需要整行宽度,窄卡已弃用) */
.settings-card{background:var(--panel);border:1px solid var(--hairline-soft);border-radius:14px;padding:22px;margin:0 auto 16px}
.settings-row{display:grid;gap:6px;margin-bottom:18px}
.settings-row:last-child{margin-bottom:0}
.settings-row label{font-size:13px;font-weight:600}
Expand Down
2 changes: 1 addition & 1 deletion frontend/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.8
2.6.10
Loading