From 13aa5b3375ccba8e1335215e2f04b4e4671ab10a Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 29 Mar 2026 22:18:14 +0800 Subject: [PATCH] Revert "fix(codex): restore prompt cache continuity for Codex requests" --- internal/runtime/executor/codex_continuity.go | 125 ----------------- internal/runtime/executor/codex_executor.go | 36 ++--- .../executor/codex_executor_cache_test.go | 128 +----------------- .../executor/codex_websockets_executor.go | 28 ++-- .../codex_websockets_executor_test.go | 45 ------ 5 files changed, 37 insertions(+), 325 deletions(-) delete mode 100644 internal/runtime/executor/codex_continuity.go diff --git a/internal/runtime/executor/codex_continuity.go b/internal/runtime/executor/codex_continuity.go deleted file mode 100644 index 9a0cd1b4..00000000 --- a/internal/runtime/executor/codex_continuity.go +++ /dev/null @@ -1,125 +0,0 @@ -package executor - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/google/uuid" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - log "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - "github.com/tidwall/sjson" -) - -type codexContinuity struct { - Key string - Source string -} - -func metadataString(meta map[string]any, key string) string { - if len(meta) == 0 { - return "" - } - raw, ok := meta[key] - if !ok || raw == nil { - return "" - } - switch v := raw.(type) { - case string: - return strings.TrimSpace(v) - case []byte: - return strings.TrimSpace(string(v)) - default: - return "" - } -} - -func principalString(raw any) string { - switch v := raw.(type) { - case string: - return strings.TrimSpace(v) - case fmt.Stringer: - return strings.TrimSpace(v.String()) - default: - return strings.TrimSpace(fmt.Sprintf("%v", raw)) - } -} - -func resolveCodexContinuity(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) codexContinuity { - if promptCacheKey := strings.TrimSpace(gjson.GetBytes(req.Payload, "prompt_cache_key").String()); promptCacheKey != "" { - return codexContinuity{Key: promptCacheKey, Source: "prompt_cache_key"} - } - if executionSession := metadataString(opts.Metadata, cliproxyexecutor.ExecutionSessionMetadataKey); executionSession != "" { - return codexContinuity{Key: executionSession, Source: "execution_session"} - } - if ginCtx := ginContextFrom(ctx); ginCtx != nil { - if ginCtx.Request != nil { - if v := strings.TrimSpace(ginCtx.GetHeader("Idempotency-Key")); v != "" { - return codexContinuity{Key: v, Source: "idempotency_key"} - } - } - if v, exists := ginCtx.Get("apiKey"); exists && v != nil { - if trimmed := principalString(v); trimmed != "" { - return codexContinuity{Key: uuid.NewSHA1(uuid.NameSpaceOID, []byte("cli-proxy-api:codex:prompt-cache:"+trimmed)).String(), Source: "client_principal"} - } - } - } - if auth != nil { - if authID := strings.TrimSpace(auth.ID); authID != "" { - return codexContinuity{Key: uuid.NewSHA1(uuid.NameSpaceOID, []byte("cli-proxy-api:codex:prompt-cache:auth:"+authID)).String(), Source: "auth_id"} - } - } - return codexContinuity{} -} - -func applyCodexContinuityBody(rawJSON []byte, continuity codexContinuity) []byte { - if continuity.Key == "" { - return rawJSON - } - rawJSON, _ = sjson.SetBytes(rawJSON, "prompt_cache_key", continuity.Key) - return rawJSON -} - -func applyCodexContinuityHeaders(headers http.Header, continuity codexContinuity) { - if headers == nil || continuity.Key == "" { - return - } - headers.Set("session_id", continuity.Key) -} - -func logCodexRequestDiagnostics(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, headers http.Header, body []byte, continuity codexContinuity) { - if !log.IsLevelEnabled(log.DebugLevel) { - return - } - entry := logWithRequestID(ctx) - authID := "" - authFile := "" - if auth != nil { - authID = strings.TrimSpace(auth.ID) - authFile = strings.TrimSpace(auth.FileName) - } - selectedAuthID := metadataString(opts.Metadata, cliproxyexecutor.SelectedAuthMetadataKey) - executionSessionID := metadataString(opts.Metadata, cliproxyexecutor.ExecutionSessionMetadataKey) - entry.Debugf( - "codex request diagnostics auth_id=%s selected_auth_id=%s auth_file=%s exec_session=%s continuity_source=%s session_id=%s prompt_cache_key=%s prompt_cache_retention=%s store=%t has_instructions=%t reasoning_effort=%s reasoning_summary=%s chatgpt_account_id=%t originator=%s model=%s source_format=%s", - authID, - selectedAuthID, - authFile, - executionSessionID, - continuity.Source, - strings.TrimSpace(headers.Get("session_id")), - gjson.GetBytes(body, "prompt_cache_key").String(), - gjson.GetBytes(body, "prompt_cache_retention").String(), - gjson.GetBytes(body, "store").Bool(), - gjson.GetBytes(body, "instructions").Exists(), - gjson.GetBytes(body, "reasoning.effort").String(), - gjson.GetBytes(body, "reasoning.summary").String(), - strings.TrimSpace(headers.Get("Chatgpt-Account-Id")) != "", - strings.TrimSpace(headers.Get("Originator")), - req.Model, - opts.SourceFormat.String(), - ) -} diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index b39ec939..fddf343d 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -111,6 +111,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re body, _ = sjson.SetBytes(body, "model", baseModel) body, _ = sjson.SetBytes(body, "stream", true) body, _ = sjson.DeleteBytes(body, "previous_response_id") + body, _ = sjson.DeleteBytes(body, "prompt_cache_retention") body, _ = sjson.DeleteBytes(body, "safety_identifier") body, _ = sjson.DeleteBytes(body, "stream_options") if !gjson.GetBytes(body, "instructions").Exists() { @@ -118,12 +119,11 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re } url := strings.TrimSuffix(baseURL, "/") + "/responses" - httpReq, continuity, err := e.cacheHelper(ctx, auth, from, url, req, opts, body) + httpReq, err := e.cacheHelper(ctx, from, url, req, body) if err != nil { return resp, err } applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg) - logCodexRequestDiagnostics(ctx, auth, req, opts, httpReq.Header, body, continuity) var authID, authLabel, authType, authValue string if auth != nil { authID = auth.ID @@ -223,12 +223,11 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A body, _ = sjson.DeleteBytes(body, "stream") url := strings.TrimSuffix(baseURL, "/") + "/responses/compact" - httpReq, continuity, err := e.cacheHelper(ctx, auth, from, url, req, opts, body) + httpReq, err := e.cacheHelper(ctx, from, url, req, body) if err != nil { return resp, err } applyCodexHeaders(httpReq, auth, apiKey, false, e.cfg) - logCodexRequestDiagnostics(ctx, auth, req, opts, httpReq.Header, body, continuity) var authID, authLabel, authType, authValue string if auth != nil { authID = auth.ID @@ -311,6 +310,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au requestedModel := payloadRequestedModel(opts, req.Model) body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) body, _ = sjson.DeleteBytes(body, "previous_response_id") + body, _ = sjson.DeleteBytes(body, "prompt_cache_retention") body, _ = sjson.DeleteBytes(body, "safety_identifier") body, _ = sjson.DeleteBytes(body, "stream_options") body, _ = sjson.SetBytes(body, "model", baseModel) @@ -319,12 +319,11 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au } url := strings.TrimSuffix(baseURL, "/") + "/responses" - httpReq, continuity, err := e.cacheHelper(ctx, auth, from, url, req, opts, body) + httpReq, err := e.cacheHelper(ctx, from, url, req, body) if err != nil { return nil, err } applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg) - logCodexRequestDiagnostics(ctx, auth, req, opts, httpReq.Header, body, continuity) var authID, authLabel, authType, authValue string if auth != nil { authID = auth.ID @@ -600,9 +599,8 @@ func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (* return auth, nil } -func (e *CodexExecutor) cacheHelper(ctx context.Context, auth *cliproxyauth.Auth, from sdktranslator.Format, url string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, rawJSON []byte) (*http.Request, codexContinuity, error) { +func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Format, url string, req cliproxyexecutor.Request, rawJSON []byte) (*http.Request, error) { var cache codexCache - continuity := codexContinuity{} if from == "claude" { userIDResult := gjson.GetBytes(req.Payload, "metadata.user_id") if userIDResult.Exists() { @@ -615,26 +613,30 @@ func (e *CodexExecutor) cacheHelper(ctx context.Context, auth *cliproxyauth.Auth } setCodexCache(key, cache) } - continuity = codexContinuity{Key: cache.ID, Source: "claude_user_cache"} } } else if from == "openai-response" { promptCacheKey := gjson.GetBytes(req.Payload, "prompt_cache_key") if promptCacheKey.Exists() { cache.ID = promptCacheKey.String() - continuity = codexContinuity{Key: cache.ID, Source: "prompt_cache_key"} } } else if from == "openai" { - continuity = resolveCodexContinuity(ctx, auth, req, opts) - cache.ID = continuity.Key + if apiKey := strings.TrimSpace(apiKeyFromContext(ctx)); apiKey != "" { + cache.ID = uuid.NewSHA1(uuid.NameSpaceOID, []byte("cli-proxy-api:codex:prompt-cache:"+apiKey)).String() + } } - rawJSON = applyCodexContinuityBody(rawJSON, continuity) + if cache.ID != "" { + rawJSON, _ = sjson.SetBytes(rawJSON, "prompt_cache_key", cache.ID) + } httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(rawJSON)) if err != nil { - return nil, continuity, err + return nil, err } - applyCodexContinuityHeaders(httpReq.Header, continuity) - return httpReq, continuity, nil + if cache.ID != "" { + httpReq.Header.Set("Conversation_id", cache.ID) + httpReq.Header.Set("Session_id", cache.ID) + } + return httpReq, nil } func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, stream bool, cfg *config.Config) { @@ -647,7 +649,7 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, s } misc.EnsureHeader(r.Header, ginHeaders, "Version", "") - misc.EnsureHeader(r.Header, ginHeaders, "session_id", uuid.NewString()) + misc.EnsureHeader(r.Header, ginHeaders, "Session_id", uuid.NewString()) misc.EnsureHeader(r.Header, ginHeaders, "X-Codex-Turn-Metadata", "") misc.EnsureHeader(r.Header, ginHeaders, "X-Client-Request-Id", "") cfgUserAgent, _ := codexHeaderDefaults(cfg, auth) diff --git a/internal/runtime/executor/codex_executor_cache_test.go b/internal/runtime/executor/codex_executor_cache_test.go index f6def7ae..d6dca031 100644 --- a/internal/runtime/executor/codex_executor_cache_test.go +++ b/internal/runtime/executor/codex_executor_cache_test.go @@ -8,7 +8,6 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" "github.com/tidwall/gjson" @@ -28,7 +27,7 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom } url := "https://example.com/responses" - httpReq, _, err := executor.cacheHelper(ctx, nil, sdktranslator.FromString("openai"), url, req, cliproxyexecutor.Options{}, rawJSON) + httpReq, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON) if err != nil { t.Fatalf("cacheHelper error: %v", err) } @@ -43,14 +42,14 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom if gotKey != expectedKey { t.Fatalf("prompt_cache_key = %q, want %q", gotKey, expectedKey) } - if gotSession := httpReq.Header.Get("session_id"); gotSession != expectedKey { - t.Fatalf("session_id = %q, want %q", gotSession, expectedKey) + if gotConversation := httpReq.Header.Get("Conversation_id"); gotConversation != expectedKey { + t.Fatalf("Conversation_id = %q, want %q", gotConversation, expectedKey) } - if got := httpReq.Header.Get("Conversation_id"); got != "" { - t.Fatalf("Conversation_id = %q, want empty", got) + if gotSession := httpReq.Header.Get("Session_id"); gotSession != expectedKey { + t.Fatalf("Session_id = %q, want %q", gotSession, expectedKey) } - httpReq2, _, err := executor.cacheHelper(ctx, nil, sdktranslator.FromString("openai"), url, req, cliproxyexecutor.Options{}, rawJSON) + httpReq2, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON) if err != nil { t.Fatalf("cacheHelper error (second call): %v", err) } @@ -63,118 +62,3 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom t.Fatalf("prompt_cache_key (second call) = %q, want %q", gotKey2, expectedKey) } } - -func TestCodexExecutorCacheHelper_OpenAIResponses_PreservesPromptCacheRetention(t *testing.T) { - executor := &CodexExecutor{} - url := "https://example.com/responses" - req := cliproxyexecutor.Request{ - Model: "gpt-5.3-codex", - Payload: []byte(`{"model":"gpt-5.3-codex","prompt_cache_key":"cache-key-1","prompt_cache_retention":"persistent"}`), - } - rawJSON := []byte(`{"model":"gpt-5.3-codex","stream":true,"prompt_cache_retention":"persistent"}`) - - httpReq, _, err := executor.cacheHelper(context.Background(), nil, sdktranslator.FromString("openai-response"), url, req, cliproxyexecutor.Options{}, rawJSON) - if err != nil { - t.Fatalf("cacheHelper error: %v", err) - } - - body, err := io.ReadAll(httpReq.Body) - if err != nil { - t.Fatalf("read request body: %v", err) - } - - if got := gjson.GetBytes(body, "prompt_cache_key").String(); got != "cache-key-1" { - t.Fatalf("prompt_cache_key = %q, want %q", got, "cache-key-1") - } - if got := gjson.GetBytes(body, "prompt_cache_retention").String(); got != "persistent" { - t.Fatalf("prompt_cache_retention = %q, want %q", got, "persistent") - } - if got := httpReq.Header.Get("session_id"); got != "cache-key-1" { - t.Fatalf("session_id = %q, want %q", got, "cache-key-1") - } - if got := httpReq.Header.Get("Conversation_id"); got != "" { - t.Fatalf("Conversation_id = %q, want empty", got) - } -} - -func TestCodexExecutorCacheHelper_OpenAIChatCompletions_UsesExecutionSessionForContinuity(t *testing.T) { - executor := &CodexExecutor{} - rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`) - req := cliproxyexecutor.Request{ - Model: "gpt-5.4", - Payload: []byte(`{"model":"gpt-5.4"}`), - } - opts := cliproxyexecutor.Options{Metadata: map[string]any{cliproxyexecutor.ExecutionSessionMetadataKey: "exec-session-1"}} - - httpReq, _, err := executor.cacheHelper(context.Background(), nil, sdktranslator.FromString("openai"), "https://example.com/responses", req, opts, rawJSON) - if err != nil { - t.Fatalf("cacheHelper error: %v", err) - } - - body, err := io.ReadAll(httpReq.Body) - if err != nil { - t.Fatalf("read request body: %v", err) - } - - if got := gjson.GetBytes(body, "prompt_cache_key").String(); got != "exec-session-1" { - t.Fatalf("prompt_cache_key = %q, want %q", got, "exec-session-1") - } - if got := httpReq.Header.Get("session_id"); got != "exec-session-1" { - t.Fatalf("session_id = %q, want %q", got, "exec-session-1") - } -} - -func TestCodexExecutorCacheHelper_OpenAIChatCompletions_FallsBackToStableAuthID(t *testing.T) { - executor := &CodexExecutor{} - rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`) - req := cliproxyexecutor.Request{ - Model: "gpt-5.4", - Payload: []byte(`{"model":"gpt-5.4"}`), - } - auth := &cliproxyauth.Auth{ID: "codex-auth-1", Provider: "codex"} - - httpReq, _, err := executor.cacheHelper(context.Background(), auth, sdktranslator.FromString("openai"), "https://example.com/responses", req, cliproxyexecutor.Options{}, rawJSON) - if err != nil { - t.Fatalf("cacheHelper error: %v", err) - } - - body, err := io.ReadAll(httpReq.Body) - if err != nil { - t.Fatalf("read request body: %v", err) - } - - expected := uuid.NewSHA1(uuid.NameSpaceOID, []byte("cli-proxy-api:codex:prompt-cache:auth:codex-auth-1")).String() - if got := gjson.GetBytes(body, "prompt_cache_key").String(); got != expected { - t.Fatalf("prompt_cache_key = %q, want %q", got, expected) - } - if got := httpReq.Header.Get("session_id"); got != expected { - t.Fatalf("session_id = %q, want %q", got, expected) - } -} - -func TestCodexExecutorCacheHelper_ClaudePreservesCacheContinuity(t *testing.T) { - executor := &CodexExecutor{} - req := cliproxyexecutor.Request{ - Model: "claude-3-7-sonnet", - Payload: []byte(`{"metadata":{"user_id":"user-1"}}`), - } - rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`) - - httpReq, continuity, err := executor.cacheHelper(context.Background(), nil, sdktranslator.FromString("claude"), "https://example.com/responses", req, cliproxyexecutor.Options{}, rawJSON) - if err != nil { - t.Fatalf("cacheHelper error: %v", err) - } - if continuity.Key == "" { - t.Fatal("continuity.Key = empty, want non-empty") - } - body, err := io.ReadAll(httpReq.Body) - if err != nil { - t.Fatalf("read request body: %v", err) - } - if got := gjson.GetBytes(body, "prompt_cache_key").String(); got != continuity.Key { - t.Fatalf("prompt_cache_key = %q, want %q", got, continuity.Key) - } - if got := httpReq.Header.Get("session_id"); got != continuity.Key { - t.Fatalf("session_id = %q, want %q", got, continuity.Key) - } -} diff --git a/internal/runtime/executor/codex_websockets_executor.go b/internal/runtime/executor/codex_websockets_executor.go index 50cc736d..fca82fe7 100644 --- a/internal/runtime/executor/codex_websockets_executor.go +++ b/internal/runtime/executor/codex_websockets_executor.go @@ -178,6 +178,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut body, _ = sjson.SetBytes(body, "model", baseModel) body, _ = sjson.SetBytes(body, "stream", true) body, _ = sjson.DeleteBytes(body, "previous_response_id") + body, _ = sjson.DeleteBytes(body, "prompt_cache_retention") body, _ = sjson.DeleteBytes(body, "safety_identifier") if !gjson.GetBytes(body, "instructions").Exists() { body, _ = sjson.SetBytes(body, "instructions", "") @@ -189,7 +190,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut return resp, err } - body, wsHeaders, continuity := applyCodexPromptCacheHeaders(ctx, auth, from, req, opts, body) + body, wsHeaders := applyCodexPromptCacheHeaders(from, req, body) wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg) var authID, authLabel, authType, authValue string @@ -208,7 +209,6 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut } wsReqBody := buildCodexWebsocketRequestBody(body) - logCodexRequestDiagnostics(ctx, auth, req, opts, wsHeaders, body, continuity) recordAPIRequest(ctx, e.cfg, upstreamRequestLog{ URL: wsURL, Method: "WEBSOCKET", @@ -385,7 +385,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr return nil, err } - body, wsHeaders, continuity := applyCodexPromptCacheHeaders(ctx, auth, from, req, opts, body) + body, wsHeaders := applyCodexPromptCacheHeaders(from, req, body) wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg) var authID, authLabel, authType, authValue string @@ -403,7 +403,6 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr } wsReqBody := buildCodexWebsocketRequestBody(body) - logCodexRequestDiagnostics(ctx, auth, req, opts, wsHeaders, body, continuity) recordAPIRequest(ctx, e.cfg, upstreamRequestLog{ URL: wsURL, Method: "WEBSOCKET", @@ -762,14 +761,13 @@ func buildCodexResponsesWebsocketURL(httpURL string) (string, error) { return parsed.String(), nil } -func applyCodexPromptCacheHeaders(ctx context.Context, auth *cliproxyauth.Auth, from sdktranslator.Format, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, rawJSON []byte) ([]byte, http.Header, codexContinuity) { +func applyCodexPromptCacheHeaders(from sdktranslator.Format, req cliproxyexecutor.Request, rawJSON []byte) ([]byte, http.Header) { headers := http.Header{} if len(rawJSON) == 0 { - return rawJSON, headers, codexContinuity{} + return rawJSON, headers } var cache codexCache - continuity := codexContinuity{} if from == "claude" { userIDResult := gjson.GetBytes(req.Payload, "metadata.user_id") if userIDResult.Exists() { @@ -783,22 +781,20 @@ func applyCodexPromptCacheHeaders(ctx context.Context, auth *cliproxyauth.Auth, } setCodexCache(key, cache) } - continuity = codexContinuity{Key: cache.ID, Source: "claude_user_cache"} } } else if from == "openai-response" { if promptCacheKey := gjson.GetBytes(req.Payload, "prompt_cache_key"); promptCacheKey.Exists() { cache.ID = promptCacheKey.String() - continuity = codexContinuity{Key: cache.ID, Source: "prompt_cache_key"} } - } else if from == "openai" { - continuity = resolveCodexContinuity(ctx, auth, req, opts) - cache.ID = continuity.Key } - rawJSON = applyCodexContinuityBody(rawJSON, continuity) - applyCodexContinuityHeaders(headers, continuity) + if cache.ID != "" { + rawJSON, _ = sjson.SetBytes(rawJSON, "prompt_cache_key", cache.ID) + headers.Set("Conversation_id", cache.ID) + headers.Set("Session_id", cache.ID) + } - return rawJSON, headers, continuity + return rawJSON, headers } func applyCodexWebsocketHeaders(ctx context.Context, headers http.Header, auth *cliproxyauth.Auth, token string, cfg *config.Config) http.Header { @@ -830,7 +826,7 @@ func applyCodexWebsocketHeaders(ctx context.Context, headers http.Header, auth * betaHeader = codexResponsesWebsocketBetaHeaderValue } headers.Set("OpenAI-Beta", betaHeader) - misc.EnsureHeader(headers, ginHeaders, "session_id", uuid.NewString()) + misc.EnsureHeader(headers, ginHeaders, "Session_id", uuid.NewString()) ensureHeaderWithConfigPrecedence(headers, ginHeaders, "User-Agent", cfgUserAgent, codexUserAgent) isAPIKey := false diff --git a/internal/runtime/executor/codex_websockets_executor_test.go b/internal/runtime/executor/codex_websockets_executor_test.go index 0a06982f..d34e7c39 100644 --- a/internal/runtime/executor/codex_websockets_executor_test.go +++ b/internal/runtime/executor/codex_websockets_executor_test.go @@ -9,9 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" "github.com/tidwall/gjson" ) @@ -34,49 +32,6 @@ func TestBuildCodexWebsocketRequestBodyPreservesPreviousResponseID(t *testing.T) } } -func TestApplyCodexPromptCacheHeaders_PreservesPromptCacheRetention(t *testing.T) { - req := cliproxyexecutor.Request{ - Model: "gpt-5-codex", - Payload: []byte(`{"prompt_cache_key":"cache-key-1","prompt_cache_retention":"persistent"}`), - } - body := []byte(`{"model":"gpt-5-codex","stream":true,"prompt_cache_retention":"persistent"}`) - - updatedBody, headers, _ := applyCodexPromptCacheHeaders(context.Background(), nil, sdktranslator.FromString("openai-response"), req, cliproxyexecutor.Options{}, body) - - if got := gjson.GetBytes(updatedBody, "prompt_cache_key").String(); got != "cache-key-1" { - t.Fatalf("prompt_cache_key = %q, want %q", got, "cache-key-1") - } - if got := gjson.GetBytes(updatedBody, "prompt_cache_retention").String(); got != "persistent" { - t.Fatalf("prompt_cache_retention = %q, want %q", got, "persistent") - } - if got := headers.Get("session_id"); got != "cache-key-1" { - t.Fatalf("session_id = %q, want %q", got, "cache-key-1") - } - if got := headers.Get("Conversation_id"); got != "" { - t.Fatalf("Conversation_id = %q, want empty", got) - } -} - -func TestApplyCodexPromptCacheHeaders_ClaudePreservesContinuity(t *testing.T) { - req := cliproxyexecutor.Request{ - Model: "claude-3-7-sonnet", - Payload: []byte(`{"metadata":{"user_id":"user-1"}}`), - } - body := []byte(`{"model":"gpt-5.4","stream":true}`) - - updatedBody, headers, continuity := applyCodexPromptCacheHeaders(context.Background(), nil, sdktranslator.FromString("claude"), req, cliproxyexecutor.Options{}, body) - - if continuity.Key == "" { - t.Fatal("continuity.Key = empty, want non-empty") - } - if got := gjson.GetBytes(updatedBody, "prompt_cache_key").String(); got != continuity.Key { - t.Fatalf("prompt_cache_key = %q, want %q", got, continuity.Key) - } - if got := headers.Get("session_id"); got != continuity.Key { - t.Fatalf("session_id = %q, want %q", got, continuity.Key) - } -} - func TestApplyCodexWebsocketHeadersDefaultsToCurrentResponsesBeta(t *testing.T) { headers := applyCodexWebsocketHeaders(context.Background(), http.Header{}, nil, "", nil)