mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-04 17:43:32 +08:00
feat(executor): enhance Codex identity obfuscation with turn and window metadata handling
- Modified `applyCodexIdentityConfuse*` functions to include `turn_id` and `window_id` in metadata transformations. - Updated test cases to validate the inclusion and restoration of these fields. - Removed deprecated `Conversation_id` header support and related logic for cleaner implementation.
This commit is contained in:
@@ -30,8 +30,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
codexUserAgent = "codex_cli_rs/0.118.0 (Mac OS 26.3.1; arm64) iTerm.app/3.6.9"
|
||||
codexOriginator = "codex_cli_rs"
|
||||
codexUserAgent = "codex-tui/0.135.0 (Mac OS 26.5.0; arm64) iTerm.app/3.6.10 (codex-tui; 0.135.0)"
|
||||
codexOriginator = "codex-tui"
|
||||
codexDefaultImageToolModel = "gpt-image-2"
|
||||
)
|
||||
|
||||
@@ -304,7 +304,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
||||
return resp, err
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
var authID, authLabel, authType, authValue string
|
||||
if auth != nil {
|
||||
authID = auth.ID
|
||||
@@ -467,7 +467,7 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
|
||||
return resp, err
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, apiKey, false, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
var authID, authLabel, authType, authValue string
|
||||
if auth != nil {
|
||||
authID = auth.ID
|
||||
@@ -575,7 +575,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
||||
return nil, err
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
var authID, authLabel, authType, authValue string
|
||||
if auth != nil {
|
||||
authID = auth.ID
|
||||
@@ -881,8 +881,16 @@ func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*
|
||||
}
|
||||
|
||||
type codexIdentityConfuseState struct {
|
||||
enabled bool
|
||||
authID string
|
||||
originalPromptCacheKey string
|
||||
promptCacheKey string
|
||||
turnIDs []codexIdentityReplacement
|
||||
}
|
||||
|
||||
type codexIdentityReplacement struct {
|
||||
original string
|
||||
confused string
|
||||
}
|
||||
|
||||
func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Format, url string, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, userPayload []byte, rawJSON []byte) (*http.Request, []byte, codexIdentityConfuseState, error) {
|
||||
@@ -931,7 +939,7 @@ func applyCodexIdentityConfuseBody(cfg *config.Config, auth *cliproxyauth.Auth,
|
||||
return rawJSON, codexIdentityConfuseState{}
|
||||
}
|
||||
|
||||
state := codexIdentityConfuseState{}
|
||||
state := codexIdentityConfuseState{enabled: true, authID: strings.TrimSpace(auth.ID)}
|
||||
if promptCacheKey := strings.TrimSpace(gjson.GetBytes(userPayload, "prompt_cache_key").String()); promptCacheKey != "" {
|
||||
state.originalPromptCacheKey = promptCacheKey
|
||||
state.promptCacheKey = codexIdentityConfuseUUID(auth.ID, "prompt-cache", promptCacheKey)
|
||||
@@ -940,10 +948,10 @@ func applyCodexIdentityConfuseBody(cfg *config.Config, auth *cliproxyauth.Auth,
|
||||
if installationID := strings.TrimSpace(gjson.GetBytes(userPayload, "client_metadata.x-codex-installation-id").String()); installationID != "" {
|
||||
rawJSON, _ = sjson.SetBytes(rawJSON, "client_metadata.x-codex-installation-id", codexIdentityConfuseUUID(auth.ID, "installation", installationID))
|
||||
}
|
||||
if turnMetadata := strings.TrimSpace(gjson.GetBytes(rawJSON, "client_metadata.x-codex-turn-metadata").String()); turnMetadata != "" {
|
||||
rawJSON, _ = sjson.SetBytes(rawJSON, "client_metadata.x-codex-turn-metadata", applyCodexTurnMetadataIdentityConfuse(turnMetadata, &state))
|
||||
}
|
||||
if state.promptCacheKey != "" {
|
||||
if turnMetadata := strings.TrimSpace(gjson.GetBytes(rawJSON, "client_metadata.x-codex-turn-metadata").String()); turnMetadata != "" {
|
||||
rawJSON, _ = sjson.SetBytes(rawJSON, "client_metadata.x-codex-turn-metadata", applyCodexTurnMetadataIdentityConfuse(turnMetadata, state))
|
||||
}
|
||||
if windowID := strings.TrimSpace(gjson.GetBytes(rawJSON, "client_metadata.x-codex-window-id").String()); windowID != "" {
|
||||
rawJSON, _ = sjson.SetBytes(rawJSON, "client_metadata.x-codex-window-id", state.promptCacheKey+":0")
|
||||
}
|
||||
@@ -952,38 +960,76 @@ func applyCodexIdentityConfuseBody(cfg *config.Config, auth *cliproxyauth.Auth,
|
||||
return rawJSON, state
|
||||
}
|
||||
|
||||
func applyCodexIdentityConfuseHeaders(headers http.Header, state codexIdentityConfuseState) {
|
||||
if headers == nil || state.promptCacheKey == "" {
|
||||
func applyCodexIdentityConfuseHeaders(headers http.Header, state *codexIdentityConfuseState) {
|
||||
if headers == nil {
|
||||
return
|
||||
}
|
||||
defer deleteDeprecatedCodexConversationHeader(headers)
|
||||
if state == nil || !state.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
setHeaderCasePreserved(headers, "Session-Id", state.promptCacheKey)
|
||||
headers.Set("Conversation_id", state.promptCacheKey)
|
||||
headers.Set("X-Client-Request-Id", state.promptCacheKey)
|
||||
headers.Set("Thread-Id", state.promptCacheKey)
|
||||
headers.Set("X-Codex-Window-Id", state.promptCacheKey+":0")
|
||||
|
||||
if rawTurnMetadata := strings.TrimSpace(headers.Get("X-Codex-Turn-Metadata")); rawTurnMetadata != "" {
|
||||
headers.Set("X-Codex-Turn-Metadata", applyCodexTurnMetadataIdentityConfuse(rawTurnMetadata, state))
|
||||
}
|
||||
if state.promptCacheKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
setHeaderCasePreserved(headers, "Session-Id", state.promptCacheKey)
|
||||
headers.Set("X-Client-Request-Id", state.promptCacheKey)
|
||||
headers.Set("Thread-Id", state.promptCacheKey)
|
||||
headers.Set("X-Codex-Window-Id", state.promptCacheKey+":0")
|
||||
}
|
||||
|
||||
func applyCodexTurnMetadataIdentityConfuse(rawTurnMetadata string, state codexIdentityConfuseState) string {
|
||||
func applyCodexTurnMetadataIdentityConfuse(rawTurnMetadata string, state *codexIdentityConfuseState) string {
|
||||
updatedTurnMetadata := rawTurnMetadata
|
||||
if gjson.Get(rawTurnMetadata, "prompt_cache_key").Exists() {
|
||||
if state == nil || !state.enabled {
|
||||
return updatedTurnMetadata
|
||||
}
|
||||
if state.promptCacheKey != "" && gjson.Get(rawTurnMetadata, "prompt_cache_key").Exists() {
|
||||
updatedTurnMetadata, _ = sjson.Set(updatedTurnMetadata, "prompt_cache_key", state.promptCacheKey)
|
||||
} else if state.originalPromptCacheKey != "" {
|
||||
} else if state.promptCacheKey != "" && state.originalPromptCacheKey != "" {
|
||||
updatedTurnMetadata = strings.ReplaceAll(updatedTurnMetadata, state.originalPromptCacheKey, state.promptCacheKey)
|
||||
}
|
||||
if turnID := strings.TrimSpace(gjson.Get(rawTurnMetadata, "turn_id").String()); turnID != "" {
|
||||
updatedTurnMetadata, _ = sjson.Set(updatedTurnMetadata, "turn_id", state.confuseTurnID(turnID))
|
||||
}
|
||||
if state.promptCacheKey != "" && gjson.Get(rawTurnMetadata, "window_id").Exists() {
|
||||
updatedTurnMetadata, _ = sjson.Set(updatedTurnMetadata, "window_id", state.promptCacheKey+":0")
|
||||
}
|
||||
return updatedTurnMetadata
|
||||
}
|
||||
|
||||
func applyCodexIdentityConfuseResponsePayload(payload []byte, state codexIdentityConfuseState) []byte {
|
||||
return replaceCodexIdentityResponsePayload(payload, state.originalPromptCacheKey, state.promptCacheKey)
|
||||
payload = replaceCodexIdentityResponsePayload(payload, state.originalPromptCacheKey, state.promptCacheKey)
|
||||
for _, turnID := range state.turnIDs {
|
||||
payload = replaceCodexIdentityResponsePayload(payload, turnID.original, turnID.confused)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func applyCodexIdentityExposeResponsePayload(payload []byte, state codexIdentityConfuseState) []byte {
|
||||
return replaceCodexIdentityResponsePayload(payload, state.promptCacheKey, state.originalPromptCacheKey)
|
||||
payload = replaceCodexIdentityResponsePayload(payload, state.promptCacheKey, state.originalPromptCacheKey)
|
||||
for _, turnID := range state.turnIDs {
|
||||
payload = replaceCodexIdentityResponsePayload(payload, turnID.confused, turnID.original)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func (state *codexIdentityConfuseState) confuseTurnID(turnID string) string {
|
||||
turnID = strings.TrimSpace(turnID)
|
||||
if state == nil || !state.enabled || strings.TrimSpace(state.authID) == "" || turnID == "" {
|
||||
return turnID
|
||||
}
|
||||
for _, replacement := range state.turnIDs {
|
||||
if replacement.original == turnID || replacement.confused == turnID {
|
||||
return replacement.confused
|
||||
}
|
||||
}
|
||||
confusedTurnID := codexIdentityConfuseUUID(state.authID, "turn", turnID)
|
||||
state.turnIDs = append(state.turnIDs, codexIdentityReplacement{original: turnID, confused: confusedTurnID})
|
||||
return confusedTurnID
|
||||
}
|
||||
|
||||
func replaceCodexIdentityResponsePayload(payload []byte, from string, to string) []byte {
|
||||
@@ -1044,18 +1090,19 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, s
|
||||
} else if !isAPIKey {
|
||||
r.Header.Set("Originator", codexOriginator)
|
||||
}
|
||||
if !isAPIKey {
|
||||
if auth != nil && auth.Metadata != nil {
|
||||
if accountID, ok := auth.Metadata["account_id"].(string); ok {
|
||||
r.Header.Set("Chatgpt-Account-Id", accountID)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if !isAPIKey {
|
||||
// if auth != nil && auth.Metadata != nil {
|
||||
// if accountID, ok := auth.Metadata["account_id"].(string); ok {
|
||||
// r.Header.Set("Chatgpt-Account-Id", accountID)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
var attrs map[string]string
|
||||
if auth != nil {
|
||||
attrs = auth.Attributes
|
||||
}
|
||||
util.ApplyCustomHeadersFromAttrs(r, attrs)
|
||||
deleteDeprecatedCodexConversationHeader(r.Header)
|
||||
}
|
||||
|
||||
func newCodexStatusErr(statusCode int, body []byte) statusErr {
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing
|
||||
recorder := httptest.NewRecorder()
|
||||
ginCtx, _ := gin.CreateTestContext(recorder)
|
||||
ginCtx.Request = httptest.NewRequest("POST", "/v1/responses", nil)
|
||||
ginCtx.Request.Header.Set("X-Codex-Turn-Metadata", `{"prompt_cache_key":"cache-1","turn_id":"turn-1"}`)
|
||||
ginCtx.Request.Header.Set("X-Codex-Turn-Metadata", `{"prompt_cache_key":"cache-1","turn_id":"turn-1","window_id":"cache-1:0"}`)
|
||||
ginCtx.Request.Header.Set("X-Client-Request-Id", "client-request-1")
|
||||
|
||||
ctx := context.WithValue(context.Background(), "gin", ginCtx)
|
||||
@@ -78,7 +78,7 @@ func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing
|
||||
Codex: config.CodexConfig{IdentityConfuse: true},
|
||||
}}
|
||||
auth := &cliproxyauth.Auth{ID: "auth-1", Provider: "codex"}
|
||||
rawJSON := []byte(`{"model":"gpt-5-codex","stream":true,"client_metadata":{"x-codex-turn-metadata":"{\"prompt_cache_key\":\"cache-1\",\"turn_id\":\"turn-1\"}","x-codex-window-id":"cache-1:0"}}`)
|
||||
rawJSON := []byte(`{"model":"gpt-5-codex","stream":true,"client_metadata":{"x-codex-turn-metadata":"{\"prompt_cache_key\":\"cache-1\",\"turn_id\":\"turn-1\",\"window_id\":\"cache-1:0\"}","x-codex-window-id":"cache-1:0"}}`)
|
||||
req := cliproxyexecutor.Request{
|
||||
Model: "gpt-5-codex",
|
||||
Payload: []byte(`{"model":"gpt-5-codex","prompt_cache_key":"cache-1","client_metadata":{"x-codex-installation-id":"install-1"}}`),
|
||||
@@ -90,9 +90,10 @@ func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing
|
||||
t.Fatalf("cacheHelper error: %v", err)
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, "oauth-token", true, executor.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
|
||||
expectedPromptCacheKey := codexIdentityConfuseUUID("auth-1", "prompt-cache", "cache-1")
|
||||
expectedTurnID := codexIdentityConfuseUUID("auth-1", "turn", "turn-1")
|
||||
if gotKey := gjson.GetBytes(body, "prompt_cache_key").String(); gotKey != expectedPromptCacheKey {
|
||||
t.Fatalf("prompt_cache_key = %q, want %q", gotKey, expectedPromptCacheKey)
|
||||
}
|
||||
@@ -100,8 +101,15 @@ func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing
|
||||
if gotID := gjson.GetBytes(body, "client_metadata.x-codex-installation-id").String(); gotID != expectedInstallationID {
|
||||
t.Fatalf("installation id = %q, want %q", gotID, expectedInstallationID)
|
||||
}
|
||||
if gotMetadata := gjson.GetBytes(body, "client_metadata.x-codex-turn-metadata").String(); gotMetadata != `{"prompt_cache_key":"`+expectedPromptCacheKey+`","turn_id":"turn-1"}` {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata = %s", gotMetadata)
|
||||
gotBodyMetadata := gjson.GetBytes(body, "client_metadata.x-codex-turn-metadata").String()
|
||||
if gotMetadataPromptCacheKey := gjson.Get(gotBodyMetadata, "prompt_cache_key").String(); gotMetadataPromptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.prompt_cache_key = %q, want %q", gotMetadataPromptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotMetadataTurnID := gjson.Get(gotBodyMetadata, "turn_id").String(); gotMetadataTurnID != expectedTurnID {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.turn_id = %q, want %q", gotMetadataTurnID, expectedTurnID)
|
||||
}
|
||||
if gotMetadataWindowID := gjson.Get(gotBodyMetadata, "window_id").String(); gotMetadataWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.window_id = %q, want %q", gotMetadataWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
if gotWindowID := gjson.GetBytes(body, "client_metadata.x-codex-window-id").String(); gotWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("client_metadata.x-codex-window-id = %q, want %q", gotWindowID, expectedPromptCacheKey+":0")
|
||||
@@ -117,8 +125,15 @@ func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing
|
||||
if gotWindow := httpReq.Header.Get("X-Codex-Window-Id"); gotWindow != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Window-Id = %q, want %q", gotWindow, expectedPromptCacheKey+":0")
|
||||
}
|
||||
if gotMetadata := httpReq.Header.Get("X-Codex-Turn-Metadata"); gotMetadata != `{"prompt_cache_key":"`+expectedPromptCacheKey+`","turn_id":"turn-1"}` {
|
||||
t.Fatalf("X-Codex-Turn-Metadata = %s", gotMetadata)
|
||||
gotHeaderMetadata := httpReq.Header.Get("X-Codex-Turn-Metadata")
|
||||
if gotMetadataPromptCacheKey := gjson.Get(gotHeaderMetadata, "prompt_cache_key").String(); gotMetadataPromptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.prompt_cache_key = %q, want %q", gotMetadataPromptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotMetadataTurnID := gjson.Get(gotHeaderMetadata, "turn_id").String(); gotMetadataTurnID != expectedTurnID {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.turn_id = %q, want %q", gotMetadataTurnID, expectedTurnID)
|
||||
}
|
||||
if gotMetadataWindowID := gjson.Get(gotHeaderMetadata, "window_id").String(); gotMetadataWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.window_id = %q, want %q", gotMetadataWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ func (e *CodexExecutor) executeOpenAIImage(ctx context.Context, auth *cliproxyau
|
||||
return resp, errCache
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
|
||||
|
||||
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||
@@ -198,7 +198,7 @@ func (e *CodexExecutor) executeOpenAIImageStream(ctx context.Context, auth *clip
|
||||
return nil, errCache
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, identityState)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
|
||||
|
||||
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||
|
||||
@@ -224,12 +224,9 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
|
||||
clientBody := body
|
||||
var identityState codexIdentityConfuseState
|
||||
upstreamBody, identityState := applyCodexIdentityConfuseBody(e.cfg, auth, originalPayloadSource, body)
|
||||
if identityState.promptCacheKey != "" {
|
||||
wsHeaders.Set("Conversation_id", identityState.promptCacheKey)
|
||||
}
|
||||
reporter.SetTranslatedReasoningEffort(clientBody, to.String())
|
||||
wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(wsHeaders, identityState)
|
||||
applyCodexIdentityConfuseHeaders(wsHeaders, &identityState)
|
||||
|
||||
var authID, authLabel, authType, authValue string
|
||||
if auth != nil {
|
||||
@@ -442,12 +439,9 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
|
||||
clientBody := body
|
||||
var identityState codexIdentityConfuseState
|
||||
upstreamBody, identityState := applyCodexIdentityConfuseBody(e.cfg, auth, userPayload, body)
|
||||
if identityState.promptCacheKey != "" {
|
||||
wsHeaders.Set("Conversation_id", identityState.promptCacheKey)
|
||||
}
|
||||
reporter.SetTranslatedReasoningEffort(clientBody, to.String())
|
||||
wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg)
|
||||
applyCodexIdentityConfuseHeaders(wsHeaders, identityState)
|
||||
applyCodexIdentityConfuseHeaders(wsHeaders, &identityState)
|
||||
|
||||
var authID, authLabel, authType, authValue string
|
||||
authID = auth.ID
|
||||
@@ -863,7 +857,6 @@ func applyCodexPromptCacheHeaders(from sdktranslator.Format, req cliproxyexecuto
|
||||
|
||||
if cache.ID != "" {
|
||||
rawJSON, _ = sjson.SetBytes(rawJSON, "prompt_cache_key", cache.ID)
|
||||
headers.Set("Conversation_id", cache.ID)
|
||||
}
|
||||
|
||||
return rawJSON, headers
|
||||
@@ -909,21 +902,22 @@ func applyCodexWebsocketHeaders(ctx context.Context, headers http.Header, auth *
|
||||
} else if !isAPIKey {
|
||||
headers.Set("Originator", codexOriginator)
|
||||
}
|
||||
if !isAPIKey {
|
||||
if auth != nil && auth.Metadata != nil {
|
||||
if accountID, ok := auth.Metadata["account_id"].(string); ok {
|
||||
if trimmed := strings.TrimSpace(accountID); trimmed != "" {
|
||||
setHeaderCasePreserved(headers, "ChatGPT-Account-ID", trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if !isAPIKey {
|
||||
// if auth != nil && auth.Metadata != nil {
|
||||
// if accountID, ok := auth.Metadata["account_id"].(string); ok {
|
||||
// if trimmed := strings.TrimSpace(accountID); trimmed != "" {
|
||||
// setHeaderCasePreserved(headers, "ChatGPT-Account-ID", trimmed)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
var attrs map[string]string
|
||||
if auth != nil {
|
||||
attrs = auth.Attributes
|
||||
}
|
||||
util.ApplyCustomHeadersFromAttrs(&http.Request{Header: headers}, attrs)
|
||||
deleteDeprecatedCodexConversationHeader(headers)
|
||||
|
||||
return headers
|
||||
}
|
||||
@@ -999,6 +993,10 @@ func deleteHeaderCaseInsensitive(headers http.Header, key string) {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteDeprecatedCodexConversationHeader(headers http.Header) {
|
||||
deleteHeaderCaseInsensitive(headers, "Conversation_id")
|
||||
}
|
||||
|
||||
func codexHeaderDefaults(cfg *config.Config, auth *cliproxyauth.Auth) (string, string) {
|
||||
if cfg == nil || auth == nil {
|
||||
return "", ""
|
||||
|
||||
@@ -341,7 +341,7 @@ func TestApplyCodexWebsocketHeadersPreservesExplicitAPIKeyUserAgent(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCodexPromptCacheHeadersSetsLegacyConversationOnly(t *testing.T) {
|
||||
func TestApplyCodexPromptCacheHeadersDoesNotSetDeprecatedConversationHeader(t *testing.T) {
|
||||
req := cliproxyexecutor.Request{Model: "gpt-5-codex", Payload: []byte(`{"prompt_cache_key":"cache-1"}`)}
|
||||
|
||||
_, headers := applyCodexPromptCacheHeaders("openai-response", req, []byte(`{"model":"gpt-5-codex"}`))
|
||||
@@ -349,8 +349,8 @@ func TestApplyCodexPromptCacheHeadersSetsLegacyConversationOnly(t *testing.T) {
|
||||
if got := headerValueCaseInsensitive(headers, "session_id"); got != "" {
|
||||
t.Fatalf("session_id = %q, want empty", got)
|
||||
}
|
||||
if got := headers.Get("Conversation_id"); got != "cache-1" {
|
||||
t.Fatalf("Conversation_id = %s, want cache-1", got)
|
||||
if got := headers.Get("Conversation_id"); got != "" {
|
||||
t.Fatalf("Conversation_id = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,17 +367,15 @@ func TestApplyCodexWebsocketHeadersIdentityConfuseRemapsPromptCacheKey(t *testin
|
||||
|
||||
body, headers := applyCodexPromptCacheHeaders("openai-response", req, []byte(`{"model":"gpt-5-codex"}`))
|
||||
body, identityState := applyCodexIdentityConfuseBody(cfg, auth, req.Payload, body)
|
||||
if identityState.promptCacheKey != "" {
|
||||
headers.Set("Conversation_id", identityState.promptCacheKey)
|
||||
}
|
||||
ctx := contextWithGinHeaders(map[string]string{
|
||||
"X-Codex-Turn-Metadata": `{"prompt_cache_key":"cache-ws-1"}`,
|
||||
"X-Codex-Turn-Metadata": `{"prompt_cache_key":"cache-ws-1","turn_id":"turn-ws-1","window_id":"cache-ws-1:0"}`,
|
||||
"X-Client-Request-Id": "client-request-1",
|
||||
})
|
||||
headers = applyCodexWebsocketHeaders(ctx, headers, auth, "oauth-token", cfg)
|
||||
applyCodexIdentityConfuseHeaders(headers, identityState)
|
||||
applyCodexIdentityConfuseHeaders(headers, &identityState)
|
||||
|
||||
expectedPromptCacheKey := codexIdentityConfuseUUID("auth-ws-1", "prompt-cache", "cache-ws-1")
|
||||
expectedTurnID := codexIdentityConfuseUUID("auth-ws-1", "turn", "turn-ws-1")
|
||||
if gotKey := gjson.GetBytes(body, "prompt_cache_key").String(); gotKey != expectedPromptCacheKey {
|
||||
t.Fatalf("prompt_cache_key = %q, want %q", gotKey, expectedPromptCacheKey)
|
||||
}
|
||||
@@ -390,11 +388,21 @@ func TestApplyCodexWebsocketHeadersIdentityConfuseRemapsPromptCacheKey(t *testin
|
||||
if gotThreadID := headers.Get("Thread-Id"); gotThreadID != expectedPromptCacheKey {
|
||||
t.Fatalf("Thread-Id = %q, want %q", gotThreadID, expectedPromptCacheKey)
|
||||
}
|
||||
if gotConversation := headers.Get("Conversation_id"); gotConversation != "" {
|
||||
t.Fatalf("Conversation_id = %q, want empty", gotConversation)
|
||||
}
|
||||
if gotWindowID := headers.Get("X-Codex-Window-Id"); gotWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Window-Id = %q, want %q", gotWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
if gotMetadata := headers.Get("X-Codex-Turn-Metadata"); gotMetadata != `{"prompt_cache_key":"`+expectedPromptCacheKey+`"}` {
|
||||
t.Fatalf("X-Codex-Turn-Metadata = %s", gotMetadata)
|
||||
gotMetadata := headers.Get("X-Codex-Turn-Metadata")
|
||||
if gotMetadataPromptCacheKey := gjson.Get(gotMetadata, "prompt_cache_key").String(); gotMetadataPromptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.prompt_cache_key = %q, want %q", gotMetadataPromptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotMetadataTurnID := gjson.Get(gotMetadata, "turn_id").String(); gotMetadataTurnID != expectedTurnID {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.turn_id = %q, want %q", gotMetadataTurnID, expectedTurnID)
|
||||
}
|
||||
if gotMetadataWindowID := gjson.Get(gotMetadata, "window_id").String(); gotMetadataWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.window_id = %q, want %q", gotMetadataWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
expectedInstallationID := codexIdentityConfuseUUID("auth-ws-1", "installation", "install-ws-1")
|
||||
if gotInstallationID := gjson.GetBytes(body, "client_metadata.x-codex-installation-id").String(); gotInstallationID != expectedInstallationID {
|
||||
@@ -404,52 +412,56 @@ func TestApplyCodexWebsocketHeadersIdentityConfuseRemapsPromptCacheKey(t *testin
|
||||
|
||||
func TestCodexIdentityConfuseResponsePayloadHidesUpstreamAndRestoresClient(t *testing.T) {
|
||||
state := codexIdentityConfuseState{
|
||||
enabled: true,
|
||||
authID: "auth-ws-1",
|
||||
originalPromptCacheKey: "cache-ws-1",
|
||||
promptCacheKey: codexIdentityConfuseUUID("auth-ws-1", "prompt-cache", "cache-ws-1"),
|
||||
}
|
||||
rawPayload := []byte(`{"type":"response.completed","response":{"prompt_cache_key":"cache-ws-1"},"prompt_cache_key":"cache-ws-1"}`)
|
||||
expectedTurnID := state.confuseTurnID("turn-ws-1")
|
||||
rawPayload := []byte(`{"type":"response.completed","response":{"prompt_cache_key":"cache-ws-1","turn_id":"turn-ws-1"},"prompt_cache_key":"cache-ws-1","turn_id":"turn-ws-1"}`)
|
||||
|
||||
upstreamPayload := applyCodexIdentityConfuseResponsePayload(rawPayload, state)
|
||||
if bytes.Contains(upstreamPayload, []byte(`cache-ws-1`)) {
|
||||
t.Fatalf("upstream payload still contains original prompt_cache_key: %s", string(upstreamPayload))
|
||||
}
|
||||
if bytes.Contains(upstreamPayload, []byte(`turn-ws-1`)) {
|
||||
t.Fatalf("upstream payload still contains original turn_id: %s", string(upstreamPayload))
|
||||
}
|
||||
if !bytes.Contains(upstreamPayload, []byte(state.promptCacheKey)) {
|
||||
t.Fatalf("upstream payload missing confused prompt_cache_key: %s", string(upstreamPayload))
|
||||
}
|
||||
if !bytes.Contains(upstreamPayload, []byte(expectedTurnID)) {
|
||||
t.Fatalf("upstream payload missing confused turn_id: %s", string(upstreamPayload))
|
||||
}
|
||||
|
||||
clientPayload := applyCodexIdentityExposeResponsePayload(upstreamPayload, state)
|
||||
if bytes.Contains(clientPayload, []byte(state.promptCacheKey)) {
|
||||
t.Fatalf("client payload still contains confused prompt_cache_key: %s", string(clientPayload))
|
||||
}
|
||||
if bytes.Contains(clientPayload, []byte(expectedTurnID)) {
|
||||
t.Fatalf("client payload still contains confused turn_id: %s", string(clientPayload))
|
||||
}
|
||||
if !bytes.Contains(clientPayload, []byte(`cache-ws-1`)) {
|
||||
t.Fatalf("client payload missing original prompt_cache_key: %s", string(clientPayload))
|
||||
}
|
||||
if !bytes.Contains(clientPayload, []byte(`turn-ws-1`)) {
|
||||
t.Fatalf("client payload missing original turn_id: %s", string(clientPayload))
|
||||
}
|
||||
|
||||
rawSSE := []byte(`data: {"type":"response.completed","response":{"prompt_cache_key":"cache-ws-1"}}`)
|
||||
rawSSE := []byte(`data: {"type":"response.completed","response":{"prompt_cache_key":"cache-ws-1","turn_id":"turn-ws-1"}}`)
|
||||
upstreamSSE := applyCodexIdentityConfuseResponsePayload(rawSSE, state)
|
||||
if bytes.Contains(upstreamSSE, []byte(`cache-ws-1`)) {
|
||||
t.Fatalf("upstream SSE still contains original prompt_cache_key: %s", string(upstreamSSE))
|
||||
}
|
||||
if bytes.Contains(upstreamSSE, []byte(`turn-ws-1`)) {
|
||||
t.Fatalf("upstream SSE still contains original turn_id: %s", string(upstreamSSE))
|
||||
}
|
||||
clientSSE := applyCodexIdentityExposeResponsePayload(upstreamSSE, state)
|
||||
if !bytes.Contains(clientSSE, []byte(`cache-ws-1`)) || bytes.Contains(clientSSE, []byte(state.promptCacheKey)) {
|
||||
t.Fatalf("client SSE prompt_cache_key was not restored: %s", string(clientSSE))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCodexWebsocketHeadersUsesCanonicalAccountHeader(t *testing.T) {
|
||||
auth := &cliproxyauth.Auth{Provider: "codex", Metadata: map[string]any{"account_id": "acct-1"}}
|
||||
|
||||
headers := applyCodexWebsocketHeaders(context.Background(), http.Header{}, auth, "", nil)
|
||||
|
||||
if got := headerValueCaseInsensitive(headers, "ChatGPT-Account-ID"); got != "acct-1" {
|
||||
t.Fatalf("ChatGPT-Account-ID = %s, want acct-1", got)
|
||||
}
|
||||
values, ok := headers["ChatGPT-Account-ID"]
|
||||
if !ok {
|
||||
t.Fatalf("expected exact ChatGPT-Account-ID key, got %#v", headers)
|
||||
}
|
||||
if len(values) != 1 || values[0] != "acct-1" {
|
||||
t.Fatalf("ChatGPT-Account-ID values = %#v, want [acct-1]", values)
|
||||
if !bytes.Contains(clientSSE, []byte(`turn-ws-1`)) || bytes.Contains(clientSSE, []byte(expectedTurnID)) {
|
||||
t.Fatalf("client SSE turn_id was not restored: %s", string(clientSSE))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user