diff --git a/src/anthropic/lib/_parse/_transform.py b/src/anthropic/lib/_parse/_transform.py index ce0c83ac..37b44852 100644 --- a/src/anthropic/lib/_parse/_transform.py +++ b/src/anthropic/lib/_parse/_transform.py @@ -96,11 +96,33 @@ def transform_schema( strict_schema["$ref"] = ref return strict_schema - type_: Optional[SupportedTypes] = json_schema.pop("type", None) + type_: Optional[SupportedTypes | list[SupportedTypes]] = json_schema.pop("type", None) any_of = json_schema.pop("anyOf", None) one_of = json_schema.pop("oneOf", None) all_of = json_schema.pop("allOf", None) + if is_list(type_): + if not type_: + raise ValueError("Schema 'type' array must contain at least one type.") + type_constraints = { + key: json_schema.pop(key) for key in tuple(json_schema) if key not in ("enum", "description", "title") + } + type_union = { + "anyOf": [{"type": variant, **(type_constraints if variant != "null" else {})} for variant in type_] + } + if is_list(any_of): + all_of = [type_union, {"anyOf": any_of}] + any_of = None + elif is_list(one_of): + all_of = [type_union, {"oneOf": one_of}] + one_of = None + elif is_list(all_of): + all_of = [type_union, *all_of] + else: + any_of = type_union["anyOf"] + type_ = None + type_ = cast("Optional[SupportedTypes]", type_) + if is_list(any_of): strict_schema["anyOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in any_of] elif is_list(one_of): diff --git a/tests/lib/_parse/test_transform.py b/tests/lib/_parse/test_transform.py index 7a2799dc..4c9e395f 100644 --- a/tests/lib/_parse/test_transform.py +++ b/tests/lib/_parse/test_transform.py @@ -61,6 +61,30 @@ def test_anyof_schema(): ) +def test_type_array_schema(): + assert transform_schema({"type": ["string", "null"]}) == snapshot( + {"anyOf": [{"type": "string"}, {"type": "null"}]} + ) + assert transform_schema({"type": ["string"]}) == {"anyOf": [{"type": "string"}]} + with pytest.raises(ValueError, match="must contain at least one type"): + transform_schema({"type": []}) + + +def test_type_array_schema_with_composition(): + union = {"anyOf": [{"type": "string"}, {"type": "null"}]} + for keyword in ("anyOf", "oneOf", "allOf"): + schema = {"type": ["string", "null"], keyword: [{"type": "string"}]} + assert transform_schema(schema)["allOf"][0] == union + + +def test_nested_type_array_schema(): + constraints = {"properties": {"name": {"type": "string"}}, "required": ["name"]} + schema = {"type": "object", "properties": {"profile": {"type": ["object", "null"], **constraints}}} + object_branch = {"type": "object", **constraints, "additionalProperties": False} + expected = {"type": "object", "properties": {"profile": {"anyOf": [object_branch, {"type": "null"}]}}, "additionalProperties": False} + assert transform_schema(schema) == snapshot(expected) + + def test_enum_schema(): schema = { "type": "string",