From fe55b7786e3b8d131096a1e203769da9cc94fd3a Mon Sep 17 00:00:00 2001 From: Bryan Bednarski Date: Thu, 13 Aug 2026 12:24:44 -0700 Subject: [PATCH 1/3] fix(relay): keep client timeout off managed payloads Signed-off-by: Bryan Bednarski --- agent/relay_llm.py | 4 +++ tests/agent/test_relay_llm.py | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/agent/relay_llm.py b/agent/relay_llm.py index 58a5c1bd5e..0e68cfb9ee 100644 --- a/agent/relay_llm.py +++ b/agent/relay_llm.py @@ -1037,6 +1037,10 @@ def _relay_request_body( body = _jsonable(request) if not isinstance(body, dict): return {} + # ``timeout`` configures the provider SDK client; it is not part of any + # supported provider wire protocol. Keep it on the original callback + # request, but never expose it to Relay intercepts or routed transports. + body.pop("timeout", None) # The Responses SDK accepts ``tools=None`` as "no tools", while Relay's # typed Responses codec correctly expects either an array or an absent # field. Normalize only the codec-facing copy; the original provider diff --git a/tests/agent/test_relay_llm.py b/tests/agent/test_relay_llm.py index e24897412a..ce9e64be69 100644 --- a/tests/agent/test_relay_llm.py +++ b/tests/agent/test_relay_llm.py @@ -39,6 +39,52 @@ def relay_turn(tmp_path, monkeypatch): relay_runtime._reset_for_tests() +@pytest.mark.parametrize( + "api_mode", + ["chat_completions", "codex_responses", "anthropic_messages"], +) +def test_relay_request_body_omits_client_timeout(api_mode): + request = {"model": "test-model", "timeout": 1800.0} + + body = relay_llm._relay_request_body(request, {"api_mode": api_mode}) + + assert "timeout" not in body + assert request["timeout"] == 1800.0 + + +def test_unintercepted_provider_callback_preserves_client_timeout( + relay_turn, monkeypatch +): + relay, _turn = relay_turn + relay_requests = [] + provider_requests = [] + original_execute = relay.llm.execute + + async def capture_relay_request(name, request, *args, **kwargs): + relay_requests.append(request.content) + return await original_execute(name, request, *args, **kwargs) + + def provider(request): + provider_requests.append(request) + return {"content": "done"} + + monkeypatch.setattr(relay.llm, "execute", capture_relay_request) + monkeypatch.setattr(relay_llm, "_codec", lambda *_args, **_kwargs: None) + + result = relay_llm.execute( + {"model": "test-model", "messages": [], "timeout": 1800.0}, + provider, + session_id="session-1", + name="custom", + model_name="test-model", + metadata={"api_mode": "chat_completions"}, + ) + + assert result == {"content": "done"} + assert relay_requests == [{"model": "test-model", "messages": []}] + assert provider_requests[0]["timeout"] == 1800.0 + + def test_stream_uses_rewritten_request_and_post_intercept_chunks(relay_turn): relay, turn = relay_turn captured_requests = [] From 173a6447db741ed552a8e6263061a85b63129bc1 Mon Sep 17 00:00:00 2001 From: Bryan Bednarski Date: Mon, 17 Aug 2026 19:00:31 -0700 Subject: [PATCH 2/3] chore: retrigger checks after main merge Signed-off-by: Bryan Bednarski From a2d0ba3d81257e62430982bad239c44a1397fcca Mon Sep 17 00:00:00 2001 From: Bryan Bednarski Date: Mon, 17 Aug 2026 19:24:27 -0700 Subject: [PATCH 3/3] docs(relay): clarify timeout payload isolation Signed-off-by: Bryan Bednarski --- agent/relay_llm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/relay_llm.py b/agent/relay_llm.py index 3804558672..ea969da43f 100644 --- a/agent/relay_llm.py +++ b/agent/relay_llm.py @@ -1103,9 +1103,9 @@ def _relay_request_body( body = _jsonable(request) if not isinstance(body, dict): return {} - # ``timeout`` configures the provider SDK client; it is not part of any - # supported provider wire protocol. Keep it on the original callback - # request, but never expose it to Relay intercepts or routed transports. + # ``timeout`` configures the provider SDK client, not a provider wire + # protocol. Preserve it for the original callback request, but never pass + # it to Relay intercepts or routed transports. body.pop("timeout", None) # The Responses SDK accepts ``tools=None`` as "no tools", while Relay's # typed Responses codec correctly expects either an array or an absent