@@ -1859,6 +1859,83 @@ def test_content_block_to_part_redacted_thinking():
18591859 assert part .thought_signature == b"redacted_data"
18601860
18611861
1862+ def test_content_to_message_param_drops_content_free_signature ():
1863+ """A signature with no content of its own used to wedge the session.
1864+
1865+ It matched no branch in `_part_to_message_block`, so the turn raised
1866+ NotImplementedError. The part stays in session history, so every later turn
1867+ raised again and the session was dead for good. It came from another model,
1868+ carries nothing, and Claude cannot verify it, so it is dropped.
1869+ """
1870+ from google .adk .models .anthropic_llm import content_to_message_param
1871+
1872+ content = types .Content (
1873+ role = "model" ,
1874+ parts = [
1875+ types .Part (text = "" , thought_signature = b"\x01 \x8f =k_\xae @L" ), # binary
1876+ types .Part (thought_signature = b"AY89a18D" ), # and one that is text
1877+ types .Part (text = "hello" ),
1878+ ],
1879+ )
1880+
1881+ message = content_to_message_param (content )
1882+
1883+ assert [block ["type" ] for block in message ["content" ]] == ["text" ]
1884+
1885+
1886+ @pytest .mark .asyncio
1887+ async def test_turn_of_only_dropped_parts_is_not_sent_as_empty_message ():
1888+ """A turn that is nothing but a foreign signature must not become `content: []`.
1889+
1890+ Dropping the part is right -- there is nothing Claude can be given -- but
1891+ emitting the message anyway trades NotImplementedError for a 400 that
1892+ repeats on every later turn, which is the same permanent wedge.
1893+ """
1894+ request = _cache_test_request (
1895+ contents = [
1896+ Content (role = "user" , parts = [Part .from_text (text = "Question" )]),
1897+ Content (
1898+ role = "model" ,
1899+ parts = [types .Part (thought_signature = b"\x01 \x8f =k_" )],
1900+ ),
1901+ ]
1902+ )
1903+
1904+ kwargs = await _sent_anthropic_kwargs (request )
1905+
1906+ assert all (m ["content" ] for m in kwargs ["messages" ])
1907+ assert [m ["role" ] for m in kwargs ["messages" ]] == ["user" ]
1908+
1909+
1910+ def test_content_to_message_param_keeps_signature_on_real_content ():
1911+ """A signature riding along with content must not take the part with it."""
1912+ from google .adk .models .anthropic_llm import content_to_message_param
1913+
1914+ content = types .Content (
1915+ role = "model" ,
1916+ parts = [
1917+ types .Part (
1918+ function_call = types .FunctionCall (id = "c1" , name = "do_it" , args = {}),
1919+ thought_signature = b"\x01 \x8f =k_" ,
1920+ ),
1921+ types .Part (text = "hi" , thought = True , thought_signature = b"sig" ),
1922+ # Claude's own redacted thinking: no content, but the flag is intact,
1923+ # so it must be sent back rather than dropped.
1924+ types .Part (thought = True , thought_signature = b"opaque" ),
1925+ # An annotation is not content, so this one still goes.
1926+ types .Part (thought_signature = b"gone" , part_metadata = {"a" : 1 }),
1927+ ],
1928+ )
1929+
1930+ message = content_to_message_param (content )
1931+
1932+ assert [block ["type" ] for block in message ["content" ]] == [
1933+ "tool_use" ,
1934+ "thinking" ,
1935+ "redacted_thinking" ,
1936+ ]
1937+
1938+
18621939def test_message_to_generate_content_response_with_thinking ():
18631940 """Message with ThinkingBlock + TextBlock yields both parts."""
18641941 from google .adk .models .anthropic_llm import message_to_generate_content_response
@@ -3906,7 +3983,11 @@ async def test_cache_breakpoint_skips_a_reasoning_block(
39063983
39073984@pytest .mark .asyncio
39083985async def test_cache_breakpoint_skips_a_turn_left_with_no_blocks ():
3909- """An assistant turn holding only an image is dropped, so it cannot carry one."""
3986+ """An assistant turn holding only an image is dropped, so it cannot carry one.
3987+
3988+ It used to be sent as a message with empty content, which Anthropic rejects;
3989+ the turn is now left out of the request entirely.
3990+ """
39103991 request = _cache_test_request (
39113992 contents = [
39123993 Content (role = "user" , parts = [Part .from_text (text = "Question" )]),
@@ -3921,7 +4002,8 @@ async def test_cache_breakpoint_skips_a_turn_left_with_no_blocks():
39214002
39224003 kwargs = await _sent_anthropic_kwargs (request )
39234004
3924- assert kwargs ["messages" ][- 1 ]["content" ] == []
4005+ assert [m ["role" ] for m in kwargs ["messages" ]] == ["user" ]
4006+ assert all (m ["content" ] for m in kwargs ["messages" ])
39254007 assert _breakpoints (kwargs ) == {
39264008 "system[0]" : _EPHEMERAL ,
39274009 "tools[1]" : _EPHEMERAL ,
0 commit comments