diff --git a/.inline-snapshot/external/.gitignore b/.inline-snapshot/external/.gitignore deleted file mode 100644 index 92f893631..000000000 --- a/.inline-snapshot/external/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# ignore all snapshots which are not referred in the source -*-new.* diff --git a/src/anthropic/lib/_parse/_transform.py b/src/anthropic/lib/_parse/_transform.py index ce0c83ac9..366111d3b 100644 --- a/src/anthropic/lib/_parse/_transform.py +++ b/src/anthropic/lib/_parse/_transform.py @@ -32,6 +32,17 @@ } +TYPE_SPECIFIC_KEYWORDS: dict[str, set[str]] = { + "object": {"properties", "additionalProperties", "required", "patternProperties", "minProperties", "maxProperties"}, + "array": {"items", "minItems", "maxItems", "uniqueItems", "contains"}, + "string": {"format", "pattern", "minLength", "maxLength"}, + "integer": {"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"}, + "number": {"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"}, + "boolean": set(), + "null": set(), +} + + def get_transformed_string( schema: dict[str, Any], ) -> dict[str, Any]: @@ -96,7 +107,7 @@ 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) @@ -107,6 +118,22 @@ def transform_schema( strict_schema["anyOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in one_of] elif is_list(all_of): strict_schema["allOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in all_of] + elif is_list(type_): + variants: list[dict[str, Any]] = [] + extracted_keywords: dict[str, Any] = {} + for t in type_: + for kw in TYPE_SPECIFIC_KEYWORDS.get(t, set()): + if kw in json_schema: + extracted_keywords[kw] = json_schema.pop(kw) + + for t in type_: + branch: dict[str, Any] = {"type": t} + for kw in TYPE_SPECIFIC_KEYWORDS.get(t, set()): + if kw in extracted_keywords: + branch[kw] = extracted_keywords[kw] + variants.append(transform_schema(branch)) + + strict_schema["anyOf"] = variants else: if type_ is None: raise ValueError("Schema must have a 'type', 'anyOf', 'oneOf', or 'allOf' field.") @@ -155,7 +182,7 @@ def transform_schema( # add it back so its treated as an extra property and appended to the description json_schema["minItems"] = min_items - elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None: + elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None or is_list(type_): pass else: assert_never(type_) diff --git a/tests/lib/_parse/test_transform.py b/tests/lib/_parse/test_transform.py index 7a2799dce..346f685bc 100644 --- a/tests/lib/_parse/test_transform.py +++ b/tests/lib/_parse/test_transform.py @@ -229,3 +229,107 @@ def test_original_schema_not_mutated(): transform_schema(original_schema) assert original_schema == original_schema_backup + + +def test_type_array(): + schema = { + "type": ["string", "null"], + "description": "Optional text", + } + result = transform_schema(schema) + assert result == { + "anyOf": [{"type": "string"}, {"type": "null"}], + "description": "Optional text", + } + + +def test_type_array_in_object(): + schema = { + "type": "object", + "properties": { + "name": { + "type": ["string", "null"], + "description": "Optional name", + } + }, + "required": ["name"], + } + result = transform_schema(schema) + assert result == { + "type": "object", + "properties": { + "name": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "description": "Optional name", + } + }, + "required": ["name"], + "additionalProperties": False, + } + + +def test_type_array_with_items(): + schema = { + "type": ["array", "null"], + "items": {"type": "string"}, + "description": "Optional list of strings", + } + result = transform_schema(schema) + assert result == { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + "description": "Optional list of strings", + } + + +def test_type_array_with_object_properties(): + schema = { + "type": ["object", "null"], + "properties": { + "title": {"type": "string"}, + }, + "required": ["title"], + } + result = transform_schema(schema) + assert result == { + "anyOf": [ + { + "type": "object", + "properties": {"title": {"type": "string"}}, + "required": ["title"], + "additionalProperties": False, + }, + {"type": "null"}, + ], + } + + +def test_type_array_nested_in_object_with_items(): + schema = { + "type": "object", + "properties": { + "tags": { + "type": ["array", "null"], + "items": {"type": "string"}, + } + }, + "required": ["tags"], + } + result = transform_schema(schema) + assert result == { + "type": "object", + "properties": { + "tags": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + } + }, + "required": ["tags"], + "additionalProperties": False, + } + +