From ceaeb75d5371fb01fead81e7c7bac8496a78663f Mon Sep 17 00:00:00 2001 From: huahai0202 <2023217276@mail.hfut.edu.cn> Date: Mon, 13 Jul 2026 19:22:58 +0800 Subject: [PATCH] fix(codex): gate tool search by model providers --- .../handlers/openai/codex_client_models.go | 40 ++++++++++- .../openai/codex_client_models_test.go | 69 +++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/sdk/api/handlers/openai/codex_client_models.go b/sdk/api/handlers/openai/codex_client_models.go index 642092df..d4450481 100644 --- a/sdk/api/handlers/openai/codex_client_models.go +++ b/sdk/api/handlers/openai/codex_client_models.go @@ -13,6 +13,8 @@ type codexClientModelsPayload struct { Models []map[string]any `json:"models"` } +type codexClientModelProvidersFunc func(string) []string + var ( codexClientModelTemplatesMu sync.Mutex codexClientModelTemplatesLoaded bool @@ -33,16 +35,20 @@ var codexClientAllowedReasoningLevels = map[string]struct{}{ } func (h *OpenAIAPIHandler) codexClientModelsResponse() map[string]any { - return CodexClientModelsResponse(h.Models()) + return codexClientModelsResponse(h.Models(), registry.GetGlobalRegistry().GetModelProviders) } func CodexClientModelsResponse(models []map[string]any) map[string]any { + return codexClientModelsResponse(models, nil) +} + +func codexClientModelsResponse(models []map[string]any, providersForModel codexClientModelProvidersFunc) map[string]any { return map[string]any{ - "models": buildCodexClientModels(models), + "models": buildCodexClientModels(models, providersForModel), } } -func buildCodexClientModels(models []map[string]any) []map[string]any { +func buildCodexClientModels(models []map[string]any, providersForModel codexClientModelProvidersFunc) []map[string]any { templates, defaultTemplate, err := loadCodexClientModelTemplates() if err != nil || defaultTemplate == nil { return nil @@ -58,6 +64,7 @@ func buildCodexClientModels(models []map[string]any) []map[string]any { if template, ok := templates[id]; ok { entry := cloneCodexClientModelMap(template) applyCodexClientDisplayName(entry, model) + applyCodexClientSearchToolSupport(entry, id, true, providersForModel) sanitizeCodexClientReasoningMetadata(entry) applyCodexClientVisibilityOverride(entry, id) result = append(result, entry) @@ -66,6 +73,7 @@ func buildCodexClientModels(models []map[string]any) []map[string]any { entry := cloneCodexClientModelMap(defaultTemplate) applyCodexClientModelMetadata(entry, id, model) + applyCodexClientSearchToolSupport(entry, id, false, providersForModel) sanitizeCodexClientReasoningMetadata(entry) applyCodexClientVisibilityOverride(entry, id) result = append(result, entry) @@ -178,6 +186,32 @@ func applyCodexClientDisplayName(entry map[string]any, model map[string]any) { } } +func applyCodexClientSearchToolSupport(entry map[string]any, id string, templateModel bool, providersForModel codexClientModelProvidersFunc) { + supportsSearch, _ := entry["supports_search_tool"].(bool) + if !supportsSearch { + return + } + + if providersForModel == nil { + if !templateModel { + entry["supports_search_tool"] = false + } + return + } + + providers := providersForModel(id) + if len(providers) == 0 { + entry["supports_search_tool"] = false + return + } + for _, provider := range providers { + if !strings.EqualFold(strings.TrimSpace(provider), "codex") { + entry["supports_search_tool"] = false + return + } + } +} + func applyCodexClientModelMetadata(entry map[string]any, id string, model map[string]any) { info := registry.LookupModelInfo(id) diff --git a/sdk/api/handlers/openai/codex_client_models_test.go b/sdk/api/handlers/openai/codex_client_models_test.go index 0cdd4131..d8bbe7ed 100644 --- a/sdk/api/handlers/openai/codex_client_models_test.go +++ b/sdk/api/handlers/openai/codex_client_models_test.go @@ -143,6 +143,75 @@ func TestCodexClientModelsResponse_AppliesDisplayNameToTemplateModel(t *testing. } } +func TestCodexClientModelsResponse_DisablesSearchToolForSynthesizedModels(t *testing.T) { + resp := CodexClientModelsResponse([]map[string]any{ + {"id": "custom-openai-compatible-model"}, + {"id": "gpt-5.5"}, + }) + models, ok := resp["models"].([]map[string]any) + if !ok { + t.Fatalf("models type = %T, want []map[string]any", resp["models"]) + } + + bySlug := make(map[string]map[string]any, len(models)) + for _, model := range models { + bySlug[stringModelValue(model, "slug")] = model + } + + custom := bySlug["custom-openai-compatible-model"] + if custom == nil { + t.Fatal("expected synthesized custom model entry") + } + if got, ok := custom["supports_search_tool"].(bool); !ok || got { + t.Fatalf("custom supports_search_tool = %#v, want false", custom["supports_search_tool"]) + } + + official := bySlug["gpt-5.5"] + if official == nil { + t.Fatal("expected official template model entry") + } + if got, ok := official["supports_search_tool"].(bool); !ok || !got { + t.Fatalf("official supports_search_tool = %#v, want true", official["supports_search_tool"]) + } +} + +func TestCodexClientModelsResponse_UsesProviderSearchToolSupport(t *testing.T) { + providers := map[string][]string{ + "new-codex-model": {"codex"}, + "gpt-5.5": {"openai-compatible-deepseek"}, + "gpt-5.4": {"codex", "xai"}, + "gpt-5.6-sol": {"codex"}, + } + resp := codexClientModelsResponse([]map[string]any{ + {"id": "new-codex-model"}, + {"id": "gpt-5.5"}, + {"id": "gpt-5.4"}, + {"id": "gpt-5.6-sol"}, + }, func(id string) []string { + return providers[id] + }) + models, ok := resp["models"].([]map[string]any) + if !ok { + t.Fatalf("models type = %T, want []map[string]any", resp["models"]) + } + + bySlug := make(map[string]map[string]any, len(models)) + for _, model := range models { + bySlug[stringModelValue(model, "slug")] = model + } + + for _, slug := range []string{"new-codex-model", "gpt-5.6-sol"} { + if got, ok := bySlug[slug]["supports_search_tool"].(bool); !ok || !got { + t.Errorf("%s supports_search_tool = %#v, want true", slug, bySlug[slug]["supports_search_tool"]) + } + } + for _, slug := range []string{"gpt-5.5", "gpt-5.4"} { + if got, ok := bySlug[slug]["supports_search_tool"].(bool); !ok || got { + t.Errorf("%s supports_search_tool = %#v, want false", slug, bySlug[slug]["supports_search_tool"]) + } + } +} + func TestCodexClientModelsResponse_PreservesUltraReasoningEffort(t *testing.T) { resp := CodexClientModelsResponse([]map[string]any{{"id": "gpt-5.6-sol"}}) models, ok := resp["models"].([]map[string]any)