feat(settings): enforce minimum client versions

Add configurable minimum versions for Codex Desktop and CLI, return 426 for unsupported recognized clients, and provide install and Windows offline download guidance.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-09-01 13:37:04 +08:00
co-authored by Codex
parent 2640a03219
commit bd2454a5b6
65 changed files with 2779 additions and 87 deletions
+55
View File
@@ -17,6 +17,28 @@ Client Key 通过账号分组限定路由范围:未绑定分组时可使用全
已启用分组成员的并集。分组可以混合 `openai` 与 `xai` 账号;同一请求只会在模型能力明确匹配且满足
重放安全边界时跨 Provider fallback。
运行设置可以分别配置 `minCodexDesktopVersion` 与 `minCodexCliVersion`。两者只接受 SemVer,`null`
表示不限制。API 在 Client Key 鉴权成功后识别官方 Desktop/CLI 请求头;已识别客户端没有合法版本,或版本
低于对应门槛时,所有 `/v1/*` HTTP 请求和新 WebSocket 握手在访问上游前返回 `426 Upgrade Required`。
未知客户端保持兼容,不应用版本门禁。
低版本响应使用 OpenAI 风格错误合同:
```json
{
"error": {
"message": "Codex CLI 0.151.0 is below the minimum required version 0.152.0. Upgrade Codex CLI and retry.",
"type": "invalid_request_error",
"code": "client_version_too_old",
"client": "codex_cli",
"current_version": "0.151.0",
"min_version": "0.152.0"
}
}
```
已识别但缺失或携带非法版本时,`code` 为 `client_version_unavailable`,`current_version` 为 `null`。
### 管理接口
除登录、会话状态和登出外,所有 `/api/admin/*` 请求都需要以下任一鉴权方式:
@@ -375,6 +397,7 @@ PostgreSQL 或 Redis。管理端只在用户打开弹窗或点击刷新时调用
| --- | --- | --- |
| `GET` | `/api/admin/settings` | 读取运行设置 |
| `POST` | `/api/admin/settings/update` | 原子替换全部运行设置 |
| `GET` | `/api/admin/settings/client-downloads/codex-desktop/windows` | 提取 Codex Desktop Windows 离线安装直链;`refresh=true` 强制刷新进程内短缓存 |
| `GET` | `/api/admin/settings/admin-api-key` | 只返回管理 API Key 是否存在 |
| `POST` | `/api/admin/settings/admin-api-key/delete` | 删除管理 API Key |
| `POST` | `/api/admin/settings/admin-api-key/regenerate` | 重新生成并一次性返回完整管理 API Key |
@@ -388,12 +411,44 @@ refreshConcurrency
maxConcurrentPerAccount
requestIntervalMs
rotationStrategy
minCodexDesktopVersion
minCodexCliVersion
usageRetentionDays
opsEventRetentionDays
auditRetentionDays
```
`rotationStrategy` 可取 `smart`、`quota_reset_priority`、`round_robin`、`sticky`。
两个 `minCodex*Version` 字段为 `string | null`,只设置最低版本,不存在最大版本字段。
Windows 离线包接口固定解析 Microsoft Store Product ID `9PLM9XGG6VKS` 的 Retail 包,不接受调用方提供
产品 ID、上游地址、ring 或文件名。后端只返回通过包名、架构、Microsoft CDN host/path、scheme 和失效
时间校验的 `x64` / `arm64` MSIX 直链,不代理安装包字节。Store 内容通道返回 HTTP/80 临时地址时保留
原始 scheme,不强制改写为该 host 不保证支持的 HTTPS。动态链接不足 10 分钟即失效时不会下发;某个架构
解析失败时只将该架构降级到 OpenAI 官方 HTTPS 稳定 MSIX,并通过 `warning` 说明。响应形状为:
```json
{
"resolvedAt": "2026-09-01T06:30:00Z",
"cached": false,
"warning": null,
"packages": [
{
"architecture": "x64",
"source": "microsoft_store",
"version": "26.825.6671.0",
"fileName": "OpenAI.Codex_26.825.6671.0_x64__2p2nqsd0c76g0.msix",
"sizeBytes": 744250000,
"downloadUrl": "http://dl.delivery.mp.microsoft.com/filestreamingservice/files/...",
"expiresAt": "2026-09-01T07:30:00Z"
}
]
}
```
`source` 为 `microsoft_store` 或 `official_openai`。Store 的四段 package version 只用于下载展示,不参与
Desktop 三段 SemVer 门禁,也不会自动回写最低版本设置。完整安全与回退设计见
[客户端最低版本与下载方案](client-min-version-plan.md)。
## 9. 备份
+13 -1
View File
@@ -43,6 +43,8 @@ flowchart LR
Store --> Object[(S3 / R2)]
Host[gateway-host] -. lifecycle / workers / update .-> API
Host -. lazy client distribution resolver .-> Admin
Host --> StoreLinks[RG-Adguard / Microsoft CDN]
```
`backend/apps/gateway` 是唯一组合根,按 Host → Store → Provider → Core → Admin → API → Worker 的顺序
@@ -95,9 +97,14 @@ sequenceDiagram
E->>S: enqueue terminal observation and accounting
```
请求开始时冻结 `RuntimeSnapshot`、Client Key 的账号范围、模型映射、Provider 候选顺序和调度策略。
请求开始时冻结 `RuntimeSnapshot`、Client Key 的账号范围、模型映射、Codex 客户端最低版本、Provider
候选顺序和调度策略。
运行中的请求不再拼接新旧配置,也不在热路径查询分组关系。
Client Key 鉴权完成后,API adapter 从有界请求头识别 Codex Desktop/CLI,Core 使用同一请求冻结的
`RuntimeSnapshot` 比较对应最低版本。Desktop 优先于其 User-Agent 内嵌的 CLI/Core 标记;未知客户端不
应用门禁。低版本或已识别但版本不可用时,在进入 Provider 前返回稳定的 `426` 合同。
核心不变量:
- 一个客户端请求对应一条 `model_requests`;attempt 是请求内事实,不建立第二张权威表。
@@ -190,6 +197,7 @@ PostgreSQL 周期对账才是正确性基础。
| 日志、OAuth 恢复记录、在线更新状态、备份暂存 | `.runtime/` | 部署节点本地运行文件 |
| 重置卡库存与消费结果 | OpenAI upstream | 后端不建立本地卡库存;前端只保留当前浏览器会话的最近查询 |
| Provider 公开模型与请求画像 | Provider/runtime cache | 由官方目录或发布源刷新,不写成第二份业务配置 |
| Windows 安装包临时直链 | Host 进程内短缓存 | 按需解析、严格校验、到期前丢弃;不写 PostgreSQL/Redis,也不代理包字节 |
账号对外状态不是独立列,而是 PostgreSQL credential/quota 事实与 Redis cooldown 的统一投影:
`normal`、`quota_exhausted`、`rate_limited`、`disabled`、`error`。只有明确上游证据才能恢复或终态化账号,
@@ -237,6 +245,10 @@ Worker 由各 Bundle 贡献、由 Host 统一监督:
启动只有在配置、PostgreSQL、Redis、Provider、Core、Admin、API 和 Worker 全部初始化成功后才进入服务。
健康检查综合 Core、Store 与 Worker 状态,但不会把单个 Provider 的业务降级等同于整个进程失活。
组合根在启动时只把 Host 的 `ClientDistributionResolver` 能力注入 Admin;此时不访问 RG-Adguard,下载
HTTP Client 构造失败也不会阻断网关启动。外部解析在已认证管理员首次打开客户端下载弹窗时惰性执行,
失败时按架构使用 OpenAI 官方稳定地址。Microsoft Store 内容通道返回的 HTTP/80 临时链接保留原始 scheme,
不会被错误改写到该 host 不保证支持的 HTTPS 虚拟主机。
关闭分为两段:先停止接收新连接并 drain HTTP/WS,再取消并等待 Worker;两段各有独立预算,Compose 的
`stop_grace_period` 必须覆盖二者之和。超时后只丢弃仍未落盘的可恢复观测,不执行隐式业务重放。