# Codex Retry Gateway - 文档层级:app 项目真源 - 应用性质:自有 infra 工具 - 源码来源:git submodule - 运行关系:多实例服务 - 部署模型:managed_systemd_apps - 对应服务:`services/pc/systemd-user/codex-retry-gateway/current/README.md`、`services/a100/systemd-user/codex-retry-gateway/current/README.md` - 对应 stack:无 - Secret 边界:本地用户配置 - 数据来源:本地 Codex provider 配置、gateway profile state 与上游 OpenAI-compatible API 响应。 - 输出边界:仅返回代理后的 Codex API 响应和本地健康/状态信息。 - 写入边界:只写本地用户态 gateway 状态目录。 - 本地状态路径:`~/.codex-retry-gateway/` - 备注:token 与 provider secret 只保留在目标用户配置或 secret 文件中,不进入 repo。 一个不依赖 `cc-switch` 路由模式的独立本地网关。 目标: - 保持 Codex 继续使用现有 `auth.json` - 只把 `config.toml` 的当前 provider `base_url` 改成本地网关 - 非流式命中 `reasoning_tokens = 516` 时返回 `502` - 上游若返回明确的容量错误(默认匹配错误文案 `Selected model is at capacity. Please try a different model.`、`stream disconnected before completion: Concurrency limit exceeded for account, please retry later`,以及 Responses 错误码 `server_is_overloaded` / `slow_down`),也会自动重试;重试耗尽后转成本地 `502` - 除了 `429/503` JSON 错误响应,也会识别 `200` 但返回体本质是错误、以及流式失败事件里携带同样文案的情况 - 流式命中时默认先缓存并判断;一旦命中 `516`,统一返回 `502` - 流式成功响应在严格检查模式下仍会先缓存完成;成功后会按真实且规范化的 Responses 生命周期与输出顺序逐块回放给 Codex,每个 SSE 块之间至少间隔 5ms,并关闭 TCP 小包聚合,避免大量 delta 在同一事件循环突发到达;不会伪造缺少 response ID 的生命周期事件 - 默认同时拦截 root 路径和 `/v1` 路径: - `/responses` - `/chat/completions` - `/v1/responses` - `/v1/chat/completions` 限制: - 这个网关不负责 `Responses` 和 `Chat Completions` 协议互转 - 如果你的上游本身不支持 Codex 当前使用的协议,这个网关不会替你补齐转换能力 ## 默认路径 Windows: - Codex 配置:`%USERPROFILE%\.codex\config.toml` - Gateway 状态目录:`%USERPROFILE%\.codex-retry-gateway` macOS / Linux: - Codex 配置:`~/.codex/config.toml` - Gateway 状态目录:`~/.codex-retry-gateway` ## 当前版本说明 - 这是一个可独立发布、独立运行的仓库 - 默认监听地址是 `http://127.0.0.1:4610` - 默认示例上游见 `config.example.json` - 实际运行时配置会写到当前用户目录下的 gateway 状态目录 ## 一键启动并打开管理页 在仓库根目录执行: Windows: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\launch-ui.ps1 ``` macOS / Linux: ```bash bash ./scripts/launch-ui.sh ``` 这个脚本是默认入口,执行后会自动完成: - 第一次运行时: - 备份当前用户目录下的 Codex `config.toml` - 生成当前用户目录下的 gateway `config.json` - 启动本地 gateway - 把当前 `model_provider` 对应的 `base_url` 改到本地 gateway - 之后再次运行时: - 自动复用现有安装状态 - 自动重启或拉起 gateway - 自动再次打开管理页 默认会打开: ```text http://127.0.0.1:4610/__codex_retry_gateway/ui ``` 如果你只想启动、不自动开浏览器: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\launch-ui.ps1 -NoOpen ``` ```bash bash ./scripts/launch-ui.sh --no-open ``` 常用参数: - Windows 参数: - `-CodexConfigPath` - `-StateRoot` - `-ListenHost` - `-ListenPort` - `-NoOpen` - macOS / Linux 参数: - `--codex-config-path` - `--state-root` - `--listen-host` - `--listen-port` - `--no-open` macOS / Linux 说明: - 需要 `bash` - 需要 `Node.js 18+` - Unix 入口会调用跨平台 `node` 管理核心,不依赖 PowerShell - 推荐显式使用 `bash ...sh` - 这样即使目录是从 Windows 或压缩包复制过来、没有可执行位,也能直接运行 ## 手工安装入口 如果你明确只想做脚本级安装,不想自动打开 UI,也可以直接执行: Windows: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\install-for-current-provider.ps1 ``` macOS / Linux: ```bash bash ./scripts/install-for-current-provider.sh ``` ## Linux systemd + profile 启动 在 Linux 上需要常驻运行时,推荐用 `scripts/run-profile.mjs` 作为 systemd 的 ExecStart。它会读取 profile env,生成运行时 `config.json`,并把当前 Codex provider 的 `base_url` 指向本机 gateway。 文本 profile env 默认放在: ```text ~/.config/codex-retry-gateway/profiles/.env ``` 图片 profile env 独立放在: ```text ~/.config/codex-retry-gateway/image-profiles/.env ``` 当前文本 profile 与当前图片 profile 分别由 state 中的 `profile_name`、`image_profile_name` 记录;切换其中任意一方不会改写另一方。首次升级时,当前文本 profile 中已有的 `CODEX_RETRY_GATEWAY_IMAGE_*` 字段会复制到同名图片 profile,旧文本文件会保留作为兼容回退,但后续 UI/TUI 保存文本 profile 不再写入图片字段。 常用字段: - `CODEX_RETRY_GATEWAY_LISTEN_HOST` - `CODEX_RETRY_GATEWAY_LISTEN_PORT` - `CODEX_RETRY_GATEWAY_UPSTREAM_BASE_URL` - `CODEX_RETRY_GATEWAY_REASONING_EQUALS` - `CODEX_RETRY_GATEWAY_RETRYABLE_STATUS_CODES` - `CODEX_RETRY_GATEWAY_RETRYABLE_ERROR_MESSAGES` - `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE` - `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_ENV` - `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_FILE` - `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_PATH` - `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_JSON_KEY` - `CODEX_RETRY_GATEWAY_REQUEST_HISTORY_LIMIT` `CODEX_RETRY_GATEWAY_UPSTREAM_AUTH_MODE` 支持: - `passthrough`:默认模式,透传 Codex 发来的 `Authorization` - `manual_bearer`:在 UI 中手动填入一次 token/password,写入用户级受限 secret 文件,并覆盖上游 `Authorization: Bearer ...` - `fixed_bearer`:从环境变量或文件读取 token,并覆盖上游 `Authorization: Bearer ...` - `auth_json`:从 Codex `auth.json` 的指定 key 读取 token,并覆盖上游 `Authorization: Bearer ...` 图片 profile 常用字段: - `CODEX_RETRY_GATEWAY_IMAGE_BASE_URL` - `CODEX_RETRY_GATEWAY_IMAGE_AUTH_MODE` - `CODEX_RETRY_GATEWAY_IMAGE_AUTH_ENV` - `CODEX_RETRY_GATEWAY_IMAGE_AUTH_FILE` - `CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_PATH` - `CODEX_RETRY_GATEWAY_IMAGE_AUTH_JSON_KEY` - 配置 `CODEX_RETRY_GATEWAY_IMAGE_BASE_URL` 后,`/images/*` 和 `/v1/images/*` 会改用该地址;root 图片路径会在上游规范化为 `/v1/images/*`,其他请求仍使用 `CODEX_RETRY_GATEWAY_UPSTREAM_BASE_URL`。 - 图片认证字段与普通上游同义,使用 `CODEX_RETRY_GATEWAY_IMAGE_AUTH_*`;默认 `fixed_bearer` 从 `CODEX_RETRY_GATEWAY_IMAGE_API_KEY` 读取 key。 - 不要把 key 直接写进 profile env;优先通过 `IMAGE_AUTH_FILE` 或管理页的 `manual_bearer` 写入用户级受限 secret 文件。 示例: ```bash node ./scripts/run-profile.mjs default ``` 指定图片 profile: ```bash node ./scripts/run-profile.mjs default --image-profile images ``` 如果已经由 state 记录了当前活跃 profile,也可以直接不传 profile 参数: ```bash node ./scripts/run-profile.mjs --prefer-state-profile ``` ## 测试与发布 这个服务会改写目标用户的 Codex `base_url`,属于当前操作链路上的高风险本地网关。后续变更默认按“先对侧宿主、再当前宿主”发布。 推荐顺序: 1. 先部署 `a100` 上的 `codex-retry-gateway`。 2. 在对侧机器的目标用户环境里发起真实 Codex 冒烟请求,不要先改当前正在依赖本机 `4610` 的会话。 3. 确认健康检查、响应完整性与关键重试行为正常。 4. 通过后再部署本机,例如 `pc`。 推荐部署命令: ```bash uv run ansible-playbook playbooks/deploy_managed_systemd_apps.yml -i inventory/hosts.yml --limit a100 -e managed_systemd_apps_selected_names=codex-retry-gateway ``` 验证通过后,再切到本机: ```bash uv run ansible-playbook playbooks/deploy_managed_systemd_apps.yml -i inventory/hosts.yml --limit pc -e managed_systemd_apps_selected_names=codex-retry-gateway ``` 真实 Codex 网关冒烟固定显式使用 `gpt-5.6-terra`,不再使用 `gpt-5.4`: ```bash codex --dangerously-bypass-approvals-and-sandbox -c model="gpt-5.6-terra" hello ``` 建议同时检查: - `curl http://:4610/__codex_retry_gateway/health` - `/responses` 成功流能完整结束,不会卡 pending;`response.created` 必须带真实 response ID,随后依次可见 `response.in_progress`、输出 delta 与 `response.completed` - 容量错误 `Selected model is at capacity. Please try a different model.` 仍能按网关策略自动重试 如果当前对话依赖本机 `4610`,不要把本机作为首个发布目标;先在另一台已接管相同 profile 的机器验证,再回到本机切换。 ## 如何恢复 Windows: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\restore-codex-config.ps1 ``` macOS / Linux: ```bash bash ./scripts/restore-codex-config.sh ``` 这个脚本会: - 停掉本地 gateway - 用最近一次备份恢复当前用户目录下的 Codex `config.toml` - 删除当前安装状态文件 ## 管理页面 页面入口: ```text http://127.0.0.1:4610/__codex_retry_gateway/ui ``` UI 现在是独立的 Vite + React + TypeScript 前端: - 源码:`ui-src/` - 构建产物:`public/ui/` - 本地开发:`npm run dev:ui` - 生产构建:`npm install && npm run build:ui` gateway 运行时只负责 API 与静态文件服务,不再把复杂 UI 硬写在 `gateway.mjs` 字符串里。 页面里可以直接做这几件事: - 看当前监听地址、真实上游、当前 provider、当前 Codex base URL - 看基于持久请求记录累计的实时统计 - 代理请求总数 - 被检查响应总数 - 累计 input / output / total / reasoning tokens - `516` 命中次数 - `516` 占比 - 看最近请求记录 - 请求时间戳、首字耗时、总耗时、请求体大小、路径、模型、状态码 - 相同重发请求会带相同的 `request_id` - `usage` 中的 input / output / total / reasoning tokens - 分别管理文本 profiles 与图片 profiles - 各自新建 / 编辑 / 探测 / 切换 / 删除 profile env - 各自导出 / 导入可移植 profile JSON;导出前必须再次验证该 profile 当前实际使用的 API key - 文本切换 provider `base_url` 不会改写图片分流,图片切换只影响 `/images/*` 与 `/v1/images/*` - 各自切换 `passthrough` / `manual_bearer` / `fixed_bearer` / `auth_json` 认证模式 - 改 `reasoning_equals` - 改 capacity error 的匹配状态码和错误文案 - 改 `endpoints` - 改 `non_stream_status_code` - 开关 `log_match` - 动态查看当前 gateway 的实时日志 - 一键恢复 Codex 原设置 说明: - 页面保存配置后会立即热生效,不需要重启 gateway - 页面点“恢复 Codex 原设置并关闭网关”后,当前页面会失联,这是预期行为 - 日常恢复优先用 UI;`restore-codex-config.ps1` 作为脚本级应急回滚入口保留 - UI 恢复不会再额外拉起恢复子进程,而是由当前 gateway 直接完成恢复并退出 - 统计口径默认按持久请求记录累计,重启后会从 `requests.sqlite` / `requests.jsonl` 回灌 - `516` 占比 = `reasoning_tokens = 516` 的响应次数 / 被检查响应总数 - 请求历史只记录元数据、请求体字节数和 token usage,不保存请求正文或响应正文;默认展示最近 200 条 - gateway 日志持久化到 `~/.codex-retry-gateway/logs/gateway.log` - 请求记录持久化到 `~/.codex-retry-gateway/logs/requests.jsonl` 与 `~/.codex-retry-gateway/logs/requests.sqlite`,重启后会用于 UI 请求页以及 overview 的累计 token / reasoning 统计 - `manual_bearer` 的手动 token/password 只写入系统 secret 文件,普通 profile API/UI 不读回明文;只有再次验证当前 key 的显式导出接口会把它放进下载文件,profile env 仍只保存 secret 文件路径 - 其他 profile env 不保存明文 `sk-...`;固定密钥请使用 env/file 引用,或用 `auth_json` 指向 `auth.json` 字段名 ### Profile 导入与导出 文本与图片 profile 都支持从 Profiles 页面导出和导入: - 文本接口:`POST /__codex_retry_gateway/api/profiles/export`、`POST /__codex_retry_gateway/api/profiles/import` - 图片接口:`POST /__codex_retry_gateway/api/image-profiles/export`、`POST /__codex_retry_gateway/api/image-profiles/import` - 导出前必须再次提交该 profile 当前实际使用的 API key;gateway 会从现有 `manual_bearer` secret 文件、`fixed_bearer` env/file 或 `auth_json` 解析当前 key,并做常量时间比对 - `passthrough` 没有 profile-owned key,无法完成再次验证,因此拒绝导出 - 验证成功后才返回 `codex-retry-gateway-profile` v1 JSON;文件包含明文 API key,文本 profile 若配置了管理 Access key 也会一并保留 - 导出响应使用 `Cache-Control: no-store`,服务端不生成或保留导出副本,日志只记录 profile 名称和认证来源,不记录 key - 导入只接受该格式的 JSON,并把包内 key 写入目标用户的默认 secret 文件,目录权限为 `0700`、文件权限为 `0600`;profile env 仍只保存 secret 文件路径 - 为避免当前控制链路被意外改写,导入不会自动切换 profile,也不允许直接覆盖当前活跃 profile;同名非活跃 profile 需要显式确认覆盖 - 导入后的认证统一落为可移植的 `manual_bearer`;先用 profile 探针验证,再手动热切换 导出文件本身就是明文 secret 载体,不应提交到 Git、上传到普通日志或长期放在共享下载目录。 ## 如何调整拦截条件 编辑: ```text Windows: %USERPROFILE%\.codex-retry-gateway\config\config.json macOS / Linux: ~/.codex-retry-gateway/config/config.json ``` 常用字段: - `reasoning_equals` - 例如 `[516]` - `retryable_status_codes` - 默认 `[429, 503]` - `retryable_error_messages` - 默认包含 `Selected model is at capacity. Please try a different model.` - 也默认包含 `stream disconnected before completion: Concurrency limit exceeded for account, please retry later` - 只要上游 JSON 错误里包含这些文案之一,gateway 就会把上游错误翻成本地 `non_stream_status_code` - `endpoints` - 默认包含 root 与 `/v1` 两套路径 - `non_stream_status_code` - 默认 `502` - `stream_action` - 默认 `strict_502` - `strict_502`:先缓存整个流,命中 `516` 时统一返回 `502` - `disconnect`:兼容旧行为;若命中发生在已透传 chunk 之后,则直接断开连接 - `log_match` - 是否记录命中日志 改完后重启: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\start-gateway.ps1 -RestartIfRunning ``` ```bash bash ./scripts/start-gateway.sh --restart-if-running ``` 如果你已经打开管理页,优先直接在页面里改,通常不需要手改 `config.json`。 ## 其他机器如何应用 在其他 Windows 机器上: 1. 复制整个仓库目录 2. 确保本机有 `Node.js 18+` 3. 不需要安装 `cc-switch`,也不需要使用 `cc-switch` 路由模式 4. 在仓库根目录执行 `powershell -ExecutionPolicy Bypass -File .\scripts\launch-ui.ps1` 5. 如需回滚,优先在 UI 里点“恢复 Codex 原设置并关闭网关”;脚本级回滚仍可执行 `powershell -ExecutionPolicy Bypass -File .\scripts\restore-codex-config.ps1` 在其他 macOS / Linux 机器上: 1. 复制整个仓库目录 2. 确保本机有 `bash` 3. 确保本机有 `Node.js 18+` 4. 不需要安装 `cc-switch`,也不需要使用 `cc-switch` 路由模式 5. 在仓库根目录执行 `bash ./scripts/launch-ui.sh` 6. 如需回滚,优先在 UI 里点“恢复 Codex 原设置并关闭网关”;脚本级回滚仍可执行 `bash ./scripts/restore-codex-config.sh` 运行时状态默认写到当前用户目录: ```text Windows: %USERPROFILE%\.codex-retry-gateway macOS / Linux: ~/.codex-retry-gateway ``` ## 已验证事项 - `test-gateway-e2e.ps1` - 已通过 - 验证 `/responses`、`/chat/completions`、`/v1/responses`、`/v1/chat/completions` - `node scripts/test-gateway-e2e.mjs` - 已通过 - 验证 strict capture 下 `/responses` 生命周期不注入空事件,长流 delta 按序完整回放并以 `response.completed` 结束 - 验证文本 / 图片 profile 导出必须重新校验 key,错误 key 不泄露 secret;导入后 key 只进入 `0600` secret 文件,不进入 profile env 或普通 API 响应 - `test-install-restore.ps1` - 已通过 - 验证安装、透传、UI 页面、热更新配置、实时日志、516 统计、恢复闭环 - `test-launch-ui.ps1` - 已通过 - 验证首次一键启动自动安装、再次启动自动复用、UI 可访问、默认 516 拦截仍生效 - `test-launch-ui-unix.ps1` - 已通过 - 在当前 Windows 主机的 Bash 环境里验证 Unix `.sh` 入口能完成启动、透传、恢复闭环 - `bash ./scripts/launch-ui.sh --no-open` - 已通过 - 当前机器实测返回 `mode=reuse` - 后续 `GET /__codex_retry_gateway/health`、`GET /__codex_retry_gateway/ui`、`GET /v1/models` 都返回 `200` - `codex exec` - 已通过 - 在 Bash 默认入口重新拉起 gateway 后,当前机器再次返回 `OK` - 当前实机验证示例 - `GET http://127.0.0.1:4610/__codex_retry_gateway/health` 已通过 - `GET http://127.0.0.1:4610/v1/models` 已通过,并成功透传到配置里的真实上游 - `GET http://127.0.0.1:4610/__codex_retry_gateway/ui` 已实际打开并确认页面内容 - `codex exec` 历史现象 - gateway 关闭时,真实报错地址为 `http://127.0.0.1:4610/responses` - gateway 恢复后,`codex exec` 已再次成功返回 `OK`