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