From b0eb2f873089fb323c56cba7ad676993913746ba Mon Sep 17 00:00:00 2001 From: codexbt Date: Fri, 28 Aug 2026 19:29:16 +0530 Subject: [PATCH 1/2] fix(lib): support JSON Schema type arrays in transform_schema (Closes #1876) --- .inline-snapshot/external/.gitignore | 2 -- src/anthropic/lib/_parse/_transform.py | 6 ++-- tests/lib/_parse/test_transform.py | 38 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) delete mode 100644 .inline-snapshot/external/.gitignore 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..1b107d0d9 100644 --- a/src/anthropic/lib/_parse/_transform.py +++ b/src/anthropic/lib/_parse/_transform.py @@ -96,7 +96,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 +107,8 @@ 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_): + strict_schema["anyOf"] = [transform_schema({"type": t}) for t in type_] else: if type_ is None: raise ValueError("Schema must have a 'type', 'anyOf', 'oneOf', or 'allOf' field.") @@ -155,7 +157,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..2a8fe0270 100644 --- a/tests/lib/_parse/test_transform.py +++ b/tests/lib/_parse/test_transform.py @@ -229,3 +229,41 @@ 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, + } + From 5d5512d4df26448c176d465c632f72fdf2fe423a Mon Sep 17 00:00:00 2001 From: Synxneuos Date: Fri, 4 Sep 2026 00:02:29 +0530 Subject: [PATCH 2/2] fix(lib): preserve type-specific sibling keywords when expanding type arrays into anyOf --- src/anthropic/lib/_parse/_transform.py | 27 ++++++++++- tests/lib/_parse/test_transform.py | 66 ++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/anthropic/lib/_parse/_transform.py b/src/anthropic/lib/_parse/_transform.py index 1b107d0d9..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]: @@ -108,7 +119,21 @@ def transform_schema( elif is_list(all_of): strict_schema["allOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in all_of] elif is_list(type_): - strict_schema["anyOf"] = [transform_schema({"type": t}) for t in 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.") diff --git a/tests/lib/_parse/test_transform.py b/tests/lib/_parse/test_transform.py index 2a8fe0270..346f685bc 100644 --- a/tests/lib/_parse/test_transform.py +++ b/tests/lib/_parse/test_transform.py @@ -267,3 +267,69 @@ def test_type_array_in_object(): "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, + } + +