# Zhanlu Proxy 一个本地 Go 代理服务,用于读取湛卢(v1.4.2)插件凭据,按插件认证规则换取模型 API Key,并暴露 OpenAI 兼容接口。 当前实现包含: - 登录页支持插件默认的移动云手机号验证码登录:验证码校验后按插件流程调用 `/api/acepilot/zhanlu/v1/login` 获取用户资料,再通过 SM2 签名调用 `/user/api/v2/external/key/get-or-create` 换取模型 API Key,凭据自动保存到本地 JSON。 - OpenAI 兼容接口:`/v1/models`(优先从 `/gateway/v1/model/info` 拉取模型列表)、`/v1/chat/completions`(携带 `Authorization: Bearer ` 请求 `{modelBaseUrl}/chat/completions`)、`/v1/responses`(Responses API,自动转换请求/响应格式,兼容新版 SDK 与 AI 编码工具)。 - 湛卢登录签名逻辑:RSA `authorization`、SHA-256 query hash、HMAC-SHA1 `Signature`,用于 v1.4.2 的 v1/login 认证。 - 模型 API Key 换取签名逻辑:SM3 摘要 + SM2 签名(`X-Auth-Signature`/`X-Auth-Timestamp`/`X-Auth-Nonce`)。 - 上游 SSE 直接透传为 OpenAI SSE;非流式请求在本地聚合为 OpenAI Chat Completion JSON。 - OpenAI 函数/工具调用:支持 `tools`、`tool_choice`、流式 `delta.tool_calls`、非流式 `message.tool_calls` 以及 `role: tool` 结果续传。 - Token 消耗统计:流式与非流式请求均解析上游 `usage`,按模型/按日/最近明细写入本地 SQLite(`zhanlu.db`),凭据也一并持久化在同一个库中。管理页提供 `GET /admin/stats` 可视化与 `GET /api/stats` JSON 接口。 - 可用模型:管理后台新增「可用模型」tab,实时拉取上游 `/gateway/v1/model/info` 返回的模型列表,并提供单模型可用性及首字延时(TTFT)探测(`GET /api/models`、`POST /api/models/test`)。 ## 运行 ```powershell go run ./cmd/zhanlu-proxy ``` 默认监听: ```text http://127.0.0.1:8080 ``` 打开首页会自动跳转到管理登录页: ```text http://127.0.0.1:8080/ ``` ## systemd 服务示例 假设二进制文件放在: ```text /opt/zhanlu-proxy/zhanlu-proxy ``` 数据库放在: ```text /opt/zhanlu-proxy/zhanlu.db ``` 创建环境变量文件 `/etc/zhanlu-proxy/zhanlu-proxy.env`: ```env ZHANLU_LISTEN_ADDR=:8080 ZHANLU_DB_FILE=/opt/zhanlu-proxy/zhanlu.db ZHANLU_MOBILE_LOGIN_BASE_URL=https://ecloud.10086.cn ZHANLU_MOBILE_MODEL_BASE_URL=https://ecloud.10086.cn/api/query/aigateway ZHANLU_UPSTREAM_TIMEOUT=300s ZHANLU_LOGIN_PASSWORD=change-this-login-password OPENAI_COMPAT_API_KEY=change-this-local-secret ``` 创建 systemd service 文件 `/etc/systemd/system/zhanlu-proxy.service`: ```ini [Unit] Description=Zhanlu OpenAI-compatible proxy After=network-online.target Wants=network-online.target [Service] Type=simple WorkingDirectory=/opt/zhanlu-proxy EnvironmentFile=/etc/zhanlu-proxy/zhanlu-proxy.env ExecStart=/opt/zhanlu-proxy/zhanlu-proxy Restart=always RestartSec=3 User=zhanlu-proxy Group=zhanlu-proxy [Install] WantedBy=multi-user.target ``` 启用并启动服务: ```bash sudo systemctl daemon-reload sudo systemctl enable --now zhanlu-proxy sudo systemctl status zhanlu-proxy ``` 查看日志: ```bash journalctl -u zhanlu-proxy -f ``` ## 登录与凭据 ### 移动云手机号验证码登录 打开 `/login` 后输入手机号并点击“获取验证码”。实现按插件默认登录分支工作: 如果设置了 `ZHANLU_LOGIN_PASSWORD`,`/login` 只负责管理密码登录。密码正确后服务会设置 HttpOnly 会话 Cookie,并跳转到 `/admin/login`。`/admin/login` 才是手机号验证码登录湛卢的页面,之后才能查看凭据状态、获取短信验证码、保存凭据或使用备用 SSO 管理接口。 - 生成 16 位一次性 `secret`。 - 使用插件内置 RSA 公钥加密手机号和 `secret`。 - 调用公网接口 `/api/query/acepilot-h5/manager/code/getAuthCode` 发送验证码。 - 输入验证码后调用 `/api/query/acepilot-h5/manager/code/checkCode`。 - 使用本次 `secret` AES 解密响应中的 `ak`、`sk`、`license`,得到 `AccessKey`、`SecretKey`、`Token`。 - 按插件 v1.4.2 流程调用 `/api/acepilot/zhanlu/v1/login`(RSA+HmacSHA1 签名 URL + `plugin_type=zhanlu_ide` 请求头)获取用户资料(email/组织/团队)。 - 用 SM2 私钥签名调用 `{mobileModelBaseUrl}/user/api/v2/external/key/get-or-create` 换取模型 `apiKey`。 - 凭据(含 `apiKey`、`modelBaseUrl`、email 等)会写入本地 SQLite 数据库(`zhanlu.db`),后续 OpenAI 兼容接口自动使用。 默认数据库保存在当前执行目录: ```text zhanlu.db ``` 可以通过环境变量覆盖: ```powershell $env:ZHANLU_DB_FILE="E:\path\to\zhanlu.db" ``` 凭据仅持久化在数据库中,不再使用 JSON 文件。 手机号验证码登录使用 `ZHANLU_MOBILE_LOGIN_BASE_URL`,默认公网地址来自插件配置(兼容旧环境变量 `ZHANLU_SERVER_BASE_URL`): ```powershell $env:ZHANLU_MOBILE_LOGIN_BASE_URL="https://ecloud.10086.cn" $env:ZHANLU_MOBILE_MODEL_BASE_URL="https://ecloud.10086.cn/api/query/aigateway" ``` 灵犀(内网)SSO 的 `/auth/start` 和 `/auth/callback` 仍保留为备用接口,但不是 `/login` 的默认主流程。 ## OpenAI 兼容接口 ### 健康检查 ```powershell curl http://127.0.0.1:8080/healthz ``` ### 模型列表 ```powershell curl http://127.0.0.1:8080/v1/models ``` ### Chat Completions 非流式: ```powershell curl http://127.0.0.1:8080/v1/chat/completions ` -H "Content-Type: application/json" ` -d '{"model":"zhanlu/auto","messages":[{"role":"user","content":"hello"}],"stream":false}' ``` 流式: ```powershell curl -N http://127.0.0.1:8080/v1/chat/completions ` -H "Content-Type: application/json" ` -d '{"model":"zhanlu/auto","messages":[{"role":"user","content":"hello"}],"stream":true}' ``` ### 工具调用 请求中的 `tools`、`tool_choice` 会传给湛卢模型。流式响应返回增量 `delta.tool_calls`;非流式响应会把分片聚合为完整的 `message.tool_calls`,并保留 `finish_reason: "tool_calls"`。执行工具后,将 assistant 的 `tool_calls` 和 `role: "tool"` 结果放回 `messages` 再发起请求即可得到最终回答。 ### Responses API 代理还兼容 OpenAI Responses API(`POST /v1/responses`),支持使用该 API 的客户端(如新版 OpenAI SDK、Cursor 等 AI 编码工具)直接对接。代理会将 Responses API 请求格式转换为上游 chat/completions 格式,再将响应转回 Responses 格式。 请求中的 `input`(字符串或消息数组)、`instructions`(系统提示词)、`max_output_tokens`、`temperature`、`top_p`、`tools`、`tool_choice`、`response_format` 等参数均会翻译并透传给上游。`tools` 格式自动从 Responses 扁平结构转换为 Chat Completions 嵌套 `{function:{…}}` 结构。多轮对话中的 `function_call` 和 `function_call_output` 输入项也会自动转换为对应的 assistant `tool_calls` 和 `role: tool` 消息。 非流式: ```powershell curl http://127.0.0.1:8080/v1/responses ` -H "Content-Type: application/json" ` -d '{"model":"zhanlu/auto","input":"hello","stream":false}' ``` 流式: ```powershell curl -N http://127.0.0.1:8080/v1/responses ` -H "Content-Type: application/json" ` -d '{"model":"zhanlu/auto","input":"hello","stream":true}' ``` 流式响应返回标准 Responses API SSE 事件序列(`response.created` → `response.output_item.added` → `response.output_text.delta` → `response.output_text.done` → `response.output_item.done` → `response.completed`),思考模型额外包含 `response.reasoning_summary_text.delta` 事件,工具调用包含 `response.function_call_arguments.delta` 事件。Token 统计同样记录在 SQLite 中。 如果设置了本地 OpenAI 兼容 API Key,需要带 `Authorization`: ```powershell $env:OPENAI_COMPAT_API_KEY="local-secret" ``` ```powershell curl http://127.0.0.1:8080/v1/models ` -H "Authorization: Bearer local-secret" ``` ## 配置项 | 环境变量 | 默认值 | 说明 | | --- | --- | --- | | `ZHANLU_LISTEN_ADDR` | `:8080` | 本地监听地址 | | `ZHANLU_MOBILE_LOGIN_BASE_URL` | `https://ecloud.10086.cn` | 移动云公网登录 Base URL(兼容旧变量 `ZHANLU_SERVER_BASE_URL`) | | `ZHANLU_MOBILE_MODEL_BASE_URL` | `https://ecloud.10086.cn/api/query/aigateway` | 移动云公网模型网关 Base URL | | `ZHANLU_UPSTREAM_PATH` | `/chat/completions` | 模型网关聊天接口路径 | | `ZHANLU_DB_FILE` | `zhanlu.db` | 本地 SQLite 数据库路径,凭据与 token 统计均存于此,默认当前执行目录 | | `ZHANLU_STATS_DISABLED` | `false` | 设为 `true` 关闭 token 用量记录(仅停止写入统计,凭据存储不受影响) | | `ZHANLU_ACCESS_KEY` | 空 | 直接从环境变量提供 AccessKey | | `ZHANLU_SECRET_KEY` | 空 | 直接从环境变量提供 SecretKey | | `ZHANLU_TOKEN` | 空 | 直接从环境变量提供 Token | | `ZHANLU_API_KEY` | 空 | 直接从环境变量提供已换取的模型 API Key | | `ZHANLU_SSO_BASE_URL` | `http://4c.hq.cmcc` | 灵犀内网 SSO 备用页面 Base URL,非默认手机号登录流程 | | `ZHANLU_SSO_EXCHANGE_URL` | `http://rdcloud.4c.hq.cmcc/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/authToken` | 灵犀 SSO 换取用户资料的备用接口 | | `ZHANLU_TOKEN_DECRYPT_KEY` | `3jw7woww2rvhla6k` | 解密 SSO 返回用户资料字段的 AES key(插件内置默认值) | | `ZHANLU_PUBLIC_KEY_PEM` | 插件内置签名公钥 | 签名 URL 中 `authorization` 使用的 RSA 公钥,通常不需要设置 | | `ZHANLU_PHONE_PUBLIC_KEY_PEM` | 插件内置手机号登录公钥 | 手机号验证码登录加密手机号和一次性 secret 使用的 RSA 公钥,通常不需要设置 | | `ZHANLU_APIKEY_AUTH_SM2_PRIVATE_KEY` | 插件内置 SM2 私钥 | 换取模型 API Key 时 `X-Auth-Signature` 使用的 SM2 私钥,通常不需要设置 | | `ZHANLU_PLUGIN_VERSION` | `1.4.2` | 请求头 `plugin_version` | | `ZHANLU_UPSTREAM_TIMEOUT` | `300s` | 上游请求超时 | | `ZHANLU_STREAM_IDLE_TIMEOUT` | `300s` | 预留的流式空闲超时配置 | | `ZHANLU_LOGIN_PASSWORD` | 空 | `/login` 管理页面密码;设置后登录成功跳转到 `/admin/login` 管理湛卢凭据 | | `ZHANLU_DEBUG` | `false` | 调试模式,错误信息更详细但会脱敏敏感 query | | `OPENAI_COMPAT_API_KEY` | 空 | 本地 OpenAI 兼容接口鉴权 key | ## 凭据优先级 服务启动时按以下优先级加载凭据: 1. 环境变量 `ZHANLU_ACCESS_KEY`、`ZHANLU_SECRET_KEY`、`ZHANLU_TOKEN` 或 `ZHANLU_API_KEY`。 2. `ZHANLU_DB_FILE` 数据库中持久化的凭据行。 登录页面保存后,运行中的服务会立即使用新凭据并写入数据库。若环境中只有 AK/SK/Token 而没有 `apiKey`,首次调用聊天接口时会自动按插件流程换取 API Key 并回写数据库。 ## Token 消耗统计 代理在 `/v1/chat/completions` 完成后解析上游 `usage`(流式路径在透传 SSE 的同时旁路解析末块 `usage`,非流式路径在聚合时解析),将以下维度写入 `ZHANLU_DB_FILE`: - 时间、模型、流式/非流式 - `prompt_tokens` / `completion_tokens` / `total_tokens` / `reasoning_tokens`(思考模型) - `cached_tokens`(来自 `prompt_tokens_details.cached_tokens`,提示缓存命中的 token 数)与缓存命中率 - 请求状态(`success` / `upstream_error`)与耗时 缓存命中率为 `cached_tokens / prompt_tokens`,仅在模型/网关支持 prompt 缓存且上游返回 `cached_tokens` 时非零。 管理页(需先登录管理页面)提供: - `GET /admin/stats`:可视化页面,展示总览、按模型、按日柱状、最近请求明细,带重置按钮。 - `GET /api/stats`:JSON 接口,支持 `since`/`until`(RFC3339)、`model`、`limit` 查询参数。 - `POST /api/stats/reset`:清空统计(凭据不受影响)。 设置 `ZHANLU_STATS_DISABLED=true` 可停止写入统计。 ## 可用模型 管理后台「可用模型」tab(位于「Token 统计」之后)实时展示当前上游接口返回的模型列表,并提供单模型可用性与延时探测: - 进入 tab 时自动拉取一次,也可随时点击「刷新」重新获取。 - 每个模型行可单独「测试」,或点击「全部测试」依次探测所有模型。 - 探测向上游 `/chat/completions` 发送一条极简流式请求(`hi`),测量首字延时(TTFT,首个 SSE 数据块到达时间)与总耗时,并在收到首个内容块后立即关闭连接,避免消耗额外 token。 - 探测请求不计入 Token 统计。 对应 JSON 接口(需先登录管理页面): - `GET /api/models`:实时返回上游模型列表 `{"ok":true,"models":["GLM-4.7",...]}`。 - `POST /api/models/test`:请求体 `{"model":""}`,返回 `{"ok":true,"model":"...","available":true,"ttft_ms":123,"total_ms":456}` 或 `{"ok":true,"model":"...","available":false,"error":"...","total_ms":789}`。 探测超时上限为 30 秒(`modelTestTimeout`);凭据未配置或 API Key 缺失时 `GET /api/models` 与 `POST /api/models/test` 会按需自动换取 API Key,与聊天接口一致。 ## 安全说明 - `zhanlu.db` 数据库包含明文 `AccessKey`、`SecretKey`、`Token` 和 `apiKey`,请不要提交到仓库。 - 默认保存在当前执行目录的 `zhanlu.db`(建议通过 `ZHANLU_DB_FILE` 指向受保护路径)。 - 建议设置 `ZHANLU_LOGIN_PASSWORD`,避免公网暴露的 `/admin/login` 被直接访问。 - 错误响应默认不会返回签名 URL,避免泄露 `AccessKey`、`authorization`、`Signature`。 - `ZHANLU_DEBUG=true` 时会返回更详细错误,但仍会对敏感 query 参数脱敏。 ## 已知限制 - 灵犀内网 SSO(`/auth/start`、`/auth/callback`)为备用接口,主流程是移动云手机号验证码登录。 - 手机号验证码接口可能有风控或频率限制;请按正常登录频率使用。 - 模型网关对 OpenAI `stream:false` 请求由代理负责聚合流式响应。 ## 验证 ```powershell go test ./... go build ./cmd/zhanlu-proxy ``` 本地端点验证: ```powershell go run ./cmd/zhanlu-proxy curl http://127.0.0.1:8080/healthz curl http://127.0.0.1:8080/v1/models ```