diff --git a/gateway/run.py b/gateway/run.py index a246372da4d..6f5fa65a57f 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -67,6 +67,7 @@ _AGENT_CACHE_MAX_SIZE = 128 _AGENT_CACHE_IDLE_TTL_SECS = 3600.0 # evict agents idle for >1h _PLATFORM_CONNECT_TIMEOUT_SECS_DEFAULT = 30.0 _ADAPTER_DISCONNECT_TIMEOUT_SECS_DEFAULT = 5.0 +_GATEWAY_PROXY_SSE_BUFFER_MAX_CHARS = 16 * 1024 * 1024 _TELEGRAM_COMMAND_MENTION_RE = re.compile(r"(? _GATEWAY_PROXY_SSE_BUFFER_MAX_CHARS: + raise ValueError( + "Proxy SSE stream exceeded max buffer size without a line boundary" + ) except asyncio.CancelledError: raise diff --git a/tests/gateway/test_proxy_mode.py b/tests/gateway/test_proxy_mode.py index 0c7fa80a0ba..be98f7eb9ac 100644 --- a/tests/gateway/test_proxy_mode.py +++ b/tests/gateway/test_proxy_mode.py @@ -334,6 +334,32 @@ class TestRunAgentViaProxy: assert "Proxy connection error" in result["final_response"] + @pytest.mark.asyncio + async def test_rejects_proxy_sse_without_line_boundary_after_buffer_cap(self, monkeypatch): + monkeypatch.setenv("GATEWAY_PROXY_URL", "http://host:8642") + monkeypatch.delenv("GATEWAY_PROXY_KEY", raising=False) + monkeypatch.setattr("gateway.run._GATEWAY_PROXY_SSE_BUFFER_MAX_CHARS", 16) + runner = _make_runner() + source = _make_source() + + resp = _FakeSSEResponse(status=200, sse_chunks=[b"data: ", b"x" * 20]) + session = _FakeSession(resp) + + with patch("gateway.run._load_gateway_config", return_value={}): + with _patch_aiohttp(session): + with patch("aiohttp.ClientTimeout"): + result = await runner._run_agent_via_proxy( + message="hi", + context_prompt="", + history=[], + source=source, + session_id="test", + ) + + assert "Proxy connection error" in result["final_response"] + assert "exceeded max buffer size" in result["final_response"] + assert result["api_calls"] == 0 + @pytest.mark.asyncio async def test_skips_tool_messages_in_history(self, monkeypatch): monkeypatch.setenv("GATEWAY_PROXY_URL", "http://host:8642")