diff --git a/.gitignore b/.gitignore index 9279c51..b8fdd2a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ serve.pid .sg_* .sg-js/ .sg_login.html +.zcode/ diff --git a/README.md b/README.md index 70f74d0..cc82242 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 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 免密登录入池,以及免浏览器的阿里云无痕验证求解——开箱即用,自托管部署。 @@ -9,7 +9,7 @@ ## 核心能力 -- **双协议兼容网关**:原生支持 `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` 等网络中间件头部,杜绝部署网络拓扑泄露与客户端伪装冲突。 @@ -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` @@ -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 ` 或 `x-api-key: `;留空则不校验。 ## 环境变量 diff --git a/docs/development/04-api-spec.md b/docs/development/04-api-spec.md index b06b23a..00ca22a 100644 --- a/docs/development/04-api-spec.md +++ b/docs/development/04-api-spec.md @@ -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`,未配置即放行——生产必须配置)。 @@ -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 @@ -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 | 调度层未捕获异常(监控条目会收口) | @@ -117,4 +142,14 @@ npx claude curl http://127.0.0.1:3000/v1/chat/completions \ -H "Authorization: Bearer " -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 " -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 " -H "Content-Type: application/json" \ + -d '{"model":"glm-5.3","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"hi"}]}],"stream":true}' ``` diff --git a/docs/development/07-roadmap.md b/docs/development/07-roadmap.md index ec4d745..ccb0836 100644 --- a/docs/development/07-roadmap.md +++ b/docs/development/07-roadmap.md @@ -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 次。 diff --git a/docs/testing/02-unit-tests.md b/docs/testing/02-unit-tests.md index 9959923..008d428 100644 --- a/docs/testing/02-unit-tests.md +++ b/docs/testing/02-unit-tests.md @@ -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_`)。入库由网关路由层完成(集成 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 | diff --git a/docs/testing/04-integration-e2e.md b/docs/testing/04-integration-e2e.md index 6e5c5d0..b100d5c 100644 --- a/docs/testing/04-integration-e2e.md +++ b/docs/testing/04-integration-e2e.md @@ -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 | 行为 | 主要消费者 | @@ -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(卷保留)→ 池/设置/历史完好 | 持久化 | diff --git a/frontend/admin/settings.html b/frontend/admin/settings.html index 8c26ad5..b296969 100644 --- a/frontend/admin/settings.html +++ b/frontend/admin/settings.html @@ -28,7 +28,7 @@
-
设置后,调用 /v1/messages 须携带 Authorization: Bearer <key> 或 x-api-key。清空并保存可关闭校验(生产必须配置)。输入框显示掩码,未改动保存不会覆盖原值。
+
设置后,调用 /v1/messages、/v1/chat/completions 或 /v1/responses 须携带 Authorization: Bearer <key> 或 x-api-key。清空并保存可关闭校验(生产必须配置)。输入框显示掩码,未改动保存不会覆盖原值。
@@ -57,7 +57,10 @@ · 在「账号池」粘贴 Coding Plan JWT 或 API Key 即可加入轮询。
· 请求按 round-robin 分发;某账号额度用完会自动切到下一个账号。
· 账号额度、状态在「账号池」页实时刷新展示。
- · 对话端点:/v1/messages(兼容 Anthropic Messages 协议)。 + · 对话端点(Anthropic 协议):/v1/messages
+ · 对话端点(OpenAI 协议):/v1/chat/completions
+ · 对话端点(OpenAI Responses 协议):/v1/responses;Codex CLI 可原生直连。
+ · 模型列表:/v1/models
@@ -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(); })(); diff --git a/frontend/css/app.css b/frontend/css/app.css index 6e6d14a..d1ef7a9 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -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} diff --git a/frontend/version b/frontend/version index 4484c2b..a04abec 100644 --- a/frontend/version +++ b/frontend/version @@ -1 +1 @@ -2.6.8 \ No newline at end of file +2.6.10