From 8e60d57475d4ceab0b3a6d4ec6d76d9960b82e4a Mon Sep 17 00:00:00 2001 From: hank Date: Tue, 25 Aug 2026 17:08:27 +0800 Subject: [PATCH] fix(openai): honor Codex session-id header --- .../service/openai_gateway_scheduling.go | 3 +- .../service/openai_gateway_service_test.go | 26 +++++++++++++ .../service/openai_ws_forwarder_logutil.go | 5 ++- .../openai_ws_forwarder_logutil_test.go | 37 +++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 backend/internal/service/openai_ws_forwarder_logutil_test.go diff --git a/backend/internal/service/openai_gateway_scheduling.go b/backend/internal/service/openai_gateway_scheduling.go index 2a4a4f3d09..7ab83a3de8 100644 --- a/backend/internal/service/openai_gateway_scheduling.go +++ b/backend/internal/service/openai_gateway_scheduling.go @@ -28,6 +28,7 @@ const ( ) var explicitOpenAIHeaderSessionNames = []string{ + "session-id", "session_id", "conversation_id", openCodeSessionAffinityHeader, @@ -145,7 +146,7 @@ func (s *OpenAIGatewayService) GenerateExplicitSessionHash(c *gin.Context, body // GenerateSessionHash generates a sticky-session hash for OpenAI requests. // // Priority: -// 1. Header: session_id +// 1. Header: session-id / session_id // 2. Header: conversation_id // 3. Header: x-session-affinity / x-session-id / x-opencode-session (OpenCode) // 4. Header: x-conversation-id (CodeBuddy) diff --git a/backend/internal/service/openai_gateway_service_test.go b/backend/internal/service/openai_gateway_service_test.go index 1e41298cc1..77f4ee33d6 100644 --- a/backend/internal/service/openai_gateway_service_test.go +++ b/backend/internal/service/openai_gateway_service_test.go @@ -391,6 +391,7 @@ func TestOpenAIGatewayService_ClientSessionHeaderPriority(t *testing.T) { name string value string }{ + {name: "session-id", value: "codex-session"}, {name: "session_id", value: "generic-session"}, {name: "conversation_id", value: "generic-conversation"}, {name: openCodeSessionAffinityHeader, value: "opencode-affinity"}, @@ -416,6 +417,31 @@ func TestOpenAIGatewayService_ClientSessionHeaderPriority(t *testing.T) { require.Equal(t, "body-session", svc.ExtractSessionID(c, body)) } +func TestOpenAIGatewayService_CodexSessionIDKeepsReconnectHashStable(t *testing.T) { + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + c, _ := gin.CreateTestContext(recorder) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/responses", nil) + c.Request.Header.Set("session-id", "codex-reconnect-session") + + svc := &OpenAIGatewayService{} + warmup := []byte(`{ + "type":"response.create", + "model":"gpt-5.6-sol", + "generate":false, + "tools":[{"type":"custom","name":"exec"}], + "input":[{"role":"user","content":"warmup"}] + }`) + business := []byte(`{ + "type":"response.create", + "model":"gpt-5.6-sol", + "input":[{"role":"user","content":"install codex"}] + }`) + + require.Equal(t, svc.GenerateSessionHash(c, warmup), svc.GenerateSessionHash(c, business)) + require.Equal(t, "codex-reconnect-session", svc.ExtractSessionID(c, business)) +} + func TestOpenAIGatewayService_ClientSessionHeadersIgnorePerRequestIDs(t *testing.T) { gin.SetMode(gin.TestMode) recorder := httptest.NewRecorder() diff --git a/backend/internal/service/openai_ws_forwarder_logutil.go b/backend/internal/service/openai_ws_forwarder_logutil.go index ac5a3720b1..ddb37d8380 100644 --- a/backend/internal/service/openai_ws_forwarder_logutil.go +++ b/backend/internal/service/openai_ws_forwarder_logutil.go @@ -65,7 +65,10 @@ func resolveOpenAIWSSessionHeaders(c *gin.Context, promptCacheKey string) openAI ConversationSource: "none", } if c != nil && c.Request != nil { - if sessionID := strings.TrimSpace(c.Request.Header.Get("session_id")); sessionID != "" { + if sessionID := strings.TrimSpace(c.Request.Header.Get("session-id")); sessionID != "" { + resolution.SessionID = sessionID + resolution.SessionSource = "header_session-id" + } else if sessionID := strings.TrimSpace(c.Request.Header.Get("session_id")); sessionID != "" { resolution.SessionID = sessionID resolution.SessionSource = "header_session_id" } diff --git a/backend/internal/service/openai_ws_forwarder_logutil_test.go b/backend/internal/service/openai_ws_forwarder_logutil_test.go new file mode 100644 index 0000000000..eaf8b5f22b --- /dev/null +++ b/backend/internal/service/openai_ws_forwarder_logutil_test.go @@ -0,0 +1,37 @@ +package service + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +func TestResolveOpenAIWSSessionHeadersPrefersCodexHyphenHeader(t *testing.T) { + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + c, _ := gin.CreateTestContext(recorder) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/responses", nil) + c.Request.Header.Set("session-id", "codex-session") + c.Request.Header.Set("session_id", "legacy-session") + + resolution := resolveOpenAIWSSessionHeaders(c, "prompt-cache") + + require.Equal(t, "codex-session", resolution.SessionID) + require.Equal(t, "header_session-id", resolution.SessionSource) +} + +func TestResolveOpenAIWSSessionHeadersFallsBackToLegacyHeader(t *testing.T) { + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + c, _ := gin.CreateTestContext(recorder) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/responses", nil) + c.Request.Header.Set("session_id", "legacy-session") + + resolution := resolveOpenAIWSSessionHeaders(c, "prompt-cache") + + require.Equal(t, "legacy-session", resolution.SessionID) + require.Equal(t, "header_session_id", resolution.SessionSource) +}