From 793840cdb4e9083948be4b8e581b2e6ae88b49a1 Mon Sep 17 00:00:00 2001 From: Junyi Du Date: Thu, 19 Mar 2026 03:41:12 +0800 Subject: [PATCH] fix: cover dated and nested codex web search aliases --- .../codex_openai-responses_request.go | 30 ++++++++++++++++--- .../codex_openai-responses_request_test.go | 30 +++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/internal/translator/codex/openai/responses/codex_openai-responses_request.go b/internal/translator/codex/openai/responses/codex_openai-responses_request.go index f6ea4771..64d5f824 100644 --- a/internal/translator/codex/openai/responses/codex_openai-responses_request.go +++ b/internal/translator/codex/openai/responses/codex_openai-responses_request.go @@ -94,19 +94,41 @@ func normalizeCodexBuiltinTools(rawJSON []byte) []byte { toolArray := tools.Array() for i := 0; i < len(toolArray); i++ { typePath := fmt.Sprintf("tools.%d.type", i) - if gjson.GetBytes(result, typePath).String() == "web_search_preview" { - if updated, err := sjson.SetBytes(result, typePath, "web_search"); err == nil { + if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, typePath).String()); normalized != "" { + if updated, err := sjson.SetBytes(result, typePath, normalized); err == nil { result = updated } } } } - if gjson.GetBytes(result, "tool_choice.type").String() == "web_search_preview" { - if updated, err := sjson.SetBytes(result, "tool_choice.type", "web_search"); err == nil { + if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, "tool_choice.type").String()); normalized != "" { + if updated, err := sjson.SetBytes(result, "tool_choice.type", normalized); err == nil { result = updated } } + toolChoiceTools := gjson.GetBytes(result, "tool_choice.tools") + if toolChoiceTools.IsArray() { + toolArray := toolChoiceTools.Array() + for i := 0; i < len(toolArray); i++ { + typePath := fmt.Sprintf("tool_choice.tools.%d.type", i) + if normalized := normalizeCodexBuiltinToolType(gjson.GetBytes(result, typePath).String()); normalized != "" { + if updated, err := sjson.SetBytes(result, typePath, normalized); err == nil { + result = updated + } + } + } + } + return result } + +func normalizeCodexBuiltinToolType(toolType string) string { + switch toolType { + case "web_search_preview", "web_search_preview_2025_03_11": + return "web_search" + default: + return "" + } +} diff --git a/internal/translator/codex/openai/responses/codex_openai-responses_request_test.go b/internal/translator/codex/openai/responses/codex_openai-responses_request_test.go index 49587c9b..3b48a76e 100644 --- a/internal/translator/codex/openai/responses/codex_openai-responses_request_test.go +++ b/internal/translator/codex/openai/responses/codex_openai-responses_request_test.go @@ -269,9 +269,15 @@ func TestConvertOpenAIResponsesRequestToCodex_NormalizesWebSearchPreview(t *test "model": "gpt-5.4-mini", "input": "find latest OpenAI model news", "tools": [ - {"type": "web_search_preview"} + {"type": "web_search_preview_2025_03_11"} ], - "tool_choice": {"type": "web_search_preview"} + "tool_choice": { + "type": "allowed_tools", + "tools": [ + {"type": "web_search_preview"}, + {"type": "web_search_preview_2025_03_11"} + ] + } }`) output := ConvertOpenAIResponsesRequestToCodex("gpt-5.4-mini", inputJSON, false) @@ -279,6 +285,26 @@ func TestConvertOpenAIResponsesRequestToCodex_NormalizesWebSearchPreview(t *test if got := gjson.GetBytes(output, "tools.0.type").String(); got != "web_search" { t.Fatalf("tools.0.type = %q, want %q: %s", got, "web_search", string(output)) } + if got := gjson.GetBytes(output, "tool_choice.type").String(); got != "allowed_tools" { + t.Fatalf("tool_choice.type = %q, want %q: %s", got, "allowed_tools", string(output)) + } + if got := gjson.GetBytes(output, "tool_choice.tools.0.type").String(); got != "web_search" { + t.Fatalf("tool_choice.tools.0.type = %q, want %q: %s", got, "web_search", string(output)) + } + if got := gjson.GetBytes(output, "tool_choice.tools.1.type").String(); got != "web_search" { + t.Fatalf("tool_choice.tools.1.type = %q, want %q: %s", got, "web_search", string(output)) + } +} + +func TestConvertOpenAIResponsesRequestToCodex_NormalizesTopLevelToolChoicePreviewAlias(t *testing.T) { + inputJSON := []byte(`{ + "model": "gpt-5.4-mini", + "input": "find latest OpenAI model news", + "tool_choice": {"type": "web_search_preview_2025_03_11"} + }`) + + output := ConvertOpenAIResponsesRequestToCodex("gpt-5.4-mini", inputJSON, false) + if got := gjson.GetBytes(output, "tool_choice.type").String(); got != "web_search" { t.Fatalf("tool_choice.type = %q, want %q: %s", got, "web_search", string(output)) }