From c7681b728bf68a0b72cfd01ca825606a4bc07efe Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Tue, 26 May 2026 03:51:43 -0700 Subject: [PATCH] Drop empty-name tools before forwarding to upstream Codex's Responses API includes structured/typed tools (e.g. image_generation, web_search, file_search, computer_use) that have no `function.name` field because they are identified by `type` instead. The converter preserves the type info via Extensions but leaves Name empty, which causes upstream Anthropic-compatible endpoints like DeepSeek to reject the entire request with `tools[N].function.name: empty string`. These hosted tool types are OpenAI-specific and not executable on other providers anyway, so skip them in flattenToolsWithNamespace alongside the existing dedup pass. --- internal/protocol/openai/adapter.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/protocol/openai/adapter.go b/internal/protocol/openai/adapter.go index 32038518..2ea5f22b 100644 --- a/internal/protocol/openai/adapter.go +++ b/internal/protocol/openai/adapter.go @@ -12,6 +12,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "strings" "sync" @@ -1639,6 +1640,17 @@ func flattenToolsWithNamespace(openaiTools []Tool, namespace string, disablePatc seen := make(map[string]bool, len(result)) deduped := make([]format.CoreTool, 0, len(result)) for _, t := range result { + if strings.TrimSpace(t.Name) == "" { + // Codex sends OpenAI-only typed tools (e.g. image_generation, web_search) + // that arrive without a function.name; upstream Anthropic-compatible + // endpoints reject these. Drop them silently rather than failing the call. + extBytes, _ := json.Marshal(t.Extensions) + slog.Default().Debug("dropping empty-name tool", + "namespace", namespace, + "extensions", string(extBytes), + ) + continue + } if seen[t.Name] { continue }