Files
CLIProxyAPI/internal/client/codex/optimize-multi-agent-v2/optimize_multi_agent_v2_test.go
Luis Pater 98c98d66be fix(codex): cache multi-agent spawn-agent model data and invalidate on updates
- Add registry generation tracking and a Codex catalog revision accessor to provide stable cache keys.
- Cache parsed model templates and rendered spawn-agent markdown, and reuse them across requests when revision/generation are unchanged.
- Invalidate/recompute caches when model registrations or catalog content change, and separate rewrite handling for spawn tool descriptions vs. message encryption stripping.

Closes: #4967
2026-08-15 03:49:56 +08:00

1010 lines
42 KiB
Go

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.145.0 (Mac OS 26.5.2; arm64) iTerm.app/3.6.11 (codex-tui; 0.145.0)",
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 &registry.ModelInfo{
Description: "Dynamic model.",
Thinking: &registry.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.145.0"}}
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: &registry.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.145.0",
want: true,
},
{
name: "optimization disabled",
cfg: &config.Config{},
userAgent: "codex-tui/0.145.0",
},
{
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.145.0"}}
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.145.0")
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.145.0" {
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.145.0"}}
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: &registry.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: &registry.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)
}
}