From bfb82b5cee3b25aa16603e065571d1130eda03f8 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:27:03 -0700 Subject: [PATCH] fix: preserve Anthropic cache markers through adapter (#1205) Keep assistant cache-control blocks intact when converting OpenAI-format messages to Anthropic format, and propagate tool-message cache markers onto generated tool_result blocks. Adds regression tests covering assistant and tool cache marker preservation through convert_messages_to_anthropic(). --- agent/anthropic_adapter.py | 12 ++++++++++-- tests/test_anthropic_adapter.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 5d400274..ae47422c 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -404,8 +404,14 @@ def convert_messages_to_anthropic( if role == "assistant": blocks = [] if content: - text = content if isinstance(content, str) else json.dumps(content) - blocks.append({"type": "text", "text": text}) + if isinstance(content, list): + for part in content: + if isinstance(part, dict): + blocks.append(dict(part)) + elif part is not None: + blocks.append({"type": "text", "text": str(part)}) + else: + blocks.append({"type": "text", "text": str(content)}) for tc in m.get("tool_calls", []): fn = tc.get("function", {}) args = fn.get("arguments", "{}") @@ -436,6 +442,8 @@ def convert_messages_to_anthropic( "tool_use_id": _sanitize_tool_id(m.get("tool_call_id", "")), "content": result_content, } + if isinstance(m.get("cache_control"), dict): + tool_result["cache_control"] = dict(m["cache_control"]) # Merge consecutive tool results into one user message if ( result diff --git a/tests/test_anthropic_adapter.py b/tests/test_anthropic_adapter.py index 07466700..1615c6cc 100644 --- a/tests/test_anthropic_adapter.py +++ b/tests/test_anthropic_adapter.py @@ -7,6 +7,7 @@ from unittest.mock import patch, MagicMock import pytest +from agent.prompt_caching import apply_anthropic_cache_control from agent.anthropic_adapter import ( _is_oauth_token, _refresh_oauth_token, @@ -491,6 +492,33 @@ class TestConvertMessages: assert isinstance(system, list) assert system[0]["cache_control"] == {"type": "ephemeral"} + def test_assistant_cache_control_blocks_are_preserved(self): + messages = apply_anthropic_cache_control([ + {"role": "system", "content": "System prompt"}, + {"role": "assistant", "content": "Hello from assistant"}, + ]) + + _, result = convert_messages_to_anthropic(messages) + assistant_blocks = result[0]["content"] + + assert assistant_blocks[0]["type"] == "text" + assert assistant_blocks[0]["text"] == "Hello from assistant" + assert assistant_blocks[0]["cache_control"] == {"type": "ephemeral"} + + def test_tool_cache_control_is_preserved_on_tool_result_block(self): + messages = apply_anthropic_cache_control([ + {"role": "system", "content": "System prompt"}, + {"role": "tool", "tool_call_id": "tc_1", "content": "result"}, + ]) + + _, result = convert_messages_to_anthropic(messages) + tool_block = result[0]["content"][0] + + assert tool_block["type"] == "tool_result" + assert tool_block["tool_use_id"] == "tc_1" + assert tool_block["content"] == "result" + assert tool_block["cache_control"] == {"type": "ephemeral"} + # --------------------------------------------------------------------------- # Build kwargs