diff --git a/src/anthropic/resources/messages/messages.py b/src/anthropic/resources/messages/messages.py index e077f6a34..2ab627ac0 100644 --- a/src/anthropic/resources/messages/messages.py +++ b/src/anthropic/resources/messages/messages.py @@ -1116,6 +1116,7 @@ def parse( max_tokens: int, messages: Iterable[MessageParam], model: ModelParam, + cache_control: Optional[CacheControlEphemeralParam] | Omit = omit, metadata: MetadataParam | Omit = omit, output_config: OutputConfigParam | Omit = omit, output_format: Optional[type[ResponseFormatT]] | Omit = omit, @@ -1202,6 +1203,7 @@ def parser(response: Message) -> ParsedMessage[ResponseFormatT]: "max_tokens": max_tokens, "messages": messages, "model": model, + "cache_control": cache_control, "metadata": metadata, "output_config": merged_output_config, "service_tier": service_tier, @@ -2517,6 +2519,7 @@ async def parse( max_tokens: int, messages: Iterable[MessageParam], model: ModelParam, + cache_control: Optional[CacheControlEphemeralParam] | Omit = omit, metadata: MetadataParam | Omit = omit, output_config: OutputConfigParam | Omit = omit, output_format: Optional[type[ResponseFormatT]] | Omit = omit, @@ -2603,6 +2606,7 @@ def parser(response: Message) -> ParsedMessage[ResponseFormatT]: "max_tokens": max_tokens, "messages": messages, "model": model, + "cache_control": cache_control, "metadata": metadata, "output_config": merged_output_config, "service_tier": service_tier, diff --git a/tests/api_resources/test_messages_parse_cache_control.py b/tests/api_resources/test_messages_parse_cache_control.py new file mode 100644 index 000000000..58d13bf3e --- /dev/null +++ b/tests/api_resources/test_messages_parse_cache_control.py @@ -0,0 +1,64 @@ +"""Regression: Messages.parse / AsyncMessages.parse must accept cache_control.""" + +from __future__ import annotations + +import json + +import httpx2 +import pytest +from pydantic import BaseModel +from respx import MockRouter + +from anthropic import Anthropic, AsyncAnthropic, _compat + + +MESSAGE_JSON = { + "id": "msg_123", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-5", + "content": [{"text": '{"name": "Ada"}', "type": "text"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5}, +} + + +@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="parse with Pydantic models requires Pydantic v2") +def test_parse_forwards_cache_control(client: Anthropic, respx_mock: MockRouter) -> None: + class User(BaseModel): + name: str + + respx_mock.post("/v1/messages").mock(return_value=httpx2.Response(200, json=MESSAGE_JSON)) + + client.messages.parse( + max_tokens=64, + messages=[{"role": "user", "content": "hi"}], + model="claude-sonnet-4-5", + output_format=User, + cache_control={"type": "ephemeral"}, + ) + + body = json.loads(respx_mock.calls.last.request.content) + assert body["cache_control"] == {"type": "ephemeral"} + + +@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="parse with Pydantic models requires Pydantic v2") +@pytest.mark.asyncio +async def test_async_parse_forwards_cache_control( + async_client: AsyncAnthropic, respx_mock: MockRouter +) -> None: + class User(BaseModel): + name: str + + respx_mock.post("/v1/messages").mock(return_value=httpx2.Response(200, json=MESSAGE_JSON)) + + await async_client.messages.parse( + max_tokens=64, + messages=[{"role": "user", "content": "hi"}], + model="claude-sonnet-4-5", + output_format=User, + cache_control={"type": "ephemeral"}, + ) + + body = json.loads(respx_mock.calls.last.request.content) + assert body["cache_control"] == {"type": "ephemeral"}