fix(agent): apply pool-level keepalive to the process_bootstrap sibling builder

The salvaged #54550 converted AIAgent._build_keepalive_http_client but the
near-identical build_keepalive_http_client in agent/process_bootstrap.py
(used by auxiliary clients: compression, vision, web_extract, titles) kept
the socket_options transport and the api.githubcopilot.com bypass. Same
conversion: httpx.Limits(keepalive_expiry=20) + pool timeouts, verify
forwarded on client and no-proxy mounts, copilot hardcode removed.
This commit is contained in:
teknium1 2026-07-05 02:27:41 -07:00 committed by Teknium
parent 8324dd19ca
commit 51c1ba6976

View file

@ -151,42 +151,51 @@ def build_keepalive_http_client(
"""Build an httpx client for OpenAI SDK calls with env-only proxy policy.
Uses explicit ``HTTPS_PROXY`` / ``NO_PROXY`` env vars via
``_get_proxy_for_base_url``. A custom transport disables httpx's default
``trust_env`` path, so macOS system proxy settings from
``_get_proxy_for_base_url``. Plain no-proxy mounts disable httpx's default
``trust_env`` proxy path, so macOS system proxy settings from
``urllib.request.getproxies()`` (which omit the ExceptionsList) are not
applied. Mirrors ``AIAgent._build_keepalive_http_client``.
Connection lifecycle is managed at the HTTP pool layer
(``keepalive_expiry=20.0`` reaps idle connections before reverse proxies'
typical 30-60 s timeouts) instead of the former custom
``socket_options`` transport, which broke streaming behind reverse
proxies (#54049, #12952) and stalled TLS handshakes by stripping
``TCP_NODELAY``.
``verify`` is forwarded to httpx so auxiliary-client calls (compression,
vision, web_extract, title generation, etc.) honor the same per-provider
``ssl_ca_cert`` / ``ssl_verify`` and ``HERMES_CA_BUNDLE`` settings the main
client uses. It is passed on the ``HTTPTransport`` (which owns the SSL
context when a custom transport is supplied) and, for the copilot branch
that has no custom transport, on the client itself.
client uses. It is passed on the client AND on the plain no-proxy mounts
(a mounted transport owns the SSL context for its scheme).
"""
try:
import httpx
import socket
if "api.githubcopilot.com" in str(base_url or "").lower():
client_cls = httpx.AsyncClient if async_mode else httpx.Client
return client_cls(verify=verify)
sock_opts = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]
if hasattr(socket, "TCP_KEEPIDLE"):
sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30))
sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10))
sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3))
elif hasattr(socket, "TCP_KEEPALIVE"):
sock_opts.append((socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 30))
proxy = _get_proxy_for_base_url(base_url)
limits = httpx.Limits(
max_keepalive_connections=20,
max_connections=100,
keepalive_expiry=20.0,
)
# Generous read=None for SSE streaming endpoints.
timeout = httpx.Timeout(connect=15.0, read=None, write=15.0, pool=10.0)
transport_cls = httpx.AsyncHTTPTransport if async_mode else httpx.HTTPTransport
client_cls = httpx.AsyncClient if async_mode else httpx.Client
# verify lives on the transport: httpx ignores the client-level
# ``verify`` when a custom ``transport=`` is supplied.
mounts = {}
if proxy is None:
mounts = {
"http://": transport_cls(verify=verify),
"https://": transport_cls(verify=verify),
}
return client_cls(
transport=transport_cls(socket_options=sock_opts, verify=verify),
limits=limits,
timeout=timeout,
proxy=proxy,
mounts=mounts or None,
verify=verify,
)
except Exception:
return None