diff --git a/litellm/proxy/hooks/message_thread_ownership.py b/litellm/proxy/hooks/message_thread_ownership.py index ff68611d22d..a475ae20090 100644 --- a/litellm/proxy/hooks/message_thread_ownership.py +++ b/litellm/proxy/hooks/message_thread_ownership.py @@ -20,7 +20,6 @@ if TYPE_CHECKING: from litellm.proxy.utils import InternalUsageCache _PASS_THROUGH_CALL_TYPE: Final = "pass_through_endpoint" -_MESSAGES_CALL_TYPE: Final = "anthropic_messages" _ANTHROPIC_BETA_HEADER: Final = "anthropic-beta" _OWNER_KEY_PREFIX: Final = "anthropic_message_thread_owner:" _OWNER_TTL_SECONDS: Final = 3600 @@ -217,7 +216,7 @@ class MessageThreadOwnership(CustomLogger): if previous_message_id is None: return None binding: Final = await self._binding(previous_message_id, user_api_key_dict) - if binding is None and call_type != _MESSAGES_CALL_TYPE: + if binding is None and call_type == _PASS_THROUGH_CALL_TYPE: return None if binding is None or binding.owner != _owner(user_api_key_dict): raise HTTPException(status_code=404, detail=message_thread_not_found_body(_UNOWNED_THREAD_MESSAGE)) diff --git a/tests/unit/proxy/hooks/test_message_thread_ownership.py b/tests/unit/proxy/hooks/test_message_thread_ownership.py index 9ff49ad55d6..aba4cfc9958 100644 --- a/tests/unit/proxy/hooks/test_message_thread_ownership.py +++ b/tests/unit/proxy/hooks/test_message_thread_ownership.py @@ -131,15 +131,19 @@ async def test_without_a_key_only_the_signed_in_identity_that_created_a_thread_c assert not await allows(ownership, bob, "msg_signed_in") +@pytest.mark.parametrize("call_type", ("anthropic_messages", "acompletion", "aresponses")) @pytest.mark.asyncio -async def test_a_continue_naming_a_message_this_proxy_never_served_in_a_thread_is_thread_not_found() -> None: +async def test_a_continue_naming_a_message_this_proxy_never_served_in_a_thread_is_thread_not_found( + call_type: CallTypesLiteral, +) -> None: ownership: Final = hook() await ownership.async_post_call_success_hook( data={}, user_api_key_dict=OWNER, response=anthropic_message("msg_unthreaded") ) - assert not await allows(ownership, OWNER, "msg_unthreaded") - assert not await allows(ownership, OWNER, "msg_unknown") + assert not await allows(ownership, OWNER, "msg_unthreaded", call_type) + assert not await allows(ownership, OWNER, "msg_unknown", call_type) + assert not await allows(ownership, INTRUDER, "msg_unknown", call_type) @pytest.mark.asyncio @@ -182,13 +186,10 @@ async def test_a_thread_continue_on_another_route_stays_with_the_key_that_create assert not await allows(ownership, INTRUDER, "msg_cross_route", call_type) -@pytest.mark.parametrize("call_type", ("pass_through_endpoint", "acompletion", "aresponses")) @pytest.mark.asyncio -async def test_a_continue_outside_the_messages_route_of_a_message_the_proxy_never_bound_is_left_to_the_upstream( - call_type: CallTypesLiteral, -) -> None: - assert await allows(hook(), OWNER, "msg_unknown", call_type) - assert await allows(hook(), INTRUDER, "msg_unknown", call_type) +async def test_a_pass_through_continue_of_a_message_the_proxy_never_bound_is_left_to_the_upstream() -> None: + assert await allows(hook(), OWNER, "msg_unknown", "pass_through_endpoint") + assert await allows(hook(), INTRUDER, "msg_unknown", "pass_through_endpoint") @pytest.mark.asyncio diff --git a/tests/unit/responses/test_responses_utils.py b/tests/unit/responses/test_responses_utils.py index 60337e8fa15..0863abd1f1e 100644 --- a/tests/unit/responses/test_responses_utils.py +++ b/tests/unit/responses/test_responses_utils.py @@ -968,23 +968,6 @@ class TestMergeProviderSpecificHeaders: extra_headers=None, provider_specific_header=None, custom_llm_provider="chatgpt" ) is None - def test_client_anthropic_betas_join_the_extra_header_betas(self): - merged = ResponsesAPIRequestUtils.merge_provider_specific_headers( - extra_headers={"anthropic-beta": "advisor-tool-2026-03-01", "x-tenant": "team-a"}, - provider_specific_header={ - "custom_llm_provider": "anthropic", - "extra_headers": {"anthropic-beta": "message-threads-2026-08-12"}, - }, - custom_llm_provider="anthropic", - ) - - assert merged is not None - assert merged["x-tenant"] == "team-a" - assert set(str(merged["anthropic-beta"]).split(",")) == { - "advisor-tool-2026-03-01", - "message-threads-2026-08-12", - } - def test_responses_object_bills_an_explicit_cache_hit_at_the_explicit_rate(): model = "dashscope/qwen-responses-explicit-cache-test" diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index aa217fa54fa..85864b2024e 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -5384,41 +5384,6 @@ def test_anthropic_oauth_credential_does_not_persist_into_next_provider_hop(): assert "anthropic-version" not in shared_headers -def test_anthropic_chat_sends_the_client_betas_beside_the_extra_header_betas(): - from litellm.types.utils import ProviderSpecificHeader - - def echo_sent_betas(request: httpx.Request) -> httpx.Response: - return httpx.Response( - 200, - json={ - "id": "msg_01EchoedBetas", - "type": "message", - "role": "assistant", - "model": "claude-haiku-5-5", - "content": [{"type": "text", "text": request.headers.get("anthropic-beta", "")}], - "stop_reason": "end_turn", - "usage": {"input_tokens": 1, "output_tokens": 1}, - }, - ) - - client: Final = HTTPHandler() - client.client = httpx.Client(transport=httpx.MockTransport(echo_sent_betas)) - - response: Final = litellm.completion( - model="anthropic/claude-haiku-5-5", - messages=[{"role": "user", "content": "Say OK"}], - extra_headers={"anthropic-beta": "advisor-tool-2026-03-01"}, - provider_specific_header=ProviderSpecificHeader( - custom_llm_provider="anthropic", extra_headers={"anthropic-beta": "message-threads-2026-08-12"} - ), - api_key="sk-fake-anthropic-key", - client=client, - ) - - sent_betas: Final = frozenset(beta.strip() for beta in response.choices[0].message.content.split(",")) - assert {"advisor-tool-2026-03-01", "message-threads-2026-08-12"} <= sent_betas - - STREAM_COST_MODEL = "gpt-4o" STREAMED_USAGE = {"prompt_tokens": 137, "completion_tokens": 42, "total_tokens": 179}