mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
上游 /backend-api/codex 按 Originator 头分桶调度容量:落在降载桶的请求即使返回 HTTP 200,也会立刻推 SSE `event: error`(code=server_is_overloaded)并以 response.failed 收尾。2026-07-29 起 codex-tui 落入降载桶,codex_cli_rs 正常—— 判定因子是 originator 而非 User-Agent(codex_cli_rs 配 curl UA 亦可正常返回)。 网关会把该错误判定为瞬时上游故障并冷却账号,对外表现为 Codex 账号频繁过载不可用: server_is_overloaded → isOpenAITransientProcessingError → shouldCooldownOpenAITransientUpstreamError → 账号冷却 → 客户端 503。 本项目有三处降载身份来源:浏览器 UA 兜底的默认 UA、客户端透传的真实 TUI 身份、 以及指纹缓存注入探针的 UA。 修复收口在 enforceCodexIdentityHeaders——HTTP / 透传 / WS 握手 / compat 桥接 / 探针 / PAT / 模型列表 / alpha-search 八条出站路径共用的唯一纯函数收口点: - 新增 NormalizeCodexClientIdentityToCLI,把降载桶身份改写为 codex_cli_rs, 只替换身份段并裁掉尾部 (name; version) 客户端标识组,保留版本 / OS / 架构 / 终端指纹;改写后 originator 与 UA 首段仍然配套,不破坏 #3901 的配对不变式, 且改写幂等。 - DefaultOpenAICodexUserAgent 从 TUI 身份改为 CLI 身份(浏览器兜底路径上最大的 降载身份来源)。 - 管理端 Codex UA 的 placeholder / hint 原本在把管理员往降载桶引导,一并修正。 新增 gateway.disable_codex_originator_normalization(默认 false,即归一化开启), 供上游调整分桶后回滚。该开关经 NewOpenAIGatewayService 发布为进程级快照,故必须 保持反义命名:正向命名的 Go 零值 false 会让未经 viper 加载而手工构造的 Config 静默关掉全局保护,viper.SetDefault 救不了这条路径。已加用例钉住该属性。 降载桶集合是上游容量策略快照而非协议常量,上游调整分桶后需同步修订。
107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
package openai
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIsCodexLoadShedOriginator(t *testing.T) {
|
|
require.True(t, IsCodexLoadShedOriginator("codex-tui"))
|
|
require.True(t, IsCodexLoadShedOriginator(" CODEX-TUI "))
|
|
require.False(t, IsCodexLoadShedOriginator("codex_cli_rs"))
|
|
require.False(t, IsCodexLoadShedOriginator("codex_vscode"))
|
|
require.False(t, IsCodexLoadShedOriginator("Codex Desktop"))
|
|
require.False(t, IsCodexLoadShedOriginator(""))
|
|
}
|
|
|
|
func TestNormalizeCodexClientIdentityToCLI(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
originator string
|
|
ua string
|
|
wantOriginator string
|
|
wantUA string
|
|
wantChanged bool
|
|
}{
|
|
{
|
|
name: "tui 完整 UA 改写首段并裁掉客户端标识组",
|
|
originator: "codex-tui",
|
|
ua: "codex-tui/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color (codex-tui; 0.144.1)",
|
|
wantOriginator: "codex_cli_rs",
|
|
wantUA: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color",
|
|
wantChanged: true,
|
|
},
|
|
{
|
|
name: "无客户端标识组时仅改写首段",
|
|
originator: "codex-tui",
|
|
ua: "codex-tui/0.144.1 (Mac OS X 14.0; arm64)",
|
|
wantOriginator: "codex_cli_rs",
|
|
wantUA: "codex_cli_rs/0.144.1 (Mac OS X 14.0; arm64)",
|
|
wantChanged: true,
|
|
},
|
|
{
|
|
name: "OS 括号组不是客户端标识不得被裁剪",
|
|
originator: "codex-tui",
|
|
ua: "codex-tui/0.144.1 (Ubuntu 22.4.0; x86_64)",
|
|
wantOriginator: "codex_cli_rs",
|
|
wantUA: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64)",
|
|
wantChanged: true,
|
|
},
|
|
{
|
|
name: "缺少版本段时只替换 originator",
|
|
originator: "codex-tui",
|
|
ua: "codex-tui",
|
|
wantOriginator: "codex_cli_rs",
|
|
wantUA: "codex-tui",
|
|
wantChanged: true,
|
|
},
|
|
{
|
|
name: "健康身份原样返回",
|
|
originator: "codex_cli_rs",
|
|
ua: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color",
|
|
wantOriginator: "codex_cli_rs",
|
|
wantUA: "codex_cli_rs/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color",
|
|
wantChanged: false,
|
|
},
|
|
{
|
|
name: "其他官方身份不受影响",
|
|
originator: "codex_vscode",
|
|
ua: "codex_vscode/1.0.0 (Ubuntu 22.4.0; x86_64) vscode (codex_vscode; 1.0.0)",
|
|
wantOriginator: "codex_vscode",
|
|
wantUA: "codex_vscode/1.0.0 (Ubuntu 22.4.0; x86_64) vscode (codex_vscode; 1.0.0)",
|
|
wantChanged: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotOriginator, gotUA, changed := NormalizeCodexClientIdentityToCLI(tt.originator, tt.ua)
|
|
|
|
require.Equal(t, tt.wantOriginator, gotOriginator)
|
|
require.Equal(t, tt.wantUA, gotUA)
|
|
require.Equal(t, tt.wantChanged, changed)
|
|
})
|
|
}
|
|
}
|
|
|
|
// 归一化后的身份必须仍然通过上游的 originator ↔ UA 首段配对校验(issue #3901),
|
|
// 且再次归一化保持幂等。
|
|
func TestNormalizeCodexClientIdentityToCLIStaysPaired(t *testing.T) {
|
|
originator, ua, changed := NormalizeCodexClientIdentityToCLI(
|
|
"codex-tui",
|
|
"codex-tui/0.144.1 (Ubuntu 22.4.0; x86_64) xterm-256color (codex-tui; 0.144.1)",
|
|
)
|
|
require.True(t, changed)
|
|
|
|
pairedOriginator, pairedUA, ok := PairCodexClientIdentity(ua)
|
|
require.True(t, ok)
|
|
require.Equal(t, originator, pairedOriginator)
|
|
require.Equal(t, ua, pairedUA)
|
|
|
|
againOriginator, againUA, againChanged := NormalizeCodexClientIdentityToCLI(originator, ua)
|
|
require.False(t, againChanged)
|
|
require.Equal(t, originator, againOriginator)
|
|
require.Equal(t, ua, againUA)
|
|
}
|