feat(config): preserve unknown nested fields as comments during v8 migration

- Traverse nested configuration paths to comment out unrecognized fields during v8 migration
- Remove deprecated Codex `identity-confuse` configuration option and request handling
- Clean up identity confuse mapping logic from Codex HTTP and WebSocket executors
This commit is contained in:
Luis Pater
2026-09-28 10:46:32 +08:00
parent acdace936f
commit 48686ccc8f
18 changed files with 233 additions and 483 deletions

View File

@@ -836,11 +836,6 @@ oauth:
# Experimental full-duplex Codex Responses WebSocket steering (default: false).
# Keeps one upstream account/model per socket; accepted input is never replayed.
response-steering: false
# When true, and routing.strategy is fill-first or routing.session-affinity is true,
# remap Codex prompt_cache_key and installation identity per selected auth.
# Some superstitious users believe request tracking identifiers can be used
# as evidence for TOS enforcement bans; this option only satisfies those odd concerns.
identity-confuse: false
# Disable forcing the official Codex User-Agent and Originator headers on HTTP/SSE and WebSocket requests.
disable-codex-cloaking: false
# Hold back the frames that carry nothing the client has seen - the handshake (response.created,

View File

@@ -175,6 +175,62 @@ func TestConfigV8CommentsUnknownLegacySectionsOnWrite(t *testing.T) {
}
}
func TestConfigV8CommentsUnknownNestedFieldsOnWrite(t *testing.T) {
gin.SetMode(gin.TestMode)
path := filepath.Join(t.TempDir(), "config.yaml")
raw := "server: {port: 8317}\noauth: {providers: {codex: {disable-codex-cloaking: true, retired-setting: false}}}\n"
if err := os.WriteFile(path, []byte(raw), 0600); err != nil {
t.Fatal(err)
}
cfg, err := config.LoadConfig(path)
if err != nil {
t.Fatal(err)
}
h := &Handler{cfg: cfg, configFilePath: path}
router := gin.New()
router.GET("/v8/management/config", h.ConfigV8)
router.PATCH("/v8/management/config", h.ConfigV8)
router.PUT("/v8/management/config/*path", h.ConfigV8)
router.DELETE("/v8/management/config/*path", h.ConfigV8)
request := func(method, url, body string, status int) {
t.Helper()
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest(method, url, strings.NewReader(body)))
if recorder.Code != status {
t.Fatalf("%s %s: status=%d body=%s", method, url, recorder.Code, recorder.Body.String())
}
}
request(http.MethodGet, "/v8/management/config", "", http.StatusOK)
if saved, errRead := os.ReadFile(path); errRead != nil || string(saved) != raw {
t.Fatalf("GET changed existing config: %v", errRead)
}
request(http.MethodPatch, "/v8/management/config", `{"server":{"port":8318}}`, http.StatusOK)
saved, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if err = config.ValidateV8Config(saved); err != nil {
t.Fatalf("saved config is invalid: %v\n%s", err, saved)
}
if strings.Count(string(saved), "# oauth.providers.codex.retired-setting: false") != 1 {
t.Fatalf("existing unknown field was not preserved as a comment: %s", saved)
}
loaded, err := config.LoadConfig(path)
if err != nil || loaded.Port != 8318 || !loaded.Codex.DisableCodexCloaking {
t.Fatalf("unrelated write changed known settings: cfg=%+v error=%v", loaded, err)
}
request(http.MethodPut, "/v8/management/config/oauth/providers/codex/new-setting", `true`, http.StatusBadRequest)
unchanged, err := os.ReadFile(path)
if err != nil || string(unchanged) != string(saved) {
t.Fatalf("invalid new setting changed the config: %v", err)
}
request(http.MethodDelete, "/v8/management/config/oauth/providers/codex/disable-codex-cloaking", "", http.StatusOK)
saved, err = os.ReadFile(path)
if err != nil || strings.Count(string(saved), "# oauth.providers.codex.retired-setting: false") != 1 {
t.Fatalf("deleting the neighboring setting lost the archived comment: %v\n%s", err, saved)
}
}
func TestV8NestedWriteMigratesOnlyOnSuccess(t *testing.T) {
for _, tc := range []struct {
name, body string
@@ -319,7 +375,7 @@ func TestConfigV8DeleteLastField(t *testing.T) {
{"websocket", "ws-auth: false\n", "oauth/providers/aistudio/ws-auth", func(cfg *config.Config) bool { return cfg.WebsocketAuth }},
{"debug", "debug: true\n", "observability/logs/debug", func(cfg *config.Config) bool { return !cfg.Debug }},
{"sibling", "routing: {strategy: fill-first, retry: {request-retry: 3}}\n", "routing/retry/request-retry", func(cfg *config.Config) bool { return cfg.RequestRetry == 0 && cfg.Routing.Strategy == "fill-first" }},
{"provider", "oauth: {providers: {codex: {identity-confuse: true}}}\n", "oauth/providers/codex/identity-confuse", func(cfg *config.Config) bool { return !cfg.Codex.IdentityConfuse }},
{"provider", "oauth: {providers: {codex: {disable-codex-cloaking: true}}}\n", "oauth/providers/codex/disable-codex-cloaking", func(cfg *config.Config) bool { return !cfg.Codex.DisableCodexCloaking }},
{"excluded models", "oauth: {excluded-models: {codex: [blocked-model]}}\n", "oauth/excluded-models", func(cfg *config.Config) bool { return len(cfg.OAuthExcludedModels) == 0 }},
} {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -34,12 +34,11 @@ codex-header-defaults:
}
}
func TestLoadConfigOptional_CodexIdentityConfuse(t *testing.T) {
func TestLoadConfigOptional_CodexOptions(t *testing.T) {
dir := t.TempDir()
configPath := filepath.Join(dir, "config.yaml")
configYAML := []byte(`
codex:
identity-confuse: true
disable-codex-cloaking: true
optimize-multi-agent-v2: true
`)
@@ -52,9 +51,6 @@ codex:
t.Fatalf("LoadConfigOptional() error = %v", err)
}
if !cfg.Codex.IdentityConfuse {
t.Fatalf("IdentityConfuse = false, want true")
}
if !cfg.Codex.DisableCodexCloaking {
t.Fatal("DisableCodexCloaking = false, want true")
}

View File

@@ -180,7 +180,6 @@ type AntigravityConnectionPoolConfig struct {
// CodexConfig configures provider-wide Codex request behavior.
type CodexConfig struct {
IdentityConfuse bool `yaml:"identity-confuse" json:"identity-confuse"`
// DisableCodexCloaking disables forcing the official Codex identity headers on HTTP/SSE and WebSocket requests.
DisableCodexCloaking bool `yaml:"disable-codex-cloaking" json:"disable-codex-cloaking"`
// StreamBootstrapBuffering holds back the frames that arrive before generation starts, none of

View File

@@ -401,7 +401,7 @@ func NormalizeConfigLayout(data []byte, migrate bool) ([]byte, bool, error) {
changed = true
}
if migrate {
// Existing unknown sections are ignored by the runtime. Retain their
// Existing unknown fields are ignored by the runtime. Retain their
// contents as comments while keeping new v8 writes strictly validated.
if err := commentUnknownV8Sections(root); err != nil {
return nil, false, err
@@ -450,27 +450,104 @@ func warnUnrecognizedV8Section(section string) {
}
func commentUnknownV8Sections(root *yaml.Node) error {
allowed := v8AllowedRoots()
if err := commentUnknownV8Fields(root, root, v8AllowedRoots(), ""); err != nil {
return err
}
children := make(map[string]map[string]bool)
for _, path := range append(append([]configPath(nil), v8Paths...), v8StructPaths...) {
parts := strings.Split(path.current, ".")
for i := 1; i < len(parts); i++ {
parent := strings.Join(parts[:i], ".")
if children[parent] == nil {
children[parent] = make(map[string]bool)
}
children[parent][parts[i]] = true
}
}
// Some structs already live at their v8 path and therefore have no remap entry.
allowedRoots := v8AllowedRoots()
var includeNative func(reflect.Type, string)
includeNative = func(t reflect.Type, path string) {
if t.Kind() != reflect.Struct {
return
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := strings.Split(field.Tag.Get("yaml"), ",")[0]
if tag == "-" || field.PkgPath != "" {
continue
}
if field.Anonymous {
includeNative(field.Type, path)
continue
}
if path == "" && !allowedRoots[tag] {
continue
}
if children[path] == nil {
children[path] = make(map[string]bool)
}
children[path][tag] = true
child := tag
if path != "" {
child = path + "." + tag
}
includeNative(field.Type, child)
}
}
includeNative(reflect.TypeOf(Config{}), "")
var walk func(*yaml.Node, string) error
walk = func(node *yaml.Node, path string) error {
if node == nil || node.Kind != yaml.MappingNode || children[path] == nil {
return nil
}
if err := commentUnknownV8Fields(node, root, children[path], path); err != nil {
return err
}
for i := 0; i+1 < len(node.Content); i += 2 {
child := path + "." + node.Content[i].Value
if err := walk(node.Content[i+1], child); err != nil {
return err
}
}
return nil
}
for i := 0; i+1 < len(root.Content); i += 2 {
if err := walk(root.Content[i+1], root.Content[i].Value); err != nil {
return err
}
}
return nil
}
func commentUnknownV8Fields(node *yaml.Node, archive *yaml.Node, allowed map[string]bool, path string) error {
var comments []string
for i := 0; i+1 < len(root.Content); {
if allowed[root.Content[i].Value] {
for i := 0; i+1 < len(node.Content); {
key := node.Content[i].Value
if allowed[key] {
i += 2
continue
}
key := root.Content[i].Value
warnUnrecognizedV8Section(key)
entry := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Content: root.Content[i : i+2]}
section := key
if path != "" {
section = path + "." + key
}
warnUnrecognizedV8Section(section)
keyNode := *node.Content[i]
keyNode.Value = section
entry := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Content: []*yaml.Node{&keyNode, node.Content[i+1]}}
data, errMarshal := yaml.Marshal(entry)
if errMarshal != nil {
return errMarshal
}
text := strings.TrimSuffix(string(data), "\n")
comments = append(comments, "# "+strings.ReplaceAll(text, "\n", "\n# "))
root.Content = append(root.Content[:i], root.Content[i+2:]...)
node.Content = append(node.Content[:i], node.Content[i+2:]...)
}
// A root-level comment survives later deletion of any neighboring setting.
// Archive at the root so deleting a neighboring nested field cannot drop it.
if len(comments) > 0 {
root.FootComment = strings.TrimSpace(root.FootComment + "\n" + strings.Join(comments, "\n"))
archive.FootComment = strings.TrimSpace(archive.FootComment + "\n" + strings.Join(comments, "\n"))
}
return nil
}

View File

@@ -307,6 +307,43 @@ proxy-url: old
}
}
func TestV8MigrationCommentsUnknownNestedFields(t *testing.T) {
raw := []byte(`server: {port: 8317}
routing: {strategy: fill-first, session-affinity: true}
oauth:
providers:
codex:
disable-codex-cloaking: true
retired-setting: {mode: old}
`)
unchanged, changed, err := NormalizeConfigLayout(raw, false)
if err != nil || changed || string(unchanged) != string(raw) {
t.Fatalf("read-only normalization changed existing config: changed=%v error=%v", changed, err)
}
migrated, _, err := NormalizeConfigLayout(raw, true)
if err != nil {
t.Fatal(err)
}
if err = ValidateV8Config(migrated); err != nil {
t.Fatalf("migrated config is invalid: %v\n%s", err, migrated)
}
var doc yaml.Node
if err = yaml.Unmarshal(migrated, &doc); err != nil {
t.Fatal(err)
}
if yamlPath(doc.Content[0], "oauth.providers.codex.retired-setting") != nil || !strings.Contains(string(migrated), "# oauth.providers.codex.retired-setting:") {
t.Fatalf("unknown nested field was not preserved as a comment: %s", migrated)
}
cfg, err := ParseConfigBytes(migrated)
if err != nil || cfg.Routing.Strategy != "fill-first" || !cfg.Routing.SessionAffinity || !cfg.Codex.DisableCodexCloaking {
t.Fatalf("migration changed known settings: cfg=%+v error=%v", cfg, err)
}
remigrated, _, err := NormalizeConfigLayout(migrated, true)
if err != nil || strings.Count(string(remigrated), "# oauth.providers.codex.retired-setting:") != 1 {
t.Fatalf("repeated migration lost or duplicated the comment: %v\n%s", err, remigrated)
}
}
func TestV8MigrationCommentsUnknownLegacySectionsWarnsConsole(t *testing.T) {
logger := log.StandardLogger()
previousHooks := logger.ReplaceHooks(make(log.LevelHooks))
@@ -554,7 +591,7 @@ func TestV8MigrationPreservesEmptyLegacyContainers(t *testing.T) {
func TestV8EmptyLegacyContainersKeepNewValues(t *testing.T) {
raw := []byte(`port: 8317
tls: null
codex: {identity-confuse: true, live-media-relay: {}}
codex: {disable-codex-cloaking: true, live-media-relay: {}}
server: {tls: {enable: true, cert: server.crt, key: server.key}}
oauth: {providers: {codex: {live-media-relay: {max-sessions: 12}}}}
`)
@@ -567,7 +604,7 @@ oauth: {providers: {codex: {live-media-relay: {max-sessions: 12}}}}
if err != nil {
t.Fatal(err)
}
if !cfg.TLS.Enable || cfg.TLS.Cert != "server.crt" || cfg.TLS.Key != "server.key" || cfg.Codex.LiveMediaRelay.MaxSessions != 12 || !cfg.Codex.IdentityConfuse {
if !cfg.TLS.Enable || cfg.TLS.Cert != "server.crt" || cfg.TLS.Key != "server.key" || cfg.Codex.LiveMediaRelay.MaxSessions != 12 || !cfg.Codex.DisableCodexCloaking {
t.Fatal("empty legacy block overwrote new values or a non-empty sibling")
}
var doc yaml.Node
@@ -577,7 +614,7 @@ oauth: {providers: {codex: {live-media-relay: {max-sessions: 12}}}}
if yamlPath(doc.Content[0], "tls") != nil || yamlPath(doc.Content[0], "codex.live-media-relay") != nil {
t.Fatal("conflicting empty legacy blocks were not removed")
}
if !migrate && yamlPath(doc.Content[0], "codex.identity-confuse") == nil {
if !migrate && yamlPath(doc.Content[0], "codex.disable-codex-cloaking") == nil {
t.Fatal("conflict cleanup migrated a non-conflicting legacy sibling")
}
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor"
@@ -31,7 +30,7 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom
}
url := "https://example.com/responses"
httpReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, nil, req, req.Payload, rawJSON)
httpReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON)
if err != nil {
t.Fatalf("cacheHelper error: %v", err)
}
@@ -56,7 +55,7 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom
t.Fatalf("Session_id = %q, want empty", gotLegacySession)
}
httpReq2, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, nil, req, req.Payload, rawJSON)
httpReq2, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON)
if err != nil {
t.Fatalf("cacheHelper error (second call): %v", err)
}
@@ -81,7 +80,7 @@ func TestCodexExecutorCacheHelper_UsesDerivedSessionUUID(t *testing.T) {
}
expectedKey := helps.DerivedSessionUUID("codex", req.Metadata)
httpReq, body, _, err := executor.cacheHelper(context.Background(), sdktranslator.FormatOpenAI, "https://example.com/responses", nil, req, req.Payload, []byte(`{"model":"gpt-5.4","stream":true}`))
httpReq, body, err := executor.cacheHelper(context.Background(), sdktranslator.FormatOpenAI, "https://example.com/responses", req, []byte(`{"model":"gpt-5.4","stream":true}`))
if err != nil {
t.Fatalf("cacheHelper error: %v", err)
}
@@ -118,11 +117,11 @@ func TestCodexExecutorCacheHelper_ClaudeUsesClaudeCodeSessionID(t *testing.T) {
}`),
}
firstHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, firstReq, firstReq.Payload, rawJSON)
firstHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, firstReq, rawJSON)
if err != nil {
t.Fatalf("cacheHelper first error: %v", err)
}
secondHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, secondReq, secondReq.Payload, rawJSON)
secondHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, secondReq, rawJSON)
if err != nil {
t.Fatalf("cacheHelper second error: %v", err)
}
@@ -158,7 +157,7 @@ func TestCodexExecutorCacheHelper_ClaudeRejectsBareUserID(t *testing.T) {
Payload: []byte(`{"model":"gpt-5.4","metadata":{"user_id":"same-user-across-chats"},"messages":[{"role":"user","content":[{"type":"text","text":"first"}]}]}`),
}
httpReq, _, _, err := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), "https://example.com/responses", nil, req, req.Payload, []byte(`{"model":"gpt-5.4","stream":true}`))
httpReq, _, err := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), "https://example.com/responses", req, []byte(`{"model":"gpt-5.4","stream":true}`))
if err != nil {
t.Fatalf("cacheHelper error: %v", err)
}
@@ -178,81 +177,6 @@ func TestCodexExecutorCacheHelper_ClaudeRejectsBareUserID(t *testing.T) {
}
}
func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing.T) {
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","window_id":"cache-1:0"}`)
ginCtx.Request.Header.Set("X-Client-Request-Id", "client-request-1")
ctx := context.WithValue(context.Background(), "gin", ginCtx)
executor := &CodexExecutor{cfg: &config.Config{
Routing: config.RoutingConfig{Strategy: "fill-first"},
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\",\"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"}}`),
}
url := "https://example.com/responses"
httpReq, body, identityState, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai-response"), url, auth, req, req.Payload, rawJSON)
if err != nil {
t.Fatalf("cacheHelper error: %v", err)
}
applyCodexHeaders(httpReq, auth, "oauth-token", true, executor.cfg)
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)
}
expectedInstallationID := codexIdentityConfuseUUID("auth-1", "installation", "install-1")
if gotID := gjson.GetBytes(body, "client_metadata.x-codex-installation-id").String(); gotID != expectedInstallationID {
t.Fatalf("installation id = %q, want %q", gotID, expectedInstallationID)
}
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")
}
if gotHeader := httpReq.Header["Session-Id"]; len(gotHeader) != 1 || gotHeader[0] != expectedPromptCacheKey {
t.Fatalf("Session-Id = %#v, want [%q]", gotHeader, expectedPromptCacheKey)
}
for _, headerName := range []string{"X-Client-Request-Id", "Thread-Id"} {
if gotHeader := httpReq.Header.Get(headerName); gotHeader != expectedPromptCacheKey {
t.Fatalf("%s = %q, want %q", headerName, gotHeader, expectedPromptCacheKey)
}
}
if gotLegacySession := httpReq.Header.Get("Session_id"); gotLegacySession != "" {
t.Fatalf("Session_id = %q, want empty", gotLegacySession)
}
if gotWindow := httpReq.Header.Get("X-Codex-Window-Id"); gotWindow != expectedPromptCacheKey+":0" {
t.Fatalf("X-Codex-Window-Id = %q, want %q", gotWindow, expectedPromptCacheKey+":0")
}
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")
}
}
func TestApplyCodexHeadersUsesAccountHeaderForOAuth(t *testing.T) {
httpReq := httptest.NewRequest("POST", "https://example.com/responses", nil)
auth := &cliproxyauth.Auth{
@@ -267,27 +191,6 @@ func TestApplyCodexHeadersUsesAccountHeaderForOAuth(t *testing.T) {
}
}
func TestCodexIdentityConfuseKeepsClientBodySeparateFromUpstreamBody(t *testing.T) {
cfg := &config.Config{
Routing: config.RoutingConfig{Strategy: "fill-first"},
Codex: config.CodexConfig{IdentityConfuse: true},
}
auth := &cliproxyauth.Auth{ID: "auth-1", Provider: "codex"}
clientBody := []byte(`{"model":"gpt-5-codex","prompt_cache_key":"cache-1"}`)
upstreamBody, identityState := applyCodexIdentityConfuseBody(cfg, auth, clientBody, clientBody)
expectedPromptCacheKey := codexIdentityConfuseUUID("auth-1", "prompt-cache", "cache-1")
if identityState.promptCacheKey != expectedPromptCacheKey {
t.Fatalf("identity prompt_cache_key = %q, want %q", identityState.promptCacheKey, expectedPromptCacheKey)
}
if gotKey := gjson.GetBytes(upstreamBody, "prompt_cache_key").String(); gotKey != expectedPromptCacheKey {
t.Fatalf("upstream prompt_cache_key = %q, want %q", gotKey, expectedPromptCacheKey)
}
if gotKey := gjson.GetBytes(clientBody, "prompt_cache_key").String(); gotKey != "cache-1" {
t.Fatalf("client prompt_cache_key = %q, want cache-1", gotKey)
}
}
func TestCodexExecutorCacheHelper_ClaudeUsesSessionHeader(t *testing.T) {
executor := &CodexExecutor{}
recorder := httptest.NewRecorder()
@@ -307,11 +210,11 @@ func TestCodexExecutorCacheHelper_ClaudeUsesSessionHeader(t *testing.T) {
rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`)
url := "https://example.com/responses"
firstHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, firstReq, firstReq.Payload, rawJSON)
firstHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, firstReq, rawJSON)
if err != nil {
t.Fatalf("cacheHelper first error: %v", err)
}
secondHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, secondReq, secondReq.Payload, rawJSON)
secondHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, secondReq, rawJSON)
if err != nil {
t.Fatalf("cacheHelper second error: %v", err)
}
@@ -347,7 +250,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
childHeaders.Set(helps.ClaudeCodeAgentHeader, "agent-a")
rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`)
rootRequest, _, _, errRoot := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, rootHeaders)
rootRequest, _, errRoot := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, req, rawJSON, rootHeaders)
if errRoot != nil {
t.Fatalf("root cacheHelper error: %v", errRoot)
}
@@ -357,7 +260,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
}
rootKey := gjson.GetBytes(rootBody, "prompt_cache_key").String()
childRequest, _, _, errChild := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, childHeaders)
childRequest, _, errChild := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, req, rawJSON, childHeaders)
if errChild != nil {
t.Fatalf("child cacheHelper error: %v", errChild)
}
@@ -372,7 +275,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
aliasReq := req
aliasReq.Model = "another-local-alias-low"
aliasRequest, _, _, errAlias := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, aliasReq, aliasReq.Payload, rawJSON, childHeaders)
aliasRequest, _, errAlias := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, aliasReq, rawJSON, childHeaders)
if errAlias != nil {
t.Fatalf("alias cacheHelper error: %v", errAlias)
}

View File

@@ -77,15 +77,13 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
reporter.SetTranslatedReasoningEffort(body, to.String())
url := strings.TrimSuffix(baseURL, "/") + "/responses"
var identityState codexIdentityConfuseState
httpReq, upstreamBody, identityState, err := e.cacheHelper(ctx, from, url, auth, req, originalPayloadSource, body, opts.Headers)
httpReq, upstreamBody, err := e.cacheHelper(ctx, from, url, req, body, opts.Headers)
if err != nil {
return resp, err
}
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg, opts.Headers)
applyCodexRoutingHint(ctx, httpReq.Header, auth, baseModel, upstreamBody, opts.Headers)
applyModelHeaderOverrides(httpReq.Header, baseModel)
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
var authID, authLabel, authType, authValue string
if auth != nil {
authID = auth.ID
@@ -118,7 +116,6 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
helps.RecordAPIResponseMetadata(ctx, e.cfg, httpResp.StatusCode, httpResp.Header.Clone())
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
b, _ := io.ReadAll(httpResp.Body)
b = applyCodexIdentityConfuseResponsePayload(b, identityState)
if errClearReplay := clearCodexReasoningReplayOnInvalidSignature(ctx, replayScope, httpResp.StatusCode, b); errClearReplay != nil {
return resp, errClearReplay
}
@@ -128,10 +125,9 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
return resp, err
}
data, errRead := io.ReadAll(httpResp.Body)
upstreamData := applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, upstreamData)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
lines := bytes.Split(upstreamData, []byte("\n"))
lines := bytes.Split(data, []byte("\n"))
outputItemsByIndex := make(map[int64][]byte)
var outputItemsFallback [][]byte
sawOutputDelta := false
@@ -191,8 +187,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
}
var param any
clientCompletedData := applyCodexIdentityExposeResponsePayload(completedData, identityState)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, clientCompletedData, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, completedData, &param)
if responseFormat == sdktranslator.FormatOpenAIResponse {
out = helps.EnsureResponsesUsageDetails(out)
}
@@ -251,15 +246,13 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
reporter.SetTranslatedReasoningEffort(body, to.String())
url := strings.TrimSuffix(baseURL, "/") + "/responses/compact"
var identityState codexIdentityConfuseState
httpReq, upstreamBody, identityState, err := e.cacheHelper(ctx, from, url, auth, req, originalPayloadSource, body, opts.Headers)
httpReq, upstreamBody, err := e.cacheHelper(ctx, from, url, req, body, opts.Headers)
if err != nil {
return resp, err
}
applyCodexHeaders(httpReq, auth, apiKey, false, e.cfg, opts.Headers)
applyCodexRoutingHint(ctx, httpReq.Header, auth, baseModel, upstreamBody, opts.Headers)
applyModelHeaderOverrides(httpReq.Header, baseModel)
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
var authID, authLabel, authType, authValue string
if auth != nil {
authID = auth.ID
@@ -292,7 +285,6 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
helps.RecordAPIResponseMetadata(ctx, e.cfg, httpResp.StatusCode, httpResp.Header.Clone())
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
b, _ := io.ReadAll(httpResp.Body)
b = applyCodexIdentityConfuseResponsePayload(b, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, b)
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), b))
err = newCodexStatusErrWithCooling(httpResp.StatusCode, b, e.modelLevelCooling())
@@ -303,14 +295,12 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
helps.RecordAPIResponseError(ctx, e.cfg, err)
return resp, err
}
upstreamData := applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, upstreamData)
upstreamData = helps.RestoreCodexMultiAgentV2Response(upstreamData, optimizeMultiAgentV2)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
upstreamData := helps.RestoreCodexMultiAgentV2Response(data, optimizeMultiAgentV2)
reporter.Publish(ctx, helps.ParseOpenAIUsage(upstreamData))
reporter.EnsurePublished(ctx)
var param any
clientData := applyCodexIdentityExposeResponsePayload(upstreamData, identityState)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, clientData, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, upstreamData, &param)
if responseFormat == sdktranslator.FormatOpenAIResponse {
out = helps.EnsureResponsesUsageDetails(out)
}

View File

@@ -91,20 +91,7 @@ func (e *CodexExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth
return httpClient.Do(httpReq)
}
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, headerSets ...http.Header) (*http.Request, []byte, codexIdentityConfuseState, error) {
func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Format, url string, req cliproxyexecutor.Request, rawJSON []byte, headerSets ...http.Header) (*http.Request, []byte, error) {
var headers http.Header
if len(headerSets) > 0 {
headers = headerSets[0]
@@ -117,7 +104,7 @@ func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Form
}
cached, ok, errCache := helps.ClaudeCodePromptCache(ctx, modelName, req.Payload, headers)
if errCache != nil {
return nil, nil, codexIdentityConfuseState{}, errCache
return nil, nil, errCache
}
if ok {
cache = cached
@@ -148,141 +135,14 @@ func (e *CodexExecutor) cacheHelper(ctx context.Context, from sdktranslator.Form
rawJSON = helps.SetStringIfDifferent(rawJSON, "prompt_cache_key", cache.ID)
}
rawJSON = helps.SanitizeCodexInputItemIDs(rawJSON)
var identityState codexIdentityConfuseState
rawJSON, identityState = applyCodexIdentityConfuseBody(e.cfg, auth, userPayload, rawJSON)
if identityState.promptCacheKey != "" {
cache.ID = identityState.promptCacheKey
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(rawJSON))
if err != nil {
return nil, nil, codexIdentityConfuseState{}, err
return nil, nil, err
}
if cache.ID != "" {
httpReq.Header.Set("Session-Id", cache.ID)
}
return httpReq, rawJSON, identityState, nil
}
func applyCodexIdentityConfuseBody(cfg *config.Config, auth *cliproxyauth.Auth, userPayload []byte, rawJSON []byte) ([]byte, codexIdentityConfuseState) {
if !codexIdentityConfuseEnabled(cfg) || auth == nil || strings.TrimSpace(auth.ID) == "" || len(rawJSON) == 0 {
return rawJSON, 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)
rawJSON = helps.SetStringIfDifferent(rawJSON, "prompt_cache_key", state.promptCacheKey)
}
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 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")
}
}
return rawJSON, state
}
func applyCodexIdentityConfuseHeaders(headers http.Header, state *codexIdentityConfuseState) {
if headers == nil {
return
}
if state == nil || !state.enabled {
return
}
if rawTurnMetadata := strings.TrimSpace(headers.Get("X-Codex-Turn-Metadata")); rawTurnMetadata != "" {
headers.Set("X-Codex-Turn-Metadata", applyCodexTurnMetadataIdentityConfuse(rawTurnMetadata, state))
}
if state.promptCacheKey == "" {
return
}
setCodexSessionHeaderCasePreserved(headers, "Session-Id", state.promptCacheKey)
if headerValueCaseInsensitive(headers, "Conversation_id") != "" {
setHeaderCasePreserved(headers, "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")
}
func applyCodexTurnMetadataIdentityConfuse(rawTurnMetadata string, state *codexIdentityConfuseState) string {
updatedTurnMetadata := rawTurnMetadata
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.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 {
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 {
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 {
from = strings.TrimSpace(from)
to = strings.TrimSpace(to)
if len(payload) == 0 || from == "" || to == "" || from == to || !bytes.Contains(payload, []byte(from)) {
return payload
}
return bytes.ReplaceAll(payload, []byte(from), []byte(to))
}
func codexIdentityConfuseEnabled(cfg *config.Config) bool {
if cfg == nil || !cfg.Codex.IdentityConfuse {
return false
}
strategy := strings.ToLower(strings.TrimSpace(cfg.Routing.Strategy))
return cfg.Routing.SessionAffinity || strategy == "fill-first" || strategy == "fillfirst" || strategy == "ff"
}
func codexIdentityConfuseUUID(authID string, kind string, value string) string {
name := strings.Join([]string{"cli-proxy-api", "codex", "identity-confuse", kind, strings.TrimSpace(authID), strings.TrimSpace(value)}, ":")
return uuid.NewSHA1(uuid.NameSpaceOID, []byte(name)).String()
return httpReq, rawJSON, nil
}
func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, stream bool, cfg *config.Config, clientHeaders ...http.Header) {

View File

@@ -85,15 +85,13 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
reporter.SetTranslatedReasoningEffort(body, to.String())
url := strings.TrimSuffix(baseURL, "/") + "/responses"
var identityState codexIdentityConfuseState
httpReq, upstreamBody, identityState, err := e.cacheHelper(ctx, from, url, auth, req, originalPayloadSource, body, opts.Headers)
httpReq, upstreamBody, err := e.cacheHelper(ctx, from, url, req, body, opts.Headers)
if err != nil {
return nil, err
}
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg, opts.Headers)
applyCodexRoutingHint(ctx, httpReq.Header, auth, baseModel, upstreamBody, opts.Headers)
applyModelHeaderOverrides(httpReq.Header, baseModel)
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
var authID, authLabel, authType, authValue string
if auth != nil {
authID = auth.ID
@@ -129,7 +127,6 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
helps.RecordAPIResponseError(ctx, e.cfg, readErr)
return nil, readErr
}
data = applyCodexIdentityConfuseResponsePayload(data, identityState)
if errClearReplay := clearCodexReasoningReplayOnInvalidSignature(ctx, replayScope, httpResp.StatusCode, data); errClearReplay != nil {
return nil, errClearReplay
}
@@ -185,7 +182,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
sawOutputDelta := false
if buffering {
for scanner.Scan() {
line := applyCodexIdentityConfuseResponsePayload(scanner.Bytes(), identityState)
line := scanner.Bytes()
helps.AppendAPIResponseChunk(ctx, e.cfg, line)
var translatedLine []byte
isHandshake := false
@@ -273,7 +270,6 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
isHandshake = true
}
translatedLine = applyCodexIdentityExposeResponsePayload(translatedLine, identityState)
chunks := helps.TranslateStreamWithClaudeInputTokens(ctx, to, responseFormat, req.Model, originalPayload, body, translatedLine, &param, claudeInputTokens)
if isHandshake && !terminalSuccess {
frameBytes := len(line)
@@ -359,7 +355,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
}
}()
for scanner.Scan() {
line := applyCodexIdentityConfuseResponsePayload(scanner.Bytes(), identityState)
line := scanner.Bytes()
helps.AppendAPIResponseChunk(ctx, e.cfg, line)
var translatedLine []byte
terminalSuccess := false
@@ -427,7 +423,6 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
translatedLine = bytes.Clone(line)
}
translatedLine = applyCodexIdentityExposeResponsePayload(translatedLine, identityState)
chunks := helps.TranslateStreamWithClaudeInputTokens(ctx, to, responseFormat, req.Model, originalPayload, body, translatedLine, &param, claudeInputTokens)
for i := range chunks {
select {

View File

@@ -110,14 +110,12 @@ func (e *CodexExecutor) executeOpenAIImage(ctx context.Context, auth *cliproxyau
reporter.SetTranslatedReasoningEffort(body, "codex")
url := strings.TrimSuffix(baseURL, "/") + "/responses"
var identityState codexIdentityConfuseState
httpReq, body, identityState, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, auth, req, req.Payload, body)
httpReq, body, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, req, body)
if errCache != nil {
return resp, errCache
}
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg, opts.Headers)
applyModelHeaderOverrides(httpReq.Header, mainModel)
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -139,7 +137,6 @@ func (e *CodexExecutor) executeOpenAIImage(ctx context.Context, auth *cliproxyau
helps.RecordAPIResponseError(ctx, e.cfg, errRead)
return resp, errRead
}
data = applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), data))
@@ -208,14 +205,12 @@ func (e *CodexExecutor) executeOpenAIImageStream(ctx context.Context, auth *clip
reporter.SetTranslatedReasoningEffort(body, "codex")
url := strings.TrimSuffix(baseURL, "/") + "/responses"
var identityState codexIdentityConfuseState
httpReq, body, identityState, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, auth, req, req.Payload, body)
httpReq, body, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, req, body)
if errCache != nil {
return nil, errCache
}
applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg, opts.Headers)
applyModelHeaderOverrides(httpReq.Header, mainModel)
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -235,7 +230,6 @@ func (e *CodexExecutor) executeOpenAIImageStream(ctx context.Context, auth *clip
helps.RecordAPIResponseError(ctx, e.cfg, errRead)
return nil, errRead
}
data = applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), data))
err = newCodexStatusErrWithCooling(httpResp.StatusCode, data, e.modelLevelCooling())
@@ -273,7 +267,7 @@ func (e *CodexExecutor) executeOpenAIImageStream(ctx context.Context, auth *clip
outputItemsByIndex := make(map[int64][]byte)
var outputItemsFallback [][]byte
for scanner.Scan() {
line := applyCodexIdentityConfuseResponsePayload(scanner.Bytes(), identityState)
line := scanner.Bytes()
helps.AppendAPIResponseChunk(ctx, e.cfg, line)
if !bytes.HasPrefix(line, dataTag) {
continue
@@ -336,8 +330,7 @@ func (e *CodexExecutor) executeDirectOpenAIImage(ctx context.Context, auth *clip
reporter.SetTranslatedReasoningEffort(body, "openai")
url := strings.TrimSuffix(baseURL, "/") + endpointPath
var identityState codexIdentityConfuseState
httpReq, body, identityState, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, auth, req, req.Payload, body)
httpReq, body, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, req, body)
if errCache != nil {
return resp, errCache
}
@@ -346,7 +339,6 @@ func (e *CodexExecutor) executeDirectOpenAIImage(ctx context.Context, auth *clip
if contentType != "" {
httpReq.Header.Set("Content-Type", contentType)
}
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -368,7 +360,6 @@ func (e *CodexExecutor) executeDirectOpenAIImage(ctx context.Context, auth *clip
helps.RecordAPIResponseError(ctx, e.cfg, errRead)
return resp, errRead
}
data = applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), data))
@@ -397,8 +388,7 @@ func (e *CodexExecutor) executeDirectOpenAIImageStream(ctx context.Context, auth
reporter.SetTranslatedReasoningEffort(body, "openai")
url := strings.TrimSuffix(baseURL, "/") + endpointPath
var identityState codexIdentityConfuseState
httpReq, body, identityState, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, auth, req, req.Payload, body)
httpReq, body, errCache := e.cacheHelper(ctx, sdktranslator.FromString(codexOpenAIImageSourceFormat), url, req, body)
if errCache != nil {
return nil, errCache
}
@@ -407,7 +397,6 @@ func (e *CodexExecutor) executeDirectOpenAIImageStream(ctx context.Context, auth
if contentType != "" {
httpReq.Header.Set("Content-Type", contentType)
}
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body)
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
@@ -427,7 +416,6 @@ func (e *CodexExecutor) executeDirectOpenAIImageStream(ctx context.Context, auth
helps.RecordAPIResponseError(ctx, e.cfg, errRead)
return nil, errRead
}
data = applyCodexIdentityConfuseResponsePayload(data, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), data))
err = newCodexStatusErrWithCooling(httpResp.StatusCode, data, e.modelLevelCooling())
@@ -451,7 +439,6 @@ func (e *CodexExecutor) executeDirectOpenAIImageStream(ctx context.Context, auth
n, errRead := httpResp.Body.Read(buffer)
if n > 0 {
chunk := bytes.Clone(buffer[:n])
chunk = applyCodexIdentityConfuseResponsePayload(chunk, identityState)
helps.AppendAPIResponseChunk(ctx, e.cfg, chunk)
for _, line := range bytes.Split(chunk, []byte("\n")) {
streamUsage.ObserveOpenAIStream(bytes.TrimSpace(line))

View File

@@ -200,7 +200,7 @@ func (e *CodexWebsocketsExecutor) streamCodexDuplex(
fail(cliproxyexecutor.NewUpstreamWebsocketReplayRequiredError())
return false
}
payload = buildCodexWebsocketRequestBody(prepared.upstreamBody)
payload = buildCodexWebsocketRequestBody(prepared.clientBody)
metadataMu.Lock()
if len(pending) >= 16 {
metadataMu.Unlock()
@@ -413,7 +413,7 @@ func (e *CodexWebsocketsExecutor) streamCodexDuplex(
// Retain response settings, not request history or authorization headers.
// In-flight steering pins its parent's settings independently of this window.
snapshot := *current
snapshot.originalPayload, snapshot.upstreamBody, snapshot.wsHeaders = nil, nil, nil
snapshot.originalPayload, snapshot.wsHeaders = nil, nil
snapshot.clientBody = []byte("{}")
if reasoning := gjson.GetBytes(current.clientBody, "reasoning"); reasoning.Exists() {
snapshot.clientBody, _ = sjson.SetRawBytes(snapshot.clientBody, "reasoning", []byte(reasoning.Raw))
@@ -511,7 +511,7 @@ func (e *CodexWebsocketsExecutor) streamCodexDuplex(
metadataMu.Lock()
// A failure for the running response must not consume a queued create.
// A rejection before response.created instead owns the oldest pending
// create, including its identity mapping and reasoning replay scope.
// create, including its reasoning replay scope.
currentFailure := failedID != "" && failedID == responseID
ambiguous := failedID == "" && ((len(pending) > 0 && responseActive) || len(unacknowledgedSteers) > 0)
if len(pending) > 0 && !currentFailure && !ambiguous {
@@ -536,7 +536,6 @@ func (e *CodexWebsocketsExecutor) streamCodexDuplex(
return
}
}
payload = applyCodexIdentityConfuseResponsePayload(payload, eventPrepared.identityState)
restoreMultiAgent := !eventPrepared.multiAgentV2Conflict && (eventPrepared.optimizeMultiAgentV2 || sess.isMultiAgentV2Optimized(conn))
payload = helps.RestoreCodexMultiAgentV2Response(payload, restoreMultiAgent)
// Parse and invalidate replay for every rejected request, using the
@@ -586,7 +585,6 @@ func (e *CodexWebsocketsExecutor) streamCodexDuplex(
reporter.EnsurePublished(ctx)
}
}
payload = applyCodexIdentityExposeResponsePayload(payload, eventPrepared.identityState)
if !send(cliproxyexecutor.StreamChunk{Payload: helps.EnsureResponsesUsageDetails(payload)}) {
return
}

View File

@@ -79,8 +79,6 @@ func TestCodexDuplexRejectedCreateMetadata(t *testing.T) {
cfg := &config.Config{}
cfg.Codex.ResponseSteering = true
cfg.CodexResponseSteering = true
cfg.Codex.IdentityConfuse = true
cfg.Routing.SessionAffinity = true
exec := NewCodexWebsocketsExecutor(cfg)
exec.store = &codexWebsocketSessionStore{sessions: make(map[string]*codexWebsocketSession)}
credential := &auth.Auth{ID: "metadata-account", Provider: "codex", Attributes: map[string]string{"api_key": "test", "base_url": upstream.URL, "websockets": "true"}}

View File

@@ -125,8 +125,6 @@ func TestCodexDuplexAutomaticSuccessorMetadata(t *testing.T) {
cfg := &config.Config{}
cfg.Codex.ResponseSteering = true
cfg.CodexResponseSteering = true
cfg.Codex.IdentityConfuse = true
cfg.Routing.SessionAffinity = true
executor := NewCodexWebsocketsExecutor(cfg)
executor.store = &codexWebsocketSessionStore{sessions: make(map[string]*codexWebsocketSession)}
credential := &auth.Auth{ID: t.Name(), Provider: "codex", Attributes: map[string]string{"api_key": "test", "base_url": upstream.URL, "websockets": "true"}}

View File

@@ -83,14 +83,10 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
if errPromptCache != nil {
return resp, errPromptCache
}
clientBody := body
var identityState codexIdentityConfuseState
upstreamBody, identityState := applyCodexIdentityConfuseBody(e.cfg, auth, originalPayloadSource, body)
reporter.SetTranslatedReasoningEffort(clientBody, to.String())
reporter.SetTranslatedReasoningEffort(body, to.String())
wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg, nativeRequest, opts.Headers)
applyCodexRoutingHint(ctx, wsHeaders, auth, baseModel, upstreamBody, opts.Headers)
applyCodexRoutingHint(ctx, wsHeaders, auth, baseModel, body, opts.Headers)
applyModelHeaderOverrides(wsHeaders, baseModel)
applyCodexIdentityConfuseHeaders(wsHeaders, &identityState)
var authID, authLabel, authType, authValue string
if auth != nil {
@@ -119,7 +115,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
sess = newEphemeralCodexWebsocketSession()
}
wsReqBody := buildCodexWebsocketRequestBody(upstreamBody)
wsReqBody := buildCodexWebsocketRequestBody(body)
wsReqLog := helps.UpstreamRequestLog{
URL: wsURL,
Method: "WEBSOCKET",
@@ -230,7 +226,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
}
readCh = sess.activate(conn)
restoreMultiAgentV2 = !multiAgentV2Conflict && (optimizeMultiAgentV2 || sess.isMultiAgentV2Optimized(conn))
wsReqBodyRetry := buildCodexWebsocketRequestBody(upstreamBody)
wsReqBodyRetry := buildCodexWebsocketRequestBody(body)
helps.RecordAPIWebsocketRequest(ctx, e.cfg, helps.UpstreamRequestLog{
URL: wsURL,
Method: "WEBSOCKET",
@@ -305,7 +301,6 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
continue
}
observeCodexTokenEvent(reporter, payload)
payload = applyCodexIdentityConfuseResponsePayload(payload, identityState)
helps.AppendCodexAPIWebsocketResponse(ctx, e.cfg, payload)
helps.EmitWebSocketResponseEvent(ctx, opts, auth, e.Identifier(), req.Model, payload)
payload = helps.RestoreCodexMultiAgentV2Response(payload, restoreMultiAgentV2)
@@ -360,8 +355,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
reporter.EnsurePublished(ctx)
}
var param any
clientPayload := applyCodexIdentityExposeResponsePayload(payload, identityState)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, clientBody, clientPayload, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, payload, &param)
if responseFormat == sdktranslator.FormatOpenAIResponse {
out = helps.EnsureResponsesUsageDetails(out)
}

View File

@@ -1589,120 +1589,6 @@ func TestApplyCodexPromptCacheHeadersClaudeRejectsBareUserID(t *testing.T) {
}
}
func TestApplyCodexWebsocketHeadersIdentityConfuseRemapsPromptCacheKey(t *testing.T) {
cfg := &config.Config{
Routing: config.RoutingConfig{SessionAffinity: true},
Codex: config.CodexConfig{IdentityConfuse: true},
}
auth := &cliproxyauth.Auth{ID: "auth-ws-1", Provider: "codex"}
req := cliproxyexecutor.Request{
Model: "gpt-5-codex",
Payload: []byte(`{"prompt_cache_key":"cache-ws-1","client_metadata":{"x-codex-installation-id":"install-ws-1"}}`),
}
body, headers := applyCodexPromptCacheHeaders("openai-response", req, []byte(`{"model":"gpt-5-codex"}`))
body, identityState := applyCodexIdentityConfuseBody(cfg, auth, req.Payload, body)
ctx := contextWithGinHeaders(map[string]string{
"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, false)
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)
}
if gotSession := headers["session_id"]; len(gotSession) != 1 || gotSession[0] != expectedPromptCacheKey {
t.Fatalf("session_id = %#v, want [%q]", gotSession, expectedPromptCacheKey)
}
if gotCanonicalSession := headers.Get("Session-Id"); gotCanonicalSession != "" {
t.Fatalf("Session-Id = %q, want empty", gotCanonicalSession)
}
if gotRequestID := headers.Get("X-Client-Request-Id"); gotRequestID != expectedPromptCacheKey {
t.Fatalf("X-Client-Request-Id = %q, want %q", gotRequestID, expectedPromptCacheKey)
}
if gotThreadID := headers.Get("Thread-Id"); gotThreadID != expectedPromptCacheKey {
t.Fatalf("Thread-Id = %q, want %q", gotThreadID, expectedPromptCacheKey)
}
if gotConversation := headers.Get("Conversation_id"); gotConversation != expectedPromptCacheKey {
t.Fatalf("Conversation_id = %q, want %q", gotConversation, expectedPromptCacheKey)
}
if gotWindowID := headers.Get("X-Codex-Window-Id"); gotWindowID != expectedPromptCacheKey+":0" {
t.Fatalf("X-Codex-Window-Id = %q, want %q", gotWindowID, expectedPromptCacheKey+":0")
}
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 {
t.Fatalf("installation id = %q, want %q", gotInstallationID, expectedInstallationID)
}
}
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"),
}
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","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))
}
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))
}
}
func TestBuildCodexResponsesWebsocketURLRequiresHTTPURL(t *testing.T) {
if got, err := buildCodexResponsesWebsocketURL("https://example.com/backend/responses"); err != nil || got != "wss://example.com/backend/responses" {
t.Fatalf("https URL = %q, %v; want wss URL", got, err)

View File

@@ -43,10 +43,8 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
preserveNativeOutput := prepared.preserveNativeOutput
originalPayload := prepared.originalPayload
clientBody := prepared.clientBody
upstreamBody := prepared.upstreamBody
wsURL := prepared.wsURL
wsHeaders := prepared.wsHeaders
identityState := prepared.identityState
replayScope := prepared.replayScope
optimizeMultiAgentV2 := prepared.optimizeMultiAgentV2
multiAgentV2Conflict := prepared.multiAgentV2Conflict
@@ -77,7 +75,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
}
}
wsReqBody := buildCodexWebsocketRequestBody(upstreamBody)
wsReqBody := buildCodexWebsocketRequestBody(clientBody)
wsReqLog := helps.UpstreamRequestLog{
URL: wsURL,
Method: "WEBSOCKET",
@@ -195,7 +193,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
}
readCh = sess.activate(conn)
restoreMultiAgentV2 = !multiAgentV2Conflict && (optimizeMultiAgentV2 || sess.isMultiAgentV2Optimized(conn))
wsReqBodyRetry := buildCodexWebsocketRequestBody(upstreamBody)
wsReqBodyRetry := buildCodexWebsocketRequestBody(clientBody)
helps.RecordAPIWebsocketRequest(ctx, e.cfg, helps.UpstreamRequestLog{
URL: wsURL,
Method: "WEBSOCKET",
@@ -354,7 +352,6 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
continue
}
observeCodexTokenEvent(reporter, payload)
payload = applyCodexIdentityConfuseResponsePayload(payload, identityState)
helps.AppendCodexAPIWebsocketResponse(ctx, e.cfg, payload)
helps.EmitWebSocketResponseEvent(ctx, opts, auth, e.Identifier(), req.Model, payload)
payload = helps.RestoreCodexMultiAgentV2Response(payload, restoreMultiAgentV2)
@@ -469,16 +466,14 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
if eventType == "response.completed" || eventType == "response.done" || eventType == "response.incomplete" {
payload = completedPayload
}
clientPayload := applyCodexIdentityExposeResponsePayload(payload, identityState)
downstreamPayload := helps.EnsureResponsesUsageDetails(clientPayload)
downstreamPayload := helps.EnsureResponsesUsageDetails(payload)
currentChunks = [][]byte{downstreamPayload}
} else {
payload = normalizeCodexWebsocketCompletion(payload)
if eventType == "response.completed" || eventType == "response.done" || eventType == "response.incomplete" {
payload = completedPayload
}
clientPayload := applyCodexIdentityExposeResponsePayload(payload, identityState)
line := encodeCodexWebsocketAsSSE(clientPayload)
line := encodeCodexWebsocketAsSSE(payload)
currentChunks = helps.TranslateStreamWithClaudeInputTokens(ctx, to, responseFormat, req.Model, originalPayload, clientBody, line, &param, claudeInputTokens)
}
@@ -621,7 +616,6 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
continue
}
observeCodexTokenEvent(reporter, payload)
payload = applyCodexIdentityConfuseResponsePayload(payload, identityState)
helps.AppendCodexAPIWebsocketResponse(ctx, e.cfg, payload)
helps.EmitWebSocketResponseEvent(ctx, opts, auth, e.Identifier(), req.Model, payload)
payload = helps.RestoreCodexMultiAgentV2Response(payload, restoreMultiAgentV2)
@@ -701,12 +695,11 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
}
}
clientPayload := applyCodexIdentityExposeResponsePayload(payload, identityState)
if cliproxyexecutor.DownstreamWebsocket(ctx) {
if eventType == "response.completed" || eventType == "response.done" || eventType == "response.incomplete" {
clientPayload = applyCodexIdentityExposeResponsePayload(completedPayload, identityState)
payload = completedPayload
}
downstreamPayload := helps.EnsureResponsesUsageDetails(clientPayload)
downstreamPayload := helps.EnsureResponsesUsageDetails(payload)
if !send(cliproxyexecutor.StreamChunk{Payload: downstreamPayload}) {
terminateReason = "context_done"
terminateErr = ctx.Err()
@@ -723,8 +716,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
payload = completedPayload
}
eventType = gjson.GetBytes(payload, "type").String()
clientPayload = applyCodexIdentityExposeResponsePayload(payload, identityState)
line := encodeCodexWebsocketAsSSE(clientPayload)
line := encodeCodexWebsocketAsSSE(payload)
chunks := helps.TranslateStreamWithClaudeInputTokens(ctx, to, responseFormat, req.Model, originalPayload, clientBody, line, &param, claudeInputTokens)
for i := range chunks {
if !send(cliproxyexecutor.StreamChunk{Payload: chunks[i]}) {
@@ -751,10 +743,8 @@ type codexWebsocketPrepared struct {
preserveNativeOutput bool
originalPayload []byte
clientBody []byte
upstreamBody []byte
wsURL string
wsHeaders http.Header
identityState codexIdentityConfuseState
replayScope codexReasoningReplayScope
optimizeMultiAgentV2 bool
multiAgentV2Conflict bool
@@ -812,13 +802,9 @@ func (e *CodexWebsocketsExecutor) prepareCodexWebsocketStream(ctx context.Contex
if errPromptCache != nil {
return nil, errPromptCache
}
clientBody := body
var identityState codexIdentityConfuseState
upstreamBody, identityState := applyCodexIdentityConfuseBody(e.cfg, auth, originalPayloadSource, body)
wsHeaders = applyCodexWebsocketHeaders(ctx, wsHeaders, auth, apiKey, e.cfg, preserveNativeOutput, opts.Headers)
applyCodexRoutingHint(ctx, wsHeaders, auth, baseModel, upstreamBody, opts.Headers)
applyCodexRoutingHint(ctx, wsHeaders, auth, baseModel, body, opts.Headers)
applyModelHeaderOverrides(wsHeaders, baseModel)
applyCodexIdentityConfuseHeaders(wsHeaders, &identityState)
return &codexWebsocketPrepared{
from: from,
@@ -826,11 +812,9 @@ func (e *CodexWebsocketsExecutor) prepareCodexWebsocketStream(ctx context.Contex
to: to,
preserveNativeOutput: preserveNativeOutput,
originalPayload: originalPayload,
clientBody: clientBody,
upstreamBody: upstreamBody,
clientBody: body,
wsURL: wsURL,
wsHeaders: wsHeaders,
identityState: identityState,
replayScope: replayScope,
optimizeMultiAgentV2: optimizeMultiAgentV2,
multiAgentV2Conflict: multiAgentV2Conflict,

View File

@@ -137,9 +137,6 @@ func BuildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
changes = append(changes, fmt.Sprintf("antigravity.connection-pool.max-idle-conns-per-host: %s -> %s", oldMaxIdle, newMaxIdle))
}
if oldCfg.Codex.IdentityConfuse != newCfg.Codex.IdentityConfuse {
changes = append(changes, fmt.Sprintf("codex.identity-confuse: %t -> %t", oldCfg.Codex.IdentityConfuse, newCfg.Codex.IdentityConfuse))
}
if oldCfg.Codex.DisableCodexCloaking != newCfg.Codex.DisableCodexCloaking {
changes = append(changes, fmt.Sprintf("codex.disable-codex-cloaking: %t -> %t", oldCfg.Codex.DisableCodexCloaking, newCfg.Codex.DisableCodexCloaking))
}