@@ -298,11 +298,13 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
298298 reporter .SetTranslatedReasoningEffort (body , to .String ())
299299
300300 url := strings .TrimSuffix (baseURL , "/" ) + "/responses"
301- httpReq , err := e .cacheHelper (ctx , from , url , req , body )
301+ var identityState codexIdentityConfuseState
302+ httpReq , upstreamBody , identityState , err := e .cacheHelper (ctx , from , url , auth , req , originalPayloadSource , body )
302303 if err != nil {
303304 return resp , err
304305 }
305306 applyCodexHeaders (httpReq , auth , apiKey , true , e .cfg )
307+ applyCodexIdentityConfuseHeaders (httpReq .Header , identityState )
306308 var authID , authLabel , authType , authValue string
307309 if auth != nil {
308310 authID = auth .ID
@@ -313,7 +315,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
313315 URL : url ,
314316 Method : http .MethodPost ,
315317 Headers : httpReq .Header .Clone (),
316- Body : body ,
318+ Body : upstreamBody ,
317319 Provider : e .Identifier (),
318320 AuthID : authID ,
319321 AuthLabel : authLabel ,
@@ -335,6 +337,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
335337 helps .RecordAPIResponseMetadata (ctx , e .cfg , httpResp .StatusCode , httpResp .Header .Clone ())
336338 if httpResp .StatusCode < 200 || httpResp .StatusCode >= 300 {
337339 b , _ := io .ReadAll (httpResp .Body )
340+ b = applyCodexIdentityConfuseResponsePayload (b , identityState )
338341 helps .AppendAPIResponseChunk (ctx , e .cfg , b )
339342 helps .LogWithRequestID (ctx ).Debugf ("request error, error status: %d, error message: %s" , httpResp .StatusCode , helps .SummarizeErrorBody (httpResp .Header .Get ("Content-Type" ), b ))
340343 err = newCodexStatusErr (httpResp .StatusCode , b )
@@ -345,9 +348,10 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
345348 helps .RecordAPIResponseError (ctx , e .cfg , err )
346349 return resp , err
347350 }
348- helps .AppendAPIResponseChunk (ctx , e .cfg , data )
351+ upstreamData := applyCodexIdentityConfuseResponsePayload (data , identityState )
352+ helps .AppendAPIResponseChunk (ctx , e .cfg , upstreamData )
349353
350- lines := bytes .Split (data , []byte ("\n " ))
354+ lines := bytes .Split (upstreamData , []byte ("\n " ))
351355 outputItemsByIndex := make (map [int64 ][]byte )
352356 var outputItemsFallback [][]byte
353357 for _ , line := range lines {
@@ -410,7 +414,8 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
410414 }
411415
412416 var param any
413- out := sdktranslator .TranslateNonStream (ctx , to , from , req .Model , originalPayload , body , completedData , & param )
417+ clientCompletedData := applyCodexIdentityExposeResponsePayload (completedData , identityState )
418+ out := sdktranslator .TranslateNonStream (ctx , to , from , req .Model , originalPayload , body , clientCompletedData , & param )
414419 resp = cliproxyexecutor.Response {Payload : out , Headers : httpResp .Header .Clone ()}
415420 return resp , nil
416421 }
@@ -456,11 +461,13 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
456461 reporter .SetTranslatedReasoningEffort (body , to .String ())
457462
458463 url := strings .TrimSuffix (baseURL , "/" ) + "/responses/compact"
459- httpReq , err := e .cacheHelper (ctx , from , url , req , body )
464+ var identityState codexIdentityConfuseState
465+ httpReq , upstreamBody , identityState , err := e .cacheHelper (ctx , from , url , auth , req , originalPayloadSource , body )
460466 if err != nil {
461467 return resp , err
462468 }
463469 applyCodexHeaders (httpReq , auth , apiKey , false , e .cfg )
470+ applyCodexIdentityConfuseHeaders (httpReq .Header , identityState )
464471 var authID , authLabel , authType , authValue string
465472 if auth != nil {
466473 authID = auth .ID
@@ -471,7 +478,7 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
471478 URL : url ,
472479 Method : http .MethodPost ,
473480 Headers : httpReq .Header .Clone (),
474- Body : body ,
481+ Body : upstreamBody ,
475482 Provider : e .Identifier (),
476483 AuthID : authID ,
477484 AuthLabel : authLabel ,
@@ -493,6 +500,7 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
493500 helps .RecordAPIResponseMetadata (ctx , e .cfg , httpResp .StatusCode , httpResp .Header .Clone ())
494501 if httpResp .StatusCode < 200 || httpResp .StatusCode >= 300 {
495502 b , _ := io .ReadAll (httpResp .Body )
503+ b = applyCodexIdentityConfuseResponsePayload (b , identityState )
496504 helps .AppendAPIResponseChunk (ctx , e .cfg , b )
497505 helps .LogWithRequestID (ctx ).Debugf ("request error, error status: %d, error message: %s" , httpResp .StatusCode , helps .SummarizeErrorBody (httpResp .Header .Get ("Content-Type" ), b ))
498506 err = newCodexStatusErr (httpResp .StatusCode , b )
@@ -503,11 +511,13 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
503511 helps .RecordAPIResponseError (ctx , e .cfg , err )
504512 return resp , err
505513 }
506- helps .AppendAPIResponseChunk (ctx , e .cfg , data )
507- reporter .Publish (ctx , helps .ParseOpenAIUsage (data ))
514+ upstreamData := applyCodexIdentityConfuseResponsePayload (data , identityState )
515+ helps .AppendAPIResponseChunk (ctx , e .cfg , upstreamData )
516+ reporter .Publish (ctx , helps .ParseOpenAIUsage (upstreamData ))
508517 reporter .EnsurePublished (ctx )
509518 var param any
510- out := sdktranslator .TranslateNonStream (ctx , to , from , req .Model , originalPayload , body , data , & param )
519+ clientData := applyCodexIdentityExposeResponsePayload (upstreamData , identityState )
520+ out := sdktranslator .TranslateNonStream (ctx , to , from , req .Model , originalPayload , body , clientData , & param )
511521 resp = cliproxyexecutor.Response {Payload : out , Headers : httpResp .Header .Clone ()}
512522 return resp , nil
513523}
@@ -559,11 +569,13 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
559569 reporter .SetTranslatedReasoningEffort (body , to .String ())
560570
561571 url := strings .TrimSuffix (baseURL , "/" ) + "/responses"
562- httpReq , err := e .cacheHelper (ctx , from , url , req , body )
572+ var identityState codexIdentityConfuseState
573+ httpReq , upstreamBody , identityState , err := e .cacheHelper (ctx , from , url , auth , req , originalPayloadSource , body )
563574 if err != nil {
564575 return nil , err
565576 }
566577 applyCodexHeaders (httpReq , auth , apiKey , true , e .cfg )
578+ applyCodexIdentityConfuseHeaders (httpReq .Header , identityState )
567579 var authID , authLabel , authType , authValue string
568580 if auth != nil {
569581 authID = auth .ID
@@ -574,7 +586,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
574586 URL : url ,
575587 Method : http .MethodPost ,
576588 Headers : httpReq .Header .Clone (),
577- Body : body ,
589+ Body : upstreamBody ,
578590 Provider : e .Identifier (),
579591 AuthID : authID ,
580592 AuthLabel : authLabel ,
@@ -599,6 +611,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
599611 helps .RecordAPIResponseError (ctx , e .cfg , readErr )
600612 return nil , readErr
601613 }
614+ data = applyCodexIdentityConfuseResponsePayload (data , identityState )
602615 helps .AppendAPIResponseChunk (ctx , e .cfg , data )
603616 helps .LogWithRequestID (ctx ).Debugf ("request error, error status: %d, error message: %s" , httpResp .StatusCode , helps .SummarizeErrorBody (httpResp .Header .Get ("Content-Type" ), data ))
604617 err = newCodexStatusErr (httpResp .StatusCode , data )
@@ -618,7 +631,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
618631 outputItemsByIndex := make (map [int64 ][]byte )
619632 var outputItemsFallback [][]byte
620633 for scanner .Scan () {
621- line := scanner .Bytes ()
634+ line := applyCodexIdentityConfuseResponsePayload ( scanner .Bytes (), identityState )
622635 helps .AppendAPIResponseChunk (ctx , e .cfg , line )
623636 translatedLine := bytes .Clone (line )
624637
@@ -646,6 +659,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
646659 }
647660 }
648661
662+ translatedLine = applyCodexIdentityExposeResponsePayload (translatedLine , identityState )
649663 chunks := sdktranslator .TranslateStream (ctx , to , from , req .Model , originalPayload , body , translatedLine , & param )
650664 for i := range chunks {
651665 select {
@@ -866,7 +880,12 @@ func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*
866880 return auth , nil
867881}
868882
869- func (e * CodexExecutor ) cacheHelper (ctx context.Context , from sdktranslator.Format , url string , req cliproxyexecutor.Request , rawJSON []byte ) (* http.Request , error ) {
883+ type codexIdentityConfuseState struct {
884+ originalPromptCacheKey string
885+ promptCacheKey string
886+ }
887+
888+ 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 ) {
870889 var cache helps.CodexCache
871890 if from == "claude" {
872891 userIDResult := gjson .GetBytes (req .Payload , "metadata.user_id" )
@@ -895,14 +914,98 @@ func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Form
895914 if cache .ID != "" {
896915 rawJSON , _ = sjson .SetBytes (rawJSON , "prompt_cache_key" , cache .ID )
897916 }
917+ var identityState codexIdentityConfuseState
918+ rawJSON , identityState = applyCodexIdentityConfuseBody (e .cfg , auth , userPayload , rawJSON )
919+ if identityState .promptCacheKey != "" {
920+ cache .ID = identityState .promptCacheKey
921+ }
898922 httpReq , err := http .NewRequestWithContext (ctx , http .MethodPost , url , bytes .NewReader (rawJSON ))
899923 if err != nil {
900- return nil , err
924+ return nil , nil , codexIdentityConfuseState {}, err
901925 }
902- if cache .ID != "" {
903- httpReq .Header .Set ("Session_id" , cache .ID )
926+ return httpReq , rawJSON , identityState , nil
927+ }
928+
929+ func applyCodexIdentityConfuseBody (cfg * config.Config , auth * cliproxyauth.Auth , userPayload []byte , rawJSON []byte ) ([]byte , codexIdentityConfuseState ) {
930+ if ! codexIdentityConfuseEnabled (cfg ) || auth == nil || strings .TrimSpace (auth .ID ) == "" || len (rawJSON ) == 0 {
931+ return rawJSON , codexIdentityConfuseState {}
932+ }
933+
934+ state := codexIdentityConfuseState {}
935+ if promptCacheKey := strings .TrimSpace (gjson .GetBytes (userPayload , "prompt_cache_key" ).String ()); promptCacheKey != "" {
936+ state .originalPromptCacheKey = promptCacheKey
937+ state .promptCacheKey = codexIdentityConfuseUUID (auth .ID , "prompt-cache" , promptCacheKey )
938+ rawJSON , _ = sjson .SetBytes (rawJSON , "prompt_cache_key" , state .promptCacheKey )
939+ }
940+ if installationID := strings .TrimSpace (gjson .GetBytes (userPayload , "client_metadata.x-codex-installation-id" ).String ()); installationID != "" {
941+ rawJSON , _ = sjson .SetBytes (rawJSON , "client_metadata.x-codex-installation-id" , codexIdentityConfuseUUID (auth .ID , "installation" , installationID ))
904942 }
905- return httpReq , nil
943+ if state .promptCacheKey != "" {
944+ if turnMetadata := strings .TrimSpace (gjson .GetBytes (rawJSON , "client_metadata.x-codex-turn-metadata" ).String ()); turnMetadata != "" {
945+ rawJSON , _ = sjson .SetBytes (rawJSON , "client_metadata.x-codex-turn-metadata" , applyCodexTurnMetadataIdentityConfuse (turnMetadata , state ))
946+ }
947+ if windowID := strings .TrimSpace (gjson .GetBytes (rawJSON , "client_metadata.x-codex-window-id" ).String ()); windowID != "" {
948+ rawJSON , _ = sjson .SetBytes (rawJSON , "client_metadata.x-codex-window-id" , state .promptCacheKey + ":0" )
949+ }
950+ }
951+
952+ return rawJSON , state
953+ }
954+
955+ func applyCodexIdentityConfuseHeaders (headers http.Header , state codexIdentityConfuseState ) {
956+ if headers == nil || state .promptCacheKey == "" {
957+ return
958+ }
959+
960+ setHeaderCasePreserved (headers , "Session-Id" , state .promptCacheKey )
961+ headers .Set ("Conversation_id" , state .promptCacheKey )
962+ headers .Set ("X-Client-Request-Id" , state .promptCacheKey )
963+ headers .Set ("Thread-Id" , state .promptCacheKey )
964+ headers .Set ("X-Codex-Window-Id" , state .promptCacheKey + ":0" )
965+
966+ if rawTurnMetadata := strings .TrimSpace (headers .Get ("X-Codex-Turn-Metadata" )); rawTurnMetadata != "" {
967+ headers .Set ("X-Codex-Turn-Metadata" , applyCodexTurnMetadataIdentityConfuse (rawTurnMetadata , state ))
968+ }
969+ }
970+
971+ func applyCodexTurnMetadataIdentityConfuse (rawTurnMetadata string , state codexIdentityConfuseState ) string {
972+ updatedTurnMetadata := rawTurnMetadata
973+ if gjson .Get (rawTurnMetadata , "prompt_cache_key" ).Exists () {
974+ updatedTurnMetadata , _ = sjson .Set (updatedTurnMetadata , "prompt_cache_key" , state .promptCacheKey )
975+ } else if state .originalPromptCacheKey != "" {
976+ updatedTurnMetadata = strings .ReplaceAll (updatedTurnMetadata , state .originalPromptCacheKey , state .promptCacheKey )
977+ }
978+ return updatedTurnMetadata
979+ }
980+
981+ func applyCodexIdentityConfuseResponsePayload (payload []byte , state codexIdentityConfuseState ) []byte {
982+ return replaceCodexIdentityResponsePayload (payload , state .originalPromptCacheKey , state .promptCacheKey )
983+ }
984+
985+ func applyCodexIdentityExposeResponsePayload (payload []byte , state codexIdentityConfuseState ) []byte {
986+ return replaceCodexIdentityResponsePayload (payload , state .promptCacheKey , state .originalPromptCacheKey )
987+ }
988+
989+ func replaceCodexIdentityResponsePayload (payload []byte , from string , to string ) []byte {
990+ from = strings .TrimSpace (from )
991+ to = strings .TrimSpace (to )
992+ if len (payload ) == 0 || from == "" || to == "" || from == to || ! bytes .Contains (payload , []byte (from )) {
993+ return payload
994+ }
995+ return bytes .ReplaceAll (payload , []byte (from ), []byte (to ))
996+ }
997+
998+ func codexIdentityConfuseEnabled (cfg * config.Config ) bool {
999+ if cfg == nil || ! cfg .Codex .IdentityConfuse {
1000+ return false
1001+ }
1002+ strategy := strings .ToLower (strings .TrimSpace (cfg .Routing .Strategy ))
1003+ return cfg .Routing .SessionAffinity || strategy == "fill-first" || strategy == "fillfirst" || strategy == "ff"
1004+ }
1005+
1006+ func codexIdentityConfuseUUID (authID string , kind string , value string ) string {
1007+ name := strings .Join ([]string {"cli-proxy-api" , "codex" , "identity-confuse" , kind , strings .TrimSpace (authID ), strings .TrimSpace (value )}, ":" )
1008+ return uuid .NewSHA1 (uuid .NameSpaceOID , []byte (name )).String ()
9061009}
9071010
9081011func applyCodexHeaders (r * http.Request , auth * cliproxyauth.Auth , token string , stream bool , cfg * config.Config ) {
@@ -923,10 +1026,6 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, s
9231026 cfgUserAgent , _ := codexHeaderDefaults (cfg , auth )
9241027 ensureHeaderWithConfigPrecedence (r .Header , ginHeaders , "User-Agent" , cfgUserAgent , codexUserAgent )
9251028
926- if strings .Contains (r .Header .Get ("User-Agent" ), "Mac OS" ) {
927- misc .EnsureHeader (r .Header , ginHeaders , "Session_id" , uuid .NewString ())
928- }
929-
9301029 if stream {
9311030 r .Header .Set ("Accept" , "text/event-stream" )
9321031 } else {
0 commit comments