diff --git a/README.md b/README.md index 127653ce8..50ffe7e43 100644 --- a/README.md +++ b/README.md @@ -640,6 +640,32 @@ Or set `GATEWAY_OPENAI_WS_MODE_ROUTER_V2_ENABLED=true` in the environment. Use `http_bridge` for client-WebSocket/upstream-HTTP operation when rolling out or mitigating upstream WebSocket issues. +#### Force OpenAI upstream HTTP/SSE + +When an egress proxy or network repeatedly reconnects OpenAI Responses +WebSockets, set the global fallback in the persisted deployment configuration: + +```yaml +gateway: + openai_ws: + force_http: true +``` + +For Compose and Apple container deployments, the equivalent `.env` setting is: + +```bash +GATEWAY_OPENAI_WS_FORCE_HTTP=true +``` + +This selects HTTP/SSE for OpenAI upstream Responses traffic that would +otherwise use WebSocket. It does not change the client-facing protocol or force +HTTP/1.1; configure `gateway.openai_http2.enabled` (or +`GATEWAY_OPENAI_HTTP2_ENABLED=false`) separately when a proxy is incompatible +with HTTP/2. Unlike the account-level `http_bridge` mode, this global fallback +takes effect without enabling `mode_router_v2_enabled`. Keep the setting in the +deployment's persisted `.env` or `config.yaml`, rather than inside a running +container, so it is read again after an image update or container recreation. + #### ⚠️ Important: Creating the Admin Account The initial admin account is **only created via the setup wizard** (served at `http://:8080` on first run). The `default.admin_email` / `default.admin_password` fields in `config.yaml` are **not used** to create it — they exist in the template for historical reasons. diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index d8cfe820b..7cef4dfc1 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -553,6 +553,15 @@ func TestLoadOpenAIWSClientFirstMessageTimeoutFromEnv(t *testing.T) { require.Equal(t, 120, cfg.Gateway.OpenAIWS.ClientFirstMessageTimeoutSeconds) } +func TestLoadOpenAIWSForceHTTPFromEnv(t *testing.T) { + resetViperWithJWTSecret(t) + t.Setenv("GATEWAY_OPENAI_WS_FORCE_HTTP", "true") + + cfg, err := Load() + require.NoError(t, err) + require.True(t, cfg.Gateway.OpenAIWS.ForceHTTP) +} + func TestLoadDefaultOpenAICompactModel(t *testing.T) { resetViperWithJWTSecret(t) diff --git a/backend/internal/service/openai_ws_protocol_resolver_test.go b/backend/internal/service/openai_ws_protocol_resolver_test.go index 12e047f10..32716a2c7 100644 --- a/backend/internal/service/openai_ws_protocol_resolver_test.go +++ b/backend/internal/service/openai_ws_protocol_resolver_test.go @@ -87,6 +87,15 @@ func TestOpenAIWSProtocolResolver_Resolve(t *testing.T) { require.Equal(t, "account_force_http", decision.Reason) }) + t.Run("全局强制HTTP无需启用mode router", func(t *testing.T) { + cfg := *baseCfg + cfg.Gateway.OpenAIWS.ForceHTTP = true + + decision := NewOpenAIWSProtocolResolver(&cfg).Resolve(openAIOAuthEnabled) + require.Equal(t, OpenAIUpstreamTransportHTTPSSE, decision.Transport) + require.Equal(t, "global_force_http", decision.Reason) + }) + t.Run("全局关闭保持HTTP", func(t *testing.T) { cfg := *baseCfg cfg.Gateway.OpenAIWS.Enabled = false diff --git a/deploy/.env.example b/deploy/.env.example index 6beca8830..76762e303 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -285,6 +285,11 @@ GATEWAY_FORCE_CODEX_CLI=false GATEWAY_OPENAI_COMPACT_MODEL=gpt-5.4 # OpenAI/Codex 等待上游响应头超时(秒);0 表示不使用本地响应头超时截断。 GATEWAY_OPENAI_RESPONSE_HEADER_TIMEOUT=0 +# 全局强制 OpenAI Responses 上游使用 HTTP/SSE,不使用 WebSocket。 +# 当代理或网络导致上游 WebSocket 反复重连时可设为 true;这不会禁用 HTTP/2。 +# 如需回退 HTTP/1.1,请另行设置 GATEWAY_OPENAI_HTTP2_ENABLED=false。 +# 将此项保存在持久化 .env 中,镜像更新或容器重建后会在启动时重新读取。 +GATEWAY_OPENAI_WS_FORCE_HTTP=false # OpenAI HTTP 上游默认启用 HTTP/2;如需紧急回滚可设为 false。 GATEWAY_OPENAI_HTTP2_ENABLED=true GATEWAY_OPENAI_HTTP2_ALLOW_PROXY_FALLBACK_TO_HTTP1=true diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml index 38a50d060..cacd07574 100644 --- a/deploy/config.example.yaml +++ b/deploy/config.example.yaml @@ -329,7 +329,8 @@ gateway: # 按账号类型细分开关 oauth_enabled: true apikey_enabled: true - # 全局强制 HTTP(紧急回滚开关) + # 全局强制 OpenAI Responses 上游使用 HTTP/SSE,不使用 WebSocket(紧急回滚开关)。 + # 此开关不强制 HTTP/1.1;HTTP/2 由 gateway.openai_http2 单独控制。 force_http: false # 允许在 WSv2 下按策略恢复 store=true(默认 false) allow_store_recovery: false