From a48e2591ed9d756d1276f91f13a0a07eec47dc50 Mon Sep 17 00:00:00 2001 From: Palash Debnath <4178343+debpalash@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:30:10 -0700 Subject: [PATCH] Refresh SDK connection pools when network settings change --- backend/services/llm_transport.py | 11 ++++++++++- docs/electron-llm-providers.md | 2 ++ tests/test_llm_transports.py | 22 ++++++++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/backend/services/llm_transport.py b/backend/services/llm_transport.py index 45bad7438..4202250f6 100644 --- a/backend/services/llm_transport.py +++ b/backend/services/llm_transport.py @@ -34,8 +34,17 @@ def _check_sdk_request(request): raise ValueError("SDK provider requests require HTTPS outside localhost") -@lru_cache(maxsize=1) def _sdk_http_client(): + # Settings can update proxies without restarting the backend. Retire the + # previous pool without closing responses still being streamed from it. + keys = ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", + "http_proxy", "https_proxy", "all_proxy", "no_proxy", + "SSL_CERT_FILE", "SSL_CERT_DIR", "REQUEST_METHOD") + return _sdk_http_client_for_env(tuple(os.environ.get(key) for key in keys)) + + +@lru_cache(maxsize=1) +def _sdk_http_client_for_env(network_env): # A shared thread-safe pool also keeps streaming responses alive after # completion() returns. It carries no provider credentials of its own. import httpx diff --git a/docs/electron-llm-providers.md b/docs/electron-llm-providers.md index 38bb0d04c..f6fa6ea70 100644 --- a/docs/electron-llm-providers.md +++ b/docs/electron-llm-providers.md @@ -153,3 +153,5 @@ Anthropic, Bedrock, and Vertex requests. The shared HTTP client bypasses environment proxies for loopback requests so local prompts stay local, preserves proxy settings for remote HTTPS providers, and never stores response cookies between provider requests. +Changing proxy settings refreshes the pool for subsequent requests while existing +streams keep their original connections until they finish. diff --git a/tests/test_llm_transports.py b/tests/test_llm_transports.py index 8a8dfa012..58ef31cc8 100644 --- a/tests/test_llm_transports.py +++ b/tests/test_llm_transports.py @@ -165,7 +165,7 @@ def test_anthropic_does_not_follow_credentialed_redirect(registry): def test_sdk_pool_keeps_loopback_direct_and_does_not_persist_cookies(monkeypatch, proxy_enabled): from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from threading import Thread - from services.llm_transport import _sdk_http_client + from services.llm_transport import _sdk_http_client_for_env seen = [] class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass @@ -182,7 +182,7 @@ def test_sdk_pool_keeps_loopback_direct_and_does_not_persist_cookies(monkeypatch server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) thread = Thread(target=server.serve_forever, daemon=True); thread.start() try: - with _sdk_http_client.__wrapped__() as client: + with _sdk_http_client_for_env.__wrapped__(()) as client: for _ in range(2): assert client.get(f"http://127.0.0.1:{server.server_port}/", timeout=2).status_code == 200 assert not list(client.cookies.jar) @@ -195,7 +195,7 @@ def test_sdk_remote_https_still_uses_environment_proxy(monkeypatch): import httpx from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from threading import Thread - from services.llm_transport import _sdk_http_client + from services.llm_transport import _sdk_http_client_for_env seen = [] class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass @@ -211,9 +211,23 @@ def test_sdk_remote_https_still_uses_environment_proxy(monkeypatch): monkeypatch.setenv("NO_PROXY", "") monkeypatch.setenv("no_proxy", "") try: - with _sdk_http_client.__wrapped__() as client: + with _sdk_http_client_for_env.__wrapped__(()) as client: with pytest.raises(httpx.ProxyError, match="502"): client.get("https://provider.invalid/", timeout=2) assert seen == ["provider.invalid:443"] finally: server.shutdown(); server.server_close(); thread.join(timeout=2) + + +def test_sdk_pool_refreshes_after_proxy_change_without_closing_active_streams(monkeypatch): + from services.llm_transport import _sdk_http_client + monkeypatch.setenv("HTTPS_PROXY", "http://127.0.0.1:8011") + monkeypatch.setenv("https_proxy", "http://127.0.0.1:8011") + first = _sdk_http_client() + assert _sdk_http_client() is first + monkeypatch.setenv("HTTPS_PROXY", "http://127.0.0.1:8012") + monkeypatch.setenv("https_proxy", "http://127.0.0.1:8012") + second = _sdk_http_client() + assert second is not first + assert not first.is_closed + assert not second.is_closed