From 21b92d26877fd51d8ef43f8a5f469085697e5902 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:56:51 -0600 Subject: [PATCH] fix(agent): bypass response cache for empty retries --- agent/conversation_loop.py | 8 ++++++ tests/run_agent/test_run_agent.py | 43 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 3cc248c805..23be8bedb1 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -2986,6 +2986,14 @@ def run_conversation( is_github_responses=agent._is_copilot_url(), sanitize_harmony_tokens=agent._is_codex_backend(), ) + # OpenRouter response caching replays identical successful + # responses verbatim, including empty completions. An empty- + # response retry must reach the provider instead of replaying + # the response that triggered the retry. + if agent._empty_content_retries > 0 and agent._is_openrouter_url(): + _xh = dict(api_kwargs.get("extra_headers") or {}) + _xh["X-OpenRouter-Cache"] = "false" + api_kwargs["extra_headers"] = _xh # Copilot x-initiator: the first API call of a user turn is # marked "user" so Copilot bills a premium request; tool-loop # follow-ups keep the default "agent" header (#3040). diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index 6bbe15ed17..236d3994c0 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -3581,6 +3581,49 @@ class TestRunConversation: assert result["final_response"] == "Here is the actual answer." assert result["api_calls"] == 2 # 1 original + 1 nudge retry + def test_openrouter_empty_retry_bypasses_response_cache(self, agent, monkeypatch): + """An OpenRouter empty retry must not replay the cached empty response.""" + self._setup_agent(agent) + empty_resp = _mock_response(content=None, finish_reason="stop") + content_resp = _mock_response( + content="Fresh provider response.", + finish_reason="stop", + ) + responses = iter([empty_resp, content_resp]) + request_kwargs = [] + + def _create(**kwargs): + request_kwargs.append(kwargs) + return next(responses) + + original_build_api_kwargs = agent._build_api_kwargs + + def _build_api_kwargs(*args, **kwargs): + built = original_build_api_kwargs(*args, **kwargs) + built["extra_headers"] = {"X-Custom-Header": "preserved"} + return built + + agent.client.chat.completions.create.side_effect = _create + monkeypatch.setattr(agent, "_build_api_kwargs", _build_api_kwargs) + monkeypatch.setattr( + "agent.conversation_loop.jittered_backoff", + lambda *args, **kwargs: 0.0, + ) + + with ( + patch.object(agent, "_persist_session"), + patch.object(agent, "_save_trajectory"), + patch.object(agent, "_cleanup_task_resources"), + ): + result = agent.run_conversation("answer me") + + assert result["final_response"] == "Fresh provider response." + assert "X-OpenRouter-Cache" not in request_kwargs[0].get( + "extra_headers", {} + ) + assert request_kwargs[1]["extra_headers"]["X-Custom-Header"] == "preserved" + assert request_kwargs[1]["extra_headers"]["X-OpenRouter-Cache"] == "false" + def test_empty_response_triggers_fallback_provider(self, agent): """After 3 empty retries, fallback provider is activated and produces content.""" self._setup_agent(agent)