diff --git a/agent/prompt_caching.py b/agent/prompt_caching.py index 91b368c86b0..9a2fdf4ccce 100644 --- a/agent/prompt_caching.py +++ b/agent/prompt_caching.py @@ -65,7 +65,11 @@ def _can_carry_marker(msg: dict, native_anthropic: bool) -> bool: if content is None or content == "": return False if isinstance(content, list): - return any(isinstance(part, dict) for part in content) + # _apply_cache_marker only marks the LAST content part, so the carrier + # predicate must agree: a list whose last element isn't a dict cannot + # actually receive a marker and would waste a breakpoint. Mirror the + # `content` truthiness + last-element-dict check in _apply_cache_marker. + return bool(content) and isinstance(content[-1], dict) return isinstance(content, str) diff --git a/tests/agent/test_prompt_caching.py b/tests/agent/test_prompt_caching.py index 8af18c02793..7a1c0495e1b 100644 --- a/tests/agent/test_prompt_caching.py +++ b/tests/agent/test_prompt_caching.py @@ -98,6 +98,25 @@ class TestCanCarryMarker: assert _can_carry_marker({"role": "tool", "content": "result"}, native_anthropic=False) is True assert _can_carry_marker({"role": "tool", "content": ""}, native_anthropic=False) is False + def test_openrouter_list_carrier_requires_last_part_dict(self): + """Carrier predicate must agree with _apply_cache_marker, which only marks + the LAST content part. A list whose last element isn't a dict cannot carry + a marker and must not consume a breakpoint.""" + # Last part is a dict -> carrier. + assert _can_carry_marker( + {"role": "user", "content": [{"type": "text", "text": "a"}]}, + native_anthropic=False, + ) is True + # Last part is a non-dict (stray raw string) -> NOT a carrier, even though + # an earlier part is a dict. Previously this passed the gate but got no + # marker, wasting a breakpoint. + assert _can_carry_marker( + {"role": "user", "content": [{"type": "text", "text": "a"}, "trailing raw"]}, + native_anthropic=False, + ) is False + # Empty list -> not a carrier. + assert _can_carry_marker({"role": "user", "content": []}, native_anthropic=False) is False + class TestApplyAnthropicCacheControl: def test_empty_messages(self):