fix(openai): honor Codex session-id header

This commit is contained in:
hank
2026-08-25 17:08:27 +08:00
parent aa2c4e8d13
commit 8e60d57475
4 changed files with 69 additions and 2 deletions
@@ -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)
@@ -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()
@@ -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"
}
@@ -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)
}