From 878ad3b56952c964cd4507f2b4d190c330130c5e Mon Sep 17 00:00:00 2001
From: shaw
Date: Wed, 20 May 2026 14:33:51 +0800
Subject: [PATCH] =?UTF-8?q?feat(openai-gateway):=20Codex=20OAuth=20?=
=?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=B5=8F=E8=A7=88=E5=99=A8=20UA=20=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E6=94=B9=E5=86=99=E8=A7=84=E9=81=BF=20Cloudflare=20?=
=?UTF-8?q?=20=20=E8=B4=A8=E8=AF=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../internal/handler/admin/setting_handler.go | 21 +++++
backend/internal/handler/dto/settings.go | 1 +
backend/internal/pkg/openai/request.go | 11 +++
backend/internal/service/domain_constants.go | 4 +
.../service/openai_gateway_service.go | 32 ++++++++
backend/internal/service/setting_service.go | 76 +++++++++++++++++++
backend/internal/service/settings_view.go | 1 +
frontend/src/api/admin/settings.ts | 2 +
frontend/src/i18n/locales/en.ts | 3 +
frontend/src/i18n/locales/zh.ts | 3 +
frontend/src/views/admin/SettingsView.vue | 33 ++++++++
.../admin/__tests__/SettingsView.spec.ts | 1 +
12 files changed, 188 insertions(+)
diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go
index 9907d441..f73e4486 100644
--- a/backend/internal/handler/admin/setting_handler.go
+++ b/backend/internal/handler/admin/setting_handler.go
@@ -247,6 +247,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
+ OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
@@ -563,6 +564,7 @@ type UpdateSettingsRequest struct {
EnableAnthropicCacheTTL1hInjection *bool `json:"enable_anthropic_cache_ttl_1h_injection"`
RewriteMessageCacheControl *bool `json:"rewrite_message_cache_control"`
AntigravityUserAgentVersion *string `json:"antigravity_user_agent_version"`
+ OpenAICodexUserAgent *string `json:"openai_codex_user_agent"`
// Payment visible method routing
PaymentVisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
@@ -1404,6 +1406,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
return
}
}
+ if req.OpenAICodexUserAgent != nil {
+ normalized := strings.TrimSpace(*req.OpenAICodexUserAgent)
+ req.OpenAICodexUserAgent = &normalized
+ // 仅做长度上限保护,不限制具体格式(运维需要可自由调整 codex 版本号)
+ if len(normalized) > 512 {
+ response.Error(c, http.StatusBadRequest, "openai_codex_user_agent must be at most 512 characters")
+ return
+ }
+ }
// 交叉验证:如果同时设置了最低和最高版本号,最高版本号必须 >= 最低版本号
if req.MinClaudeCodeVersion != "" && req.MaxClaudeCodeVersion != "" {
@@ -1597,6 +1608,12 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
return previousSettings.AntigravityUserAgentVersion
}(),
+ OpenAICodexUserAgent: func() string {
+ if req.OpenAICodexUserAgent != nil {
+ return *req.OpenAICodexUserAgent
+ }
+ return previousSettings.OpenAICodexUserAgent
+ }(),
PaymentVisibleMethodAlipaySource: func() string {
if req.PaymentVisibleMethodAlipaySource != nil {
return strings.TrimSpace(*req.PaymentVisibleMethodAlipaySource)
@@ -1956,6 +1973,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
EnableAnthropicCacheTTL1hInjection: updatedSettings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: updatedSettings.RewriteMessageCacheControl,
AntigravityUserAgentVersion: updatedSettings.AntigravityUserAgentVersion,
+ OpenAICodexUserAgent: updatedSettings.OpenAICodexUserAgent,
PaymentVisibleMethodAlipaySource: updatedSettings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: updatedSettings.PaymentVisibleMethodWxpaySource,
PaymentVisibleMethodAlipayEnabled: updatedSettings.PaymentVisibleMethodAlipayEnabled,
@@ -2411,6 +2429,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.AntigravityUserAgentVersion != after.AntigravityUserAgentVersion {
changed = append(changed, "antigravity_user_agent_version")
}
+ if before.OpenAICodexUserAgent != after.OpenAICodexUserAgent {
+ changed = append(changed, "openai_codex_user_agent")
+ }
if before.PaymentVisibleMethodAlipaySource != after.PaymentVisibleMethodAlipaySource {
changed = append(changed, "payment_visible_method_alipay_source")
}
diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go
index 45ad7a70..fd3a90b3 100644
--- a/backend/internal/handler/dto/settings.go
+++ b/backend/internal/handler/dto/settings.go
@@ -181,6 +181,7 @@ type SystemSettings struct {
EnableAnthropicCacheTTL1hInjection bool `json:"enable_anthropic_cache_ttl_1h_injection"`
RewriteMessageCacheControl bool `json:"rewrite_message_cache_control"`
AntigravityUserAgentVersion string `json:"antigravity_user_agent_version"`
+ OpenAICodexUserAgent string `json:"openai_codex_user_agent"`
// Web Search Emulation
WebSearchEmulationEnabled bool `json:"web_search_emulation_enabled"`
diff --git a/backend/internal/pkg/openai/request.go b/backend/internal/pkg/openai/request.go
index dd8fe566..ae3886d6 100644
--- a/backend/internal/pkg/openai/request.go
+++ b/backend/internal/pkg/openai/request.go
@@ -30,6 +30,17 @@ var CodexOfficialClientOriginatorPrefixes = []string{
"codex ",
}
+// IsBrowserUserAgent 判断 User-Agent 是否来自浏览器(Chrome/Firefox/Safari/Edge/Opera 等)。
+// 所有现代浏览器的 UA 均以 "Mozilla/" 作为前缀,CLI 工具(codex/claude/curl/postman/python-requests 等)不会。
+// 该判定用于避免 Cloudflare 对浏览器型 UA 在 OpenAI 上游接口上触发 JS 质询。
+func IsBrowserUserAgent(userAgent string) bool {
+ ua := strings.TrimSpace(userAgent)
+ if ua == "" {
+ return false
+ }
+ return strings.HasPrefix(strings.ToLower(ua), "mozilla/")
+}
+
// IsCodexCLIRequest checks if the User-Agent indicates a Codex CLI request
func IsCodexCLIRequest(userAgent string) bool {
ua := normalizeCodexClientHeader(userAgent)
diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go
index f39c5d7e..e697f459 100644
--- a/backend/internal/service/domain_constants.go
+++ b/backend/internal/service/domain_constants.go
@@ -400,6 +400,10 @@ const (
SettingKeyRewriteMessageCacheControl = "rewrite_message_cache_control"
// SettingKeyAntigravityUserAgentVersion Antigravity 上游 User-Agent 版本号(空值使用环境变量/默认值)
SettingKeyAntigravityUserAgentVersion = "antigravity_user_agent_version"
+ // SettingKeyOpenAICodexUserAgent OpenAI Codex 完整 User-Agent(空值使用内置默认)
+ // 当客户端 UA 被识别为浏览器(Chrome/Firefox/Safari/Edge 等)时,转发给 OpenAI 上游前会替换为此值,
+ // 用于避免 Cloudflare 对浏览器型 UA 的质询拦截。
+ SettingKeyOpenAICodexUserAgent = "openai_codex_user_agent"
// Balance Low Notification
SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关
diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go
index cfaf5bff..bf3318a3 100644
--- a/backend/internal/service/openai_gateway_service.go
+++ b/backend/internal/service/openai_gateway_service.go
@@ -3231,6 +3231,10 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
req.Header.Set("user-agent", codexCLIUserAgent)
}
+ // 浏览器型 UA 兜底:仅 OAuth(ChatGPT 内部接口)账号生效,若最终 user-agent 仍为浏览器
+ // (Chrome/Firefox/Safari/Edge 等),替换为后台配置的 Codex UA,避免 Cloudflare 触发 JS 质询。
+ s.overrideBrowserUserAgent(ctx, account, req)
+
if req.Header.Get("content-type") == "" {
req.Header.Set("content-type", "application/json")
}
@@ -3947,6 +3951,10 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
req.Header.Set("user-agent", codexCLIUserAgent)
}
+ // 浏览器型 UA 兜底:仅 OAuth(ChatGPT 内部接口)账号生效,若最终 user-agent 仍为浏览器
+ // (Chrome/Firefox/Safari/Edge 等),替换为后台配置的 Codex UA,避免 Cloudflare 触发 JS 质询。
+ s.overrideBrowserUserAgent(ctx, account, req)
+
// Ensure required headers exist
if req.Header.Get("content-type") == "" {
req.Header.Set("content-type", "application/json")
@@ -3955,6 +3963,30 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
return req, nil
}
+// overrideBrowserUserAgent 检查请求的最终 user-agent,若为浏览器 UA 则替换为后台配置的 Codex UA。
+// 用于规避 Cloudflare 对浏览器型 UA 在 ChatGPT 内部接口上的访问质询。
+// 影响范围严格限定:仅 OAuth(Codex/ChatGPT 内部接口)账号生效;API Key 等其他账号原样透传。
+// 仅在识别为浏览器(Mozilla/...)时改写,其他 CLI/工具 UA 不动。
+func (s *OpenAIGatewayService) overrideBrowserUserAgent(ctx context.Context, account *Account, req *http.Request) {
+ if req == nil || account == nil {
+ return
+ }
+ if account.Type != AccountTypeOAuth {
+ return
+ }
+ currentUA := req.Header.Get("user-agent")
+ if !openai.IsBrowserUserAgent(currentUA) {
+ return
+ }
+ codexUA := DefaultOpenAICodexUserAgent
+ if s != nil && s.settingService != nil {
+ if v := strings.TrimSpace(s.settingService.GetOpenAICodexUserAgent(ctx)); v != "" {
+ codexUA = v
+ }
+ }
+ req.Header.Set("user-agent", codexUA)
+}
+
func (s *OpenAIGatewayService) handleErrorResponse(
ctx context.Context,
resp *http.Response,
diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go
index a5c16b1f..bd99e341 100644
--- a/backend/internal/service/setting_service.go
+++ b/backend/internal/service/setting_service.go
@@ -128,6 +128,19 @@ const antigravityUserAgentVersionCacheTTL = 60 * time.Second
const antigravityUserAgentVersionErrorTTL = 5 * time.Second
const antigravityUserAgentVersionDBTimeout = 5 * time.Second
+// DefaultOpenAICodexUserAgent OpenAI Codex 默认 User-Agent(用于规避 Cloudflare 对浏览器 UA 的质询)
+const DefaultOpenAICodexUserAgent = "codex-tui/0.125.0 (Ubuntu 22.4.0; x86_64) xterm-256color (codex-tui; 0.125.0)"
+
+// cachedOpenAICodexUserAgent 缓存 OpenAI Codex UA(进程内缓存,60s TTL)
+type cachedOpenAICodexUserAgent struct {
+ value string
+ expiresAt int64 // unix nano
+}
+
+const openAICodexUserAgentCacheTTL = 60 * time.Second
+const openAICodexUserAgentErrorTTL = 5 * time.Second
+const openAICodexUserAgentDBTimeout = 5 * time.Second
+
// DefaultSubscriptionGroupReader validates group references used by default subscriptions.
type DefaultSubscriptionGroupReader interface {
GetByID(ctx context.Context, id int64) (*Group, error)
@@ -148,6 +161,8 @@ type SettingService struct {
webSearchManagerBuilder WebSearchManagerBuilder
antigravityUAVersionCache atomic.Value // *cachedAntigravityUserAgentVersion
antigravityUAVersionSF singleflight.Group
+ openAICodexUACache atomic.Value // *cachedOpenAICodexUserAgent
+ openAICodexUASF singleflight.Group
}
type ProviderDefaultGrantSettings struct {
@@ -907,6 +922,55 @@ func (s *SettingService) GetAntigravityUserAgentVersion(ctx context.Context) str
return fallback
}
+// GetOpenAICodexUserAgent 返回 OpenAI Codex 上游请求使用的 User-Agent。
+// 后台设置优先;为空时回退到内置默认值。
+func (s *SettingService) GetOpenAICodexUserAgent(ctx context.Context) string {
+ fallback := DefaultOpenAICodexUserAgent
+ if s == nil || s.settingRepo == nil {
+ return fallback
+ }
+ if cached, ok := s.openAICodexUACache.Load().(*cachedOpenAICodexUserAgent); ok && cached != nil {
+ if time.Now().UnixNano() < cached.expiresAt {
+ return cached.value
+ }
+ }
+
+ result, _, _ := s.openAICodexUASF.Do("openai_codex_user_agent", func() (any, error) {
+ if cached, ok := s.openAICodexUACache.Load().(*cachedOpenAICodexUserAgent); ok && cached != nil {
+ if time.Now().UnixNano() < cached.expiresAt {
+ return cached.value, nil
+ }
+ }
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), openAICodexUserAgentDBTimeout)
+ defer cancel()
+ value, err := s.settingRepo.GetValue(dbCtx, SettingKeyOpenAICodexUserAgent)
+ if err != nil && !errors.Is(err, ErrSettingNotFound) {
+ slog.Warn("failed to get openai codex user agent setting", "error", err)
+ s.openAICodexUACache.Store(&cachedOpenAICodexUserAgent{
+ value: fallback,
+ expiresAt: time.Now().Add(openAICodexUserAgentErrorTTL).UnixNano(),
+ })
+ return fallback, nil
+ }
+ ua := strings.TrimSpace(value)
+ if ua == "" {
+ ua = fallback
+ }
+ s.openAICodexUACache.Store(&cachedOpenAICodexUserAgent{
+ value: ua,
+ expiresAt: time.Now().Add(openAICodexUserAgentCacheTTL).UnixNano(),
+ })
+ return ua, nil
+ })
+ if ua, ok := result.(string); ok && ua != "" {
+ return ua
+ }
+ return fallback
+}
+
// SetOnUpdateCallback sets a callback function to be called when settings are updated
// This is used for cache invalidation (e.g., HTML cache in frontend server)
func (s *SettingService) SetOnUpdateCallback(callback func()) {
@@ -1706,6 +1770,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
updates[SettingKeyEnableAnthropicCacheTTL1hInjection] = strconv.FormatBool(settings.EnableAnthropicCacheTTL1hInjection)
updates[SettingKeyRewriteMessageCacheControl] = strconv.FormatBool(settings.RewriteMessageCacheControl)
updates[SettingKeyAntigravityUserAgentVersion] = antigravity.NormalizeUserAgentVersion(settings.AntigravityUserAgentVersion)
+ updates[SettingKeyOpenAICodexUserAgent] = strings.TrimSpace(settings.OpenAICodexUserAgent)
updates[SettingPaymentVisibleMethodAlipaySource] = settings.PaymentVisibleMethodAlipaySource
updates[SettingPaymentVisibleMethodWxpaySource] = settings.PaymentVisibleMethodWxpaySource
updates[SettingPaymentVisibleMethodAlipayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodAlipayEnabled)
@@ -1788,6 +1853,15 @@ func (s *SettingService) refreshCachedSettings(settings *SystemSettings) {
version: antigravityUserAgentVersion,
expiresAt: time.Now().Add(antigravityUserAgentVersionCacheTTL).UnixNano(),
})
+ s.openAICodexUASF.Forget("openai_codex_user_agent")
+ codexUA := strings.TrimSpace(settings.OpenAICodexUserAgent)
+ if codexUA == "" {
+ codexUA = DefaultOpenAICodexUserAgent
+ }
+ s.openAICodexUACache.Store(&cachedOpenAICodexUserAgent{
+ value: codexUA,
+ expiresAt: time.Now().Add(openAICodexUserAgentCacheTTL).UnixNano(),
+ })
openAIAdvancedSchedulerSettingSF.Forget(openAIAdvancedSchedulerSettingKey)
openAIAdvancedSchedulerSettingCache.Store(&cachedOpenAIAdvancedSchedulerSetting{
enabled: settings.OpenAIAdvancedSchedulerEnabled,
@@ -2529,6 +2603,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyEnableAnthropicCacheTTL1hInjection: "false",
SettingKeyRewriteMessageCacheControl: strconv.FormatBool(s.defaultRewriteMessageCacheControl()),
SettingKeyAntigravityUserAgentVersion: "",
+ SettingKeyOpenAICodexUserAgent: "",
SettingPaymentVisibleMethodAlipaySource: "",
SettingPaymentVisibleMethodWxpaySource: "",
SettingPaymentVisibleMethodAlipayEnabled: "false",
@@ -3041,6 +3116,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result.RewriteMessageCacheControl = s.defaultRewriteMessageCacheControl()
}
result.AntigravityUserAgentVersion = antigravity.NormalizeUserAgentVersion(settings[SettingKeyAntigravityUserAgentVersion])
+ result.OpenAICodexUserAgent = strings.TrimSpace(settings[SettingKeyOpenAICodexUserAgent])
// Web search emulation: quick enabled check from the JSON config
if raw := settings[SettingKeyWebSearchEmulationConfig]; raw != "" {
diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go
index ea5fa57c..1e5e8b1c 100644
--- a/backend/internal/service/settings_view.go
+++ b/backend/internal/service/settings_view.go
@@ -193,6 +193,7 @@ type SystemSettings struct {
EnableAnthropicCacheTTL1hInjection bool // 是否对 Anthropic OAuth/SetupToken 请求体注入 1h cache_control ttl(默认 false)
RewriteMessageCacheControl bool // 是否改写 messages[*].content[*].cache_control(默认 false)
AntigravityUserAgentVersion string // Antigravity 上游 User-Agent 版本号;空值使用配置/默认值
+ OpenAICodexUserAgent string // OpenAI Codex 上游完整 User-Agent;空值使用内置默认
// Web Search Emulation
WebSearchEmulationEnabled bool // 是否启用 web search 模拟
diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts
index dfef451d..fd9e2b8d 100644
--- a/frontend/src/api/admin/settings.ts
+++ b/frontend/src/api/admin/settings.ts
@@ -504,6 +504,7 @@ export interface SystemSettings {
enable_anthropic_cache_ttl_1h_injection: boolean;
rewrite_message_cache_control: boolean;
antigravity_user_agent_version: string;
+ openai_codex_user_agent: string;
web_search_emulation_enabled?: boolean;
// Payment configuration
@@ -724,6 +725,7 @@ export interface UpdateSettingsRequest {
enable_anthropic_cache_ttl_1h_injection?: boolean;
rewrite_message_cache_control?: boolean;
antigravity_user_agent_version?: string;
+ openai_codex_user_agent?: string;
// Payment configuration
payment_enabled?: boolean;
risk_control_enabled?: boolean;
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 9ac1466e..70f7724c 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -5454,6 +5454,9 @@ export default {
antigravityUserAgentVersion: 'Antigravity UA Version',
antigravityUserAgentVersionPlaceholder: '1.23.2',
antigravityUserAgentVersionHint: 'Leave empty to use ANTIGRAVITY_USER_AGENT_VERSION or the built-in default 1.23.2; when set, the admin setting takes precedence.',
+ openaiCodexUserAgent: 'OpenAI Codex UA',
+ openaiCodexUserAgentPlaceholder: 'codex-tui/0.125.0 (Ubuntu 22.4.0; x86_64) xterm-256color (codex-tui; 0.125.0)',
+ openaiCodexUserAgentHint: 'Used to bypass Cloudflare browser-UA challenges on the OpenAI upstream. Only applies when the client User-Agent is detected as a browser (Mozilla/...). Leave empty to use the built-in default.',
},
webSearchEmulation: {
title: 'Web Search Emulation',
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index 3e90405a..3a9a64ff 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -5612,6 +5612,9 @@ export default {
antigravityUserAgentVersion: 'Antigravity UA 版本',
antigravityUserAgentVersionPlaceholder: '1.23.2',
antigravityUserAgentVersionHint: '留空时使用 ANTIGRAVITY_USER_AGENT_VERSION 或内置默认值 1.23.2;填写后后台设置优先。',
+ openaiCodexUserAgent: 'OpenAI Codex UA',
+ openaiCodexUserAgentPlaceholder: 'codex-tui/0.125.0 (Ubuntu 22.4.0; x86_64) xterm-256color (codex-tui; 0.125.0)',
+ openaiCodexUserAgentHint: '用于规避 OpenAI 上游 Cloudflare 对浏览器 UA 的访问质询。仅在检测到客户端 User-Agent 为浏览器(Mozilla/...)时生效,其他客户端原样透传。留空使用内置默认值。',
},
webSearchEmulation: {
title: 'Web Search 模拟',
diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue
index e0c3e1d4..118dd1a9 100644
--- a/frontend/src/views/admin/SettingsView.vue
+++ b/frontend/src/views/admin/SettingsView.vue
@@ -3769,6 +3769,36 @@
}}
+
+
+
+
+
+
+ {{
+ t(
+ "admin.settings.gatewayForwarding.openaiCodexUserAgentHint",
+ )
+ }}
+
+
@@ -6942,6 +6972,7 @@ const form = reactive({
enable_anthropic_cache_ttl_1h_injection: false,
rewrite_message_cache_control: false,
antigravity_user_agent_version: "",
+ openai_codex_user_agent: "",
// Balance & quota notification
balance_low_notify_enabled: false,
balance_low_notify_threshold: 0,
@@ -8042,6 +8073,8 @@ async function saveSettings() {
rewrite_message_cache_control: form.rewrite_message_cache_control,
antigravity_user_agent_version:
form.antigravity_user_agent_version?.trim() || "",
+ openai_codex_user_agent:
+ form.openai_codex_user_agent?.trim() || "",
// Payment configuration
payment_enabled: form.payment_enabled,
risk_control_enabled: form.risk_control_enabled,
diff --git a/frontend/src/views/admin/__tests__/SettingsView.spec.ts b/frontend/src/views/admin/__tests__/SettingsView.spec.ts
index 275e38c5..0d4ab7d2 100644
--- a/frontend/src/views/admin/__tests__/SettingsView.spec.ts
+++ b/frontend/src/views/admin/__tests__/SettingsView.spec.ts
@@ -371,6 +371,7 @@ const baseSettingsResponse = {
enable_anthropic_cache_ttl_1h_injection: false,
rewrite_message_cache_control: false,
antigravity_user_agent_version: "",
+ openai_codex_user_agent: "",
payment_enabled: true,
payment_min_amount: 1,
payment_max_amount: 10000,