From 52cf9dbada1b95876e4a67d2d82984dd3c803835 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:12:15 +0530 Subject: [PATCH] fix(prompt-caching): align _can_carry_marker with last-part-dict marking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the salvaged #57845 fix. _can_carry_marker used any(isinstance(part, dict)) but _apply_cache_marker only marks the LAST content part, so a list whose last element is a non-dict passed the carrier gate yet received no marker — wasting one of the four breakpoints. Tighten the predicate to require content[-1] to be a dict (mirroring the apply logic) and add a regression test. Flagged by a 3-agent review. --- agent/prompt_caching.py | 6 +++++- tests/agent/test_prompt_caching.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) 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):