package multiagentv2 import ( "context" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) func TestIsCodexMultiAgentClient(t *testing.T) { t.Parallel() tests := []struct { name string userAgent string want bool }{ { name: "Codex Desktop", userAgent: "Codex Desktop/0.146.0-alpha.3 (Mac OS 26.5.2; arm64) unknown (Codex Desktop; 26.721.30844)", want: true, }, { name: "codex tui", userAgent: "codex-tui/0.153.3 (Mac OS 26.5.1; arm64) iTerm.app/3.6.11 (codex-tui; 0.153.3)", want: true, }, { name: "codex cli rs", userAgent: "codex_cli_rs/0.144.1 (Mac OS 26.3.1; arm64) iTerm.app/3.6.9", want: true, }, { name: "bare codex cli rs", userAgent: "codex_cli_rs", want: true, }, { name: "other client", userAgent: "curl/8.7.1", want: false, }, { name: "embedded token", userAgent: "proxy Codex Desktop/0.146.0", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := isCodexMultiAgentClient(tt.userAgent); got != tt.want { t.Fatalf("isCodexMultiAgentClient(%q) = %v, want %v", tt.userAgent, got, tt.want) } }) } } func TestCodexSpawnAgentModelsFromSourcesIncludesModelMetadata(t *testing.T) { t.Parallel() catalog := []byte(`{"models":[ {"slug":"model-template","display_name":"Template","description":"Template model.","default_reasoning_level":"low","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"}],"service_tiers":[{"id":"priority"}],"priority":1}, {"slug":"gpt-5.5","display_name":"Default","description":"Default model.","default_reasoning_level":"medium","supported_reasoning_levels":[{"effort":"low"},{"effort":"medium"},{"effort":"high"}],"service_tiers":[{"id":"priority"}],"priority":2} ]}`) available := []map[string]any{ {"id": "custom-model", "display_name": "Custom", "description": "Registry description."}, {"id": "model-template"}, {"id": "custom-model", "description": "duplicate"}, } lookup := func(modelID string) *registry.ModelInfo { if modelID != "custom-model" { return nil } return ®istry.ModelInfo{ Description: "Dynamic model.", Thinking: ®istry.ThinkingSupport{ Levels: []string{"none", "low", "medium", "high"}, }, } } models := codexSpawnAgentModelsFromSources(available, catalog, lookup) if len(models) != 2 { t.Fatalf("model count = %d, want 2", len(models)) } if got := models[0]; got.id != "model-template" || got.description != "Template model." || got.defaultReasoningEffort != "low" { t.Fatalf("template model = %+v", got) } if got := strings.Join(models[0].serviceTiers, ","); got != "priority" { t.Fatalf("template service tiers = %q, want priority", got) } custom := models[1] if custom.id != "custom-model" || custom.description != "Dynamic model." { t.Fatalf("custom model = %+v", custom) } if got := strings.Join(custom.reasoningEfforts, ","); got != "none,low,medium,high" { t.Fatalf("custom reasoning efforts = %q", got) } if custom.defaultReasoningEffort != "medium" { t.Fatalf("custom default reasoning effort = %q, want medium", custom.defaultReasoningEffort) } if len(custom.serviceTiers) != 0 { t.Fatalf("custom service tiers = %v, want none", custom.serviceTiers) } } func TestDecodeCodexHomeAvailableModels(t *testing.T) { t.Parallel() raw := []byte(`{ "codex":[{"id":"model-b","display_name":"Model B"},{"id":"model-a"}], "other":[{"name":"models/model-c","displayName":"Model C"},{"id":"model-a","display_name":"duplicate"}] }`) models := decodeCodexHomeAvailableModels(raw) if len(models) != 3 { t.Fatalf("model count = %d, want 3", len(models)) } if got := mapString(models[0], "id"); got != "model-a" { t.Fatalf("first model ID = %q, want model-a", got) } if got := mapString(models[1], "description"); got != "Model B" { t.Fatalf("model-b description = %q, want Model B", got) } if got := mapString(models[2], "id"); got != "model-c" { t.Fatalf("last model ID = %q, want model-c", got) } if got := decodeCodexHomeAvailableModels([]byte(`{"error":{"type":"no_credentials"}}`)); got != nil { t.Fatalf("error envelope decoded as models: %#v", got) } } func TestRewriteCodexSpawnAgentDescriptionNormalizesModelList(t *testing.T) { t.Parallel() payload := []byte(`{ "input":[{ "type":"additional_tools", "role":"developer", "tools":[{ "type":"namespace", "name":"collaboration", "tools":[ {"type":"function","name":"send_message","description":"unchanged"}, {"type":"function","name":"spawn_agent","description":"\n Available model overrides (optional; inherited parent model is preferred):\n- old duplicate\n- old duplicate\n Spawns an agent to work on a task.","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ] }] }] }`) models := []codexSpawnAgentModel{ { id: "model-alpha", description: "Alpha model.", reasoningEfforts: []string{"low", "medium", "high"}, defaultReasoningEffort: "medium", serviceTiers: []string{"priority"}, }, { id: "model-beta", description: "Beta model", reasoningEfforts: []string{"low", "high"}, defaultReasoningEffort: "low", }, } got := rewriteCodexSpawnAgentDescription(payload, models) description := gjson.GetBytes(got, "input.0.tools.0.tools.1.description").String() wantAlpha := "- `model-alpha`: Alpha model. Reasoning efforts: low, medium (default), high. Service tiers: priority." wantBeta := "- `model-beta`: Beta model. Reasoning efforts: low (default), high." if !strings.Contains(description, wantAlpha) || !strings.Contains(description, wantBeta) { t.Fatalf("description does not contain model metadata:\n%s", description) } if strings.Contains(description, "old duplicate") { t.Fatalf("stale model list was not replaced: %q", description) } for _, modelID := range []string{"model-alpha", "model-beta"} { if count := strings.Count(description, "`"+modelID+"`"); count != 1 { t.Fatalf("model %q reference count = %d, want 1", modelID, count) } } if strings.Index(description, "`model-beta`") > strings.Index(description, codexSpawnAgentDescriptionMarker) { t.Fatalf("model list was not inserted before spawn instructions: %q", description) } if gotDescription := gjson.GetBytes(got, "input.0.tools.0.tools.0.description").String(); gotDescription != "unchanged" { t.Fatalf("non-spawn tool description = %q, want unchanged", gotDescription) } if encrypted := gjson.GetBytes(got, "input.0.tools.0.tools.1.parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("spawn_agent message encrypted was not removed: %s", encrypted.Raw) } } func TestRewriteCodexSpawnAgentDescriptionTopLevelWithoutMarker(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"namespace","name":"collaboration","tools":[{"type":"function","name":"spawn_agent","description":"Create a worker."}]}]}`) models := []codexSpawnAgentModel{{ id: "model-a", description: "Model A.", reasoningEfforts: []string{"medium"}, defaultReasoningEffort: "medium", }} got := rewriteCodexSpawnAgentDescription(payload, models) description := gjson.GetBytes(got, "tools.0.tools.0.description").String() wantSuffix := codexSpawnAgentModelsHeading + "\n- `model-a`: Model A. Reasoning efforts: medium (default)." if !strings.HasPrefix(description, "Create a worker.\n\n") || !strings.HasSuffix(description, wantSuffix) { t.Fatalf("description = %q, want original text followed by model list", description) } } func TestCodexSpawnAgentToolPathsIgnoreInvalidContainers(t *testing.T) { t.Parallel() payload := []byte(`{ "input":[{"type":"message","tools":[{"type":"function","name":"spawn_agent","description":"message"}]}], "tools":[ {"type":"function","name":"wrapper","tools":[{"type":"function","name":"spawn_agent","description":"child"}]}, {"type":"custom","name":"spawn_agent","description":"custom"}, {"type":"namespace","name":"spawn_agent","description":"namespace"} ] }`) if paths := codexSpawnAgentToolPaths(payload); len(paths) != 0 { t.Fatalf("invalid container paths = %v, want none", paths) } } func TestOptimizeCodexMultiAgentV2RequestSkipsNamespaceConflict(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"namespace","name":"collaboration","tools":[{"type":"function","name":"spawn_agent"}]},{"type":"namespace","name":"collaboration-optimize","tools":[]}]}`) headers := http.Header{"User-Agent": []string{"codex-tui/0.153.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, optimized := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) if optimized { t.Fatal("namespace conflict unexpectedly enabled optimization") } if string(got) != string(payload) { t.Fatalf("namespace conflict changed payload: %s", got) } } func TestOptimizeCodexCollaborationNamespaceWithoutModels(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"namespace","name":"collaboration","tools":[{"type":"function","name":"spawn_agent"}]}]}`) toolPaths := codexSpawnAgentToolPaths(payload) got, optimized := optimizeCodexCollaborationNamespace(payload, toolPaths) if !optimized { t.Fatal("collaboration namespace was not optimized") } if namespace := gjson.GetBytes(got, "tools.0.name").String(); namespace != codexOptimizedCollaborationNamespace { t.Fatalf("namespace = %q, want collaboration-optimize", namespace) } } func TestRewriteCodexSpawnAgentDescriptionWithoutModelsStillRemovesEncrypted(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"function","name":"spawn_agent","description":"unchanged","parameters":{"properties":{"message":{"encrypted":true}}}}]}`) got := rewriteCodexSpawnAgentDescription(payload, nil) if description := gjson.GetBytes(got, "tools.0.description").String(); description != "unchanged" { t.Fatalf("description = %q, want unchanged", description) } if encrypted := gjson.GetBytes(got, "tools.0.parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("message encrypted was not removed: %s", encrypted.Raw) } } func TestRewriteCodexSpawnAgentDescriptionLeavesPayloadWithoutToolUnchanged(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"function","name":"other","description":"unchanged"}]}`) models := []codexSpawnAgentModel{{id: "model-a", description: "Model A."}} got := rewriteCodexSpawnAgentDescription(payload, models) if string(got) != string(payload) { t.Fatalf("payload changed without spawn_agent tool: %s", got) } } func TestRewriteCodexSpawnAgentDescriptionEnabledOptimizesTool(t *testing.T) { modelID := "codex-spawn-agent-test-model" clientID := "codex-spawn-agent-test-client" modelRegistry := registry.GetGlobalRegistry() modelRegistry.RegisterClient(clientID, "codex", []*registry.ModelInfo{{ ID: modelID, Description: "Test agent model.", Thinking: ®istry.ThinkingSupport{ Levels: []string{"low", "medium", "high"}, }, }}) defer modelRegistry.UnregisterClient(clientID) payload := []byte(`{"tools":[{"type":"namespace","name":"collaboration","tools":[{"type":"function","name":"spawn_agent","description":"Spawns an agent.","parameters":{"properties":{"message":{"type":"string","encrypted":true}}}}]}]}`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, optimized := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) if !optimized { t.Fatal("collaboration namespace was not marked optimized") } if namespace := gjson.GetBytes(got, "tools.0.name").String(); namespace != codexOptimizedCollaborationNamespace { t.Fatalf("namespace = %q, want %q", namespace, codexOptimizedCollaborationNamespace) } description := gjson.GetBytes(got, "tools.0.tools.0.description").String() want := "- `" + modelID + "`: Test agent model. Reasoning efforts: low, medium (default), high." if !strings.Contains(description, want) { t.Fatalf("description does not contain dynamic model metadata: %q", description) } if encrypted := gjson.GetBytes(got, "tools.0.tools.0.parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("spawn_agent message encrypted was not removed: %s", encrypted.Raw) } } func TestPrepareCodexMultiAgentV2ToolsOnlyPreparesToolDefinitions(t *testing.T) { t.Parallel() payload := []byte(`{ "input":[ {"type":"agent_message","content":[{"type":"encrypted_content","encrypted_content":"task"}]}, {"type":"additional_tools","role":"developer","tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","description":"Spawns an agent.","parameters":{"properties":{"message":{"encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"properties":{"message":{"encrypted":true}}}} ]} ]} ] }`) headers := http.Header{"User-Agent": []string{"codex_cli_rs/0.144.1"}} got, prepared := PrepareCodexMultiAgentV2Tools(context.Background(), headers, payload, true, false) if !prepared { t.Fatal("Codex CLI request was not marked prepared") } if messageType := gjson.GetBytes(got, "input.0.content.0.type").String(); messageType != "encrypted_content" { t.Fatalf("agent_message content type = %q, want encrypted_content", messageType) } if namespace := gjson.GetBytes(got, "input.1.tools.0.name").String(); namespace != codexCollaborationNamespace { t.Fatalf("namespace = %q, want %q", namespace, codexCollaborationNamespace) } for _, path := range []string{"input.1.tools.0.tools.0", "input.1.tools.0.tools.1"} { if encrypted := gjson.GetBytes(got, path+".parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("%s message.encrypted was not removed: %s", path, encrypted.Raw) } } } func TestOptimizeCodexMultiAgentV2RequestSkipsPreparedToolRefresh(t *testing.T) { t.Parallel() request := httptest.NewRequest(http.MethodPost, "/v1/responses", nil) request.Header.Set("User-Agent", "codex_cli_rs/0.144.1") ginContext, _ := gin.CreateTestContext(httptest.NewRecorder()) ginContext.Request = request ginContext.Set(CodexMultiAgentV2ToolsPreparedContextKey, true) ctx := context.WithValue(context.Background(), "gin", ginContext) payload := []byte(`{"tools":[{"type":"namespace","name":"collaboration","tools":[{"type":"function","name":"spawn_agent","description":"Available model overrides (optional; inherited parent model is preferred): - old-model: Old model. Spawns an agent.","parameters":{"properties":{"message":{"encrypted":true}}}}]}]}`) cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, optimized := OptimizeCodexMultiAgentV2Request(ctx, nil, payload, cfg) if !optimized { t.Fatal("collaboration namespace was not optimized") } if description := gjson.GetBytes(got, "tools.0.tools.0.description").String(); !strings.Contains(description, "old-model") { t.Fatalf("prepared spawn_agent description was refreshed: %q", description) } if encrypted := gjson.GetBytes(got, "tools.0.tools.0.parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("message.encrypted was not removed: %s", encrypted.Raw) } } func TestOptimizeCodexMultiAgentV2RequestNormalizesAgentMessageContentOnly(t *testing.T) { t.Parallel() payload := []byte(`{"input":[{"type":"agent_message","id":"amsg_1","author":"/root","recipient":"/root/worker","content":[{"type":"input_text","text":"Payload:\n"},{"type":"encrypted_content","encrypted_content":"delegated task"}],"internal_chat_message_metadata_passthrough":{"turn_id":"turn_1"}}]}`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, namespaceOptimized := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) if namespaceOptimized { t.Fatal("payload without spawn_agent unexpectedly optimized a namespace") } message := gjson.GetBytes(got, "input.0") if message.Get("type").String() != "agent_message" || message.Get("role").Exists() { t.Fatalf("outer agent message changed: %s", got) } if message.Get("content.1.type").String() != "input_text" || message.Get("content.1.text").String() != "delegated task" { t.Fatalf("encrypted content was not normalized: %s", got) } if message.Get("content.1.encrypted_content").Exists() { t.Fatalf("encrypted_content was preserved: %s", got) } if message.Get("author").String() != "/root" || message.Get("recipient").String() != "/root/worker" || message.Get("internal_chat_message_metadata_passthrough.turn_id").String() != "turn_1" { t.Fatalf("agent message metadata changed: %s", got) } for _, tt := range []struct { name string headers http.Header cfg *config.Config }{ {name: "disabled", headers: headers, cfg: &config.Config{}}, {name: "unrelated client", headers: http.Header{"User-Agent": []string{"curl/8.7.1"}}, cfg: cfg}, } { t.Run(tt.name, func(t *testing.T) { unchanged, _ := OptimizeCodexMultiAgentV2Request(context.Background(), tt.headers, payload, tt.cfg) if string(unchanged) != string(payload) { t.Fatalf("ineligible request changed: %s", unchanged) } }) } } func TestRestoreCodexMultiAgentV2Response(t *testing.T) { t.Parallel() payload := []byte(`{ "type":"response.completed", "response":{ "output":[ {"type":"function_call","name":"spawn_agent","namespace":"collaboration-optimize","arguments":{"namespace":"collaboration-optimize","name":"collaboration-optimize__opaque"}}, {"type":"function_call","name":"collaboration-optimize__send_message"}, {"type":"message","namespace":"collaboration-optimize","name":"collaboration-optimize__plain"} ], "tools":[{"type":"namespace","name":"collaboration-optimize"}] } }`) got := RestoreCodexMultiAgentV2Response(payload, true) if namespace := gjson.GetBytes(got, "response.output.0.namespace").String(); namespace != codexCollaborationNamespace { t.Fatalf("function namespace = %q, want collaboration", namespace) } if name := gjson.GetBytes(got, "response.output.1.name").String(); name != "collaboration__send_message" { t.Fatalf("qualified function name = %q, want collaboration__send_message", name) } if name := gjson.GetBytes(got, "response.tools.0.name").String(); name != codexCollaborationNamespace { t.Fatalf("namespace tool name = %q, want collaboration", name) } if namespace := gjson.GetBytes(got, "response.output.0.arguments.namespace").String(); namespace != codexOptimizedCollaborationNamespace { t.Fatalf("opaque arguments namespace was unexpectedly rewritten: %q", namespace) } if namespace := gjson.GetBytes(got, "response.output.2.namespace").String(); namespace != codexOptimizedCollaborationNamespace { t.Fatalf("ordinary namespace field was unexpectedly rewritten: %q", namespace) } if name := gjson.GetBytes(got, "response.output.2.name").String(); name != "collaboration-optimize__plain" { t.Fatalf("ordinary name field was unexpectedly rewritten: %q", name) } if unchanged := RestoreCodexMultiAgentV2Response(payload, false); string(unchanged) != string(payload) { t.Fatalf("inactive restore changed payload: %s", unchanged) } } func TestRewriteCodexMultiAgentV2InputRewritesAgentMessage(t *testing.T) { t.Parallel() payload := []byte(`{"model":"gpt-5.4","input":[{ "type":"agent_message", "id":"amsg_019f92ae-84fd-76f0-aa66-5a722dee382e", "author":"/root", "recipient":"/root/arithmetic_problem", "content":[ {"type":"input_text","text":"Message Type: NEW_TASK\nTask name: /root/arithmetic_problem\nSender: /root\nPayload:\n"}, {"type":"encrypted_content","encrypted_content":"请出一道四则运算题,并给出答案。全程使用简体中文,题目简洁。"} ], "internal_chat_message_metadata_passthrough":{"turn_id":"019f92ae-7eae-7371-957e-8f6f734edddc"} }]}`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got := RewriteCodexMultiAgentV2Input(context.Background(), headers, payload, cfg) if messageType := gjson.GetBytes(got, "input.0.type").String(); messageType != "message" { t.Fatalf("type = %q, want message; payload=%s", messageType, got) } if role := gjson.GetBytes(got, "input.0.role").String(); role != "user" { t.Fatalf("role = %q, want user; payload=%s", role, got) } if partType := gjson.GetBytes(got, "input.0.content.1.type").String(); partType != "input_text" { t.Fatalf("content[1].type = %q, want input_text; payload=%s", partType, got) } if text := gjson.GetBytes(got, "input.0.content.1.text").String(); text != "请出一道四则运算题,并给出答案。全程使用简体中文,题目简洁。" { t.Fatalf("content[1].text = %q; payload=%s", text, got) } if encrypted := gjson.GetBytes(got, "input.0.content.1.encrypted_content"); encrypted.Exists() { t.Fatalf("content[1].encrypted_content was preserved: %s", got) } if author := gjson.GetBytes(got, "input.0.author").String(); author != "/root" { t.Fatalf("author = %q, want /root", author) } if turnID := gjson.GetBytes(got, "input.0.internal_chat_message_metadata_passthrough.turn_id").String(); turnID != "019f92ae-7eae-7371-957e-8f6f734edddc" { t.Fatalf("turn_id = %q", turnID) } } func TestRewriteCodexMultiAgentV2InputConditions(t *testing.T) { t.Parallel() payload := []byte(`{"input":[{"type":"agent_message","content":[{"type":"encrypted_content","encrypted_content":"task"}]}]}`) tests := []struct { name string cfg *config.Config userAgent string want bool }{ { name: "Codex Desktop enabled", cfg: &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}}, userAgent: "Codex Desktop/0.146.0-alpha.3", want: true, }, { name: "codex tui enabled", cfg: &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}}, userAgent: "codex-tui/0.153.3", want: true, }, { name: "optimization disabled", cfg: &config.Config{}, userAgent: "codex-tui/0.153.3", }, { name: "unrelated client", cfg: &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}}, userAgent: "curl/8.7.1", }, { name: "nil config", userAgent: "Codex Desktop/0.146.0-alpha.3", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() headers := http.Header{"User-Agent": []string{tt.userAgent}} got := RewriteCodexMultiAgentV2Input(context.Background(), headers, payload, tt.cfg) if rewritten := gjson.GetBytes(got, "input.0.type").String() == "message"; rewritten != tt.want { t.Fatalf("rewritten = %v, want %v; payload=%s", rewritten, tt.want, got) } }) } } func TestTranslateRequestWithCodexMultiAgentV2Conditions(t *testing.T) { payload := []byte(`{"model":"test-model","input":[{"type":"agent_message","content":[{"type":"encrypted_content","encrypted_content":"task"}]}]}`) enabledCfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} eligibleHeaders := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} translations := []struct { name string to sdktranslator.Format path string want string model string }{ {name: "Claude", to: sdktranslator.FormatClaude, path: "messages.0.content", want: "task", model: "claude-sonnet-4-5"}, {name: "Gemini", to: sdktranslator.FormatGemini, path: "contents.0.parts.0.text", want: "task", model: "gemini-2.5-pro"}, {name: "Antigravity", to: sdktranslator.FormatAntigravity, path: "request.contents.0.parts.0.text", want: "task", model: "gemini-2.5-pro"}, {name: "OpenAI", to: sdktranslator.FormatOpenAI, path: "messages.0.content.0.text", want: "task", model: "chat-model"}, {name: "Interactions", to: sdktranslator.FormatInteractions, path: "input.0.content.0.text", want: "task", model: "interaction-model"}, } for _, tt := range translations { t.Run(tt.name, func(t *testing.T) { got := TranslateRequestWithCodexMultiAgentV2(context.Background(), eligibleHeaders, enabledCfg, sdktranslator.FormatOpenAIResponse, tt.to, tt.model, payload, false) if value := gjson.GetBytes(got, tt.path).String(); value != tt.want { t.Fatalf("%s = %q, want %q; output=%s", tt.path, value, tt.want, got) } }) } t.Run("disabled optimization", func(t *testing.T) { got := TranslateRequestWithCodexMultiAgentV2(context.Background(), eligibleHeaders, &config.Config{}, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, "chat-model", payload, false) if count := gjson.GetBytes(got, "messages.#").Int(); count != 0 { t.Fatalf("disabled optimization translated agent_message; output=%s", got) } }) t.Run("unrelated client", func(t *testing.T) { headers := http.Header{"User-Agent": []string{"curl/8.7.1"}} got := TranslateRequestWithCodexMultiAgentV2(context.Background(), headers, enabledCfg, sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, "chat-model", payload, false) if count := gjson.GetBytes(got, "messages.#").Int(); count != 0 { t.Fatalf("unrelated client agent_message was translated; output=%s", got) } }) t.Run("non-Responses source", func(t *testing.T) { got := TranslateRequestWithCodexMultiAgentV2(context.Background(), eligibleHeaders, enabledCfg, sdktranslator.FormatOpenAI, sdktranslator.FormatOpenAI, "test-model", payload, false) if messageType := gjson.GetBytes(got, "input.0.type").String(); messageType != "agent_message" { t.Fatalf("non-Responses source changed agent_message; output=%s", got) } }) for _, target := range []sdktranslator.Format{sdktranslator.FormatCodex, sdktranslator.FormatOpenAIResponse} { t.Run("excluded target "+target.String(), func(t *testing.T) { got := TranslateRequestWithCodexMultiAgentV2(context.Background(), eligibleHeaders, enabledCfg, sdktranslator.FormatOpenAIResponse, target, "test-model", payload, false) if messageType := gjson.GetBytes(got, "input.0.type").String(); messageType != "agent_message" { t.Fatalf("target %s changed agent_message; output=%s", target, got) } }) } } func TestRewriteCodexSpawnAgentDescriptionDisabledLeavesPayloadUnchanged(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"function","name":"spawn_agent","description":"unchanged","parameters":{"properties":{"message":{"encrypted":true}}}}]}`) headers := http.Header{"User-Agent": []string{"codex-tui/0.153.3"}} got := RewriteCodexSpawnAgentDescription(context.Background(), headers, payload, &config.Config{}) if string(got) != string(payload) { t.Fatalf("disabled optimization changed payload: %s", got) } } func TestRewriteCodexSpawnAgentDescriptionIgnoresOtherUserAgent(t *testing.T) { t.Parallel() payload := []byte(`{"tools":[{"type":"function","name":"spawn_agent","description":"unchanged"}]}`) headers := http.Header{"User-Agent": []string{"curl/8.7.1"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got := RewriteCodexSpawnAgentDescription(context.Background(), headers, payload, cfg) if string(got) != string(payload) { t.Fatalf("payload changed for unrelated User-Agent: %s", got) } } func TestReplaceCodexSpawnAgentModelsNormalizesSectionsAndPreservesInstructions(t *testing.T) { t.Parallel() description := codexSpawnAgentModelsHeading + "\n- `old-model`: old\nKeep this multi-agent instruction.\nSpawns an agent.\n" + codexSpawnAgentModelsHeading got := replaceCodexSpawnAgentModels(description, "- `new-model`: New model.") if strings.Contains(got, "old-model") { t.Fatalf("old model list was preserved: %q", got) } if count := strings.Count(got, codexSpawnAgentModelsHeading); count != 1 { t.Fatalf("model heading count = %d, want 1: %q", count, got) } if !strings.Contains(got, "Keep this multi-agent instruction.") { t.Fatalf("following instruction was removed: %q", got) } } func TestCodexClientUserAgentPrefersGinRequest(t *testing.T) { gin.SetMode(gin.TestMode) request := httptest.NewRequest(http.MethodPost, "/v1/responses", nil) request.Header.Set("User-Agent", "codex-tui/0.153.3") ginCtx, _ := gin.CreateTestContext(httptest.NewRecorder()) ginCtx.Request = request ctx := context.WithValue(context.Background(), "gin", ginCtx) headers := http.Header{"User-Agent": []string{"overridden-client/1.0"}} if got := codexClientUserAgent(ctx, headers); got != "codex-tui/0.153.3" { t.Fatalf("codexClientUserAgent() = %q, want gin request User-Agent", got) } } func TestCodexCollaborationMessageToolPathsFindsAllThreeTools(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","parameters":{"properties":{"message":{"encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"properties":{"message":{"encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"properties":{"message":{"encrypted":true}}}}, {"type":"function","name":"unrelated_tool","parameters":{"properties":{"message":{"encrypted":true}}}} ]} ] }`) paths := codexCollaborationMessageToolPaths(payload) wantCount := 3 if len(paths) != wantCount { t.Fatalf("path count = %d, want %d; paths=%v", len(paths), wantCount, paths) } } func TestCodexCollaborationMessageToolPathsAdditionalTools(t *testing.T) { t.Parallel() payload := []byte(`{ "input":[ {"type":"additional_tools","role":"developer","tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"send_message","parameters":{"properties":{"message":{"encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"properties":{"message":{"encrypted":true}}}} ]} ]} ] }`) paths := codexCollaborationMessageToolPaths(payload) if len(paths) != 2 { t.Fatalf("path count = %d, want 2; paths=%v", len(paths), paths) } } func TestRemoveCodexCollaborationMessageEncryptionAllTools(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ] }`) paths := codexCollaborationMessageToolPaths(payload) got := removeCodexCollaborationMessageEncryption(payload, paths) for _, toolPath := range []string{ "tools.0.tools.0", "tools.0.tools.1", "tools.0.tools.2", } { if encrypted := gjson.GetBytes(got, toolPath+".parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("%s.parameters.properties.message.encrypted was not removed: %s", toolPath, encrypted.Raw) } if msgType := gjson.GetBytes(got, toolPath+".parameters.properties.message.type").String(); msgType != "string" { t.Fatalf("%s.parameters.properties.message.type changed: %q", toolPath, msgType) } } } func TestRemoveCodexCollaborationMessageEncryptionPreservesUnrelatedEncryptedFields(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"function","name":"send_message","parameters":{"properties":{"message":{"type":"string","encrypted":true},"data":{"encrypted":"keep-me"}}}}, {"type":"function","name":"unrelated_tool","parameters":{"properties":{"message":{"encrypted":true}}}} ] }`) paths := codexCollaborationMessageToolPaths(payload) got := removeCodexCollaborationMessageEncryption(payload, paths) if encrypted := gjson.GetBytes(got, "tools.0.parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("send_message message.encrypted was not removed: %s", encrypted.Raw) } if dataEncrypted := gjson.GetBytes(got, "tools.0.parameters.properties.data.encrypted").String(); dataEncrypted != "keep-me" { t.Fatalf("unrelated data.encrypted was changed: %q", dataEncrypted) } if unrelatedEncrypted := gjson.GetBytes(got, "tools.1.parameters.properties.message.encrypted"); !unrelatedEncrypted.Exists() { t.Fatalf("unrelated tool message.encrypted was removed: %s", got) } } func TestOptimizeCodexMultiAgentV2RequestRemovesEncryptionWithoutSpawnAgent(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ] }`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, optimized := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) if optimized { t.Fatal("namespace was unexpectedly optimized without spawn_agent") } for _, path := range []string{"tools.0.tools.0", "tools.0.tools.1"} { if encrypted := gjson.GetBytes(got, path+".parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("%s.parameters.properties.message.encrypted was not removed: %s", path, encrypted.Raw) } } } func TestOptimizeCodexMultiAgentV2RequestRemovesEncryptionInAdditionalTools(t *testing.T) { t.Parallel() payload := []byte(`{ "input":[ {"type":"additional_tools","role":"developer","tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ]} ] }`) headers := http.Header{"User-Agent": []string{"codex-tui/0.153.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, _ := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) for _, path := range []string{"input.0.tools.0.tools.0", "input.0.tools.0.tools.1"} { if encrypted := gjson.GetBytes(got, path+".parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("%s.parameters.properties.message.encrypted was not removed: %s", path, encrypted.Raw) } } } func TestOptimizeCodexMultiAgentV2RequestRemovesEncryptionFromAllThreeToolsWithSpawnAgent(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","description":"Spawns an agent.","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ] }`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} got, optimized := OptimizeCodexMultiAgentV2Request(context.Background(), headers, payload, cfg) if !optimized { t.Fatal("collaboration namespace was not optimized with spawn_agent present") } for _, path := range []string{"tools.0.tools.0", "tools.0.tools.1", "tools.0.tools.2"} { if encrypted := gjson.GetBytes(got, path+".parameters.properties.message.encrypted"); encrypted.Exists() { t.Fatalf("%s.parameters.properties.message.encrypted was not removed: %s", path, encrypted.Raw) } } if namespace := gjson.GetBytes(got, "tools.0.name").String(); namespace != codexOptimizedCollaborationNamespace { t.Fatalf("namespace = %q, want %q", namespace, codexOptimizedCollaborationNamespace) } } func TestRemoveCodexCollaborationMessageEncryptionNoOpWithoutEncrypted(t *testing.T) { t.Parallel() payload := []byte(`{ "tools":[ {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string"}}}} ] }`) paths := codexCollaborationMessageToolPaths(payload) got := removeCodexCollaborationMessageEncryption(payload, paths) if string(got) != string(payload) { t.Fatalf("payload changed when no encrypted field existed: %s", got) } } func TestCodexSpawnAgentModelsCacheInvalidation(t *testing.T) { modelRegistry := registry.GetGlobalRegistry() clientID1 := "cache-invalidation-client-1" clientID2 := "cache-invalidation-client-2" // 1. Initial registration modelRegistry.RegisterClient(clientID1, "openai", []*registry.ModelInfo{ { ID: "test-spawn-model-alpha", DisplayName: "Test Spawn Model Alpha", Description: "Initial description.", Thinking: ®istry.ThinkingSupport{ Levels: []string{"low", "medium"}, }, }, }) t.Cleanup(func() { modelRegistry.UnregisterClient(clientID1) modelRegistry.UnregisterClient(clientID2) }) formatted1 := formatCodexSpawnAgentModelsForRequest(context.Background(), nil, false) if !strings.Contains(formatted1, "test-spawn-model-alpha") { t.Fatalf("expected initial markdown to contain test-spawn-model-alpha, got: %s", formatted1) } if !strings.Contains(formatted1, "Reasoning efforts: low, medium") { t.Fatalf("expected initial reasoning efforts low, medium, got: %s", formatted1) } // 2. Cache hit returns identical content formattedHit := formatCodexSpawnAgentModelsForRequest(context.Background(), nil, false) if formattedHit != formatted1 { t.Fatalf("cache hit expected identical output, got %s vs %s", formattedHit, formatted1) } // 3. Registering second model invalidates cache modelRegistry.RegisterClient(clientID2, "openai", []*registry.ModelInfo{ { ID: "test-spawn-model-beta", DisplayName: "Test Spawn Model Beta", Description: "Second model.", }, }) formatted2 := formatCodexSpawnAgentModelsForRequest(context.Background(), nil, false) if !strings.Contains(formatted2, "test-spawn-model-beta") { t.Fatalf("expected cache invalidation to include test-spawn-model-beta, got: %s", formatted2) } // 4. Modifying model thinking levels invalidates cache modelRegistry.RegisterClient(clientID1, "openai", []*registry.ModelInfo{ { ID: "test-spawn-model-alpha", DisplayName: "Test Spawn Model Alpha", Description: "Initial description.", Thinking: ®istry.ThinkingSupport{ Levels: []string{"low", "medium", "high", "max"}, }, }, }) formatted3 := formatCodexSpawnAgentModelsForRequest(context.Background(), nil, false) if !strings.Contains(formatted3, "low, medium (default), high, max") { t.Fatalf("expected updated thinking levels to reflect in markdown, got: %s", formatted3) } // 5. Unregistering client invalidates cache modelRegistry.UnregisterClient(clientID2) formatted4 := formatCodexSpawnAgentModelsForRequest(context.Background(), nil, false) if strings.Contains(formatted4, "test-spawn-model-beta") { t.Fatalf("expected test-spawn-model-beta to be removed after unregistering, got: %s", formatted4) } } func BenchmarkCodexSpawnAgentModelsForRequest(b *testing.B) { modelRegistry := registry.GetGlobalRegistry() clientID := "bench-client-models" modelRegistry.RegisterClient(clientID, "openai", []*registry.ModelInfo{ { ID: "gpt-5.5", DisplayName: "Default model", Description: "Default model description.", }, { ID: "claude-3-7-sonnet", DisplayName: "Claude 3.7 Sonnet", Description: "Claude model description.", }, }) b.Cleanup(func() { modelRegistry.UnregisterClient(clientID) }) ctx := context.Background() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { codexSpawnAgentModelsForRequest(ctx, nil, false) } } func BenchmarkPrepareCodexMultiAgentV2Tools(b *testing.B) { modelRegistry := registry.GetGlobalRegistry() clientID := "bench-client-prepare" modelRegistry.RegisterClient(clientID, "openai", []*registry.ModelInfo{ { ID: "gpt-5.5", DisplayName: "Default model", Description: "Default model description.", }, }) b.Cleanup(func() { modelRegistry.UnregisterClient(clientID) }) payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","description":"Spawns an agent.\n","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ] }`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} ctx := context.Background() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { PrepareCodexMultiAgentV2Tools(ctx, headers, payload, true, false) } } func BenchmarkOptimizeCodexMultiAgentV2Request(b *testing.B) { modelRegistry := registry.GetGlobalRegistry() clientID := "bench-client-opt" modelRegistry.RegisterClient(clientID, "openai", []*registry.ModelInfo{ { ID: "gpt-5.5", DisplayName: "Default model", Description: "Default model description.", }, }) b.Cleanup(func() { modelRegistry.UnregisterClient(clientID) }) payload := []byte(`{ "tools":[ {"type":"namespace","name":"collaboration","tools":[ {"type":"function","name":"spawn_agent","description":"Spawns an agent.\n","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"send_message","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}}, {"type":"function","name":"followup_task","parameters":{"type":"object","properties":{"message":{"type":"string","encrypted":true}}}} ]} ] }`) headers := http.Header{"User-Agent": []string{"Codex Desktop/0.146.0-alpha.3"}} cfg := &config.Config{Codex: config.CodexConfig{OptimizeMultiAgentV2: true}} ctx := context.Background() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { OptimizeCodexMultiAgentV2Request(ctx, headers, payload, cfg) } }