From a219a0a4df2aeacd5ff7dcdbaf75e0bb6e6ef876 Mon Sep 17 00:00:00 2001 From: Grey0202 Date: Mon, 4 May 2026 03:17:12 -0700 Subject: [PATCH] fix(anthropic): strip top-level oneOf/allOf/anyOf from tool input_schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the existing _normalize_tool_input_schema to also drop top-level union keywords that Anthropic's tool schema validator rejects with HTTP 400. Several upstream and plugin tools ship schemas with a top-level oneOf/ allOf/anyOf (common for Pydantic discriminated unions). The existing strip_nullable_unions pass only handles anyOf-with-null patterns; a non-null top-level union keyword sails through and hits the API. Salvage of #16471 — approach folded into the existing normalize helper rather than introducing a parallel _sanitize_input_schema function, to avoid two schema-munging code paths running against the same input. Co-authored-by: Grey0202 --- agent/anthropic_adapter.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 7cdac560b1..8c468e8686 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -1225,6 +1225,14 @@ def _normalize_tool_input_schema(schema: Any) -> Dict[str, Any]: ``keep_nullable_hint=False`` because the Anthropic validator does not recognize the OpenAPI-style ``nullable: true`` extension and strict schema-to-grammar converters may reject unknown keywords. + + Top-level ``oneOf``/``allOf``/``anyOf`` are also stripped here: the + Anthropic API rejects union keywords at the schema root with a generic + HTTP 400. Several upstream and plugin tools ship schemas with one of + these keywords at the top level (commonly for Pydantic discriminated + unions). If we land here with those keywords still present after + nullable-union stripping, drop them and fall back to a plain object + schema so the tool still validates at the Anthropic boundary. """ if not schema: return {"type": "object", "properties": {}} @@ -1234,6 +1242,12 @@ def _normalize_tool_input_schema(schema: Any) -> Dict[str, Any]: normalized = strip_nullable_unions(schema, keep_nullable_hint=False) if not isinstance(normalized, dict): return {"type": "object", "properties": {}} + # Strip top-level union keywords that Anthropic's validator rejects. + banned = {"oneOf", "allOf", "anyOf"} + if banned & normalized.keys(): + normalized = {k: v for k, v in normalized.items() if k not in banned} + if "type" not in normalized: + normalized["type"] = "object" if normalized.get("type") == "object" and not isinstance(normalized.get("properties"), dict): normalized = {**normalized, "properties": {}} return normalized