11"""envd RPC client plumbing shared by the sync and async flavors.
22
33The envd RPC clients (process, filesystem) run on `connectrpc`, whose HTTP
4- layer is `pyqwest` (Rust reqwest/hyper). This is a separate stack from the
5- `httpx` transports in `e2b.api`, which keep serving the REST API and the
6- multipart file transfer endpoints. Unlike the previous httpcore-based
7- transport, hyper sends RST_STREAM when a server stream is closed early, so
8- abandoned command/watch streams don't leak on the shared HTTP/2 connection.
4+ layer is `pyqwest` (Rust reqwest/hyper) — built on the same
5+ `retrying_http_transport` pieces as the REST API client in `e2b.api`, in a
6+ separately cached pool. Only the multipart file transfer endpoints stay on
7+ the `httpx` envd transports. Unlike the previous httpcore-based transport,
8+ hyper sends RST_STREAM when a server stream is closed early, so abandoned
9+ command/watch streams don't leak on the shared HTTP/2 connection.
910
1011The flavor-specific transports and client factories live in
1112:mod:`e2b.envd.client_sync` and :mod:`e2b.envd.client_async`, mirroring the
1516"""
1617
1718import json
18- import os
1919from typing import Optional , TypedDict , TypeVar
2020
21- import httpx
2221from connectrpc .code import Code
2322from connectrpc .errors import ConnectError
2423from protobuf import Message
2524
26- from e2b .exceptions import InvalidArgumentException
27-
28- # Mirror the httpx pool tuning in `e2b.api.limits` with pyqwest's equivalents.
29- # `pool_max_idle_per_host` is per host rather than httpx's global idle cap,
30- # which suits envd traffic — each sandbox is its own host.
31- pool_idle_timeout = float (os .getenv ("E2B_KEEPALIVE_EXPIRY" ) or "300" )
32- pool_max_idle_per_host = int (os .getenv ("E2B_MAX_KEEPALIVE_CONNECTIONS" ) or "20" )
33-
3425_MESSAGE = TypeVar ("_MESSAGE" , bound = Message )
3526
3627
@@ -133,25 +124,6 @@ def plain_http_error(
133124 )
134125
135126
136- def should_retry_connection (response : object ) -> bool :
137- """Whether a transport result is a retryable connection-establishment
138- failure — the shared policy of the flavor ``ConnectionRetryTransport``s.
139-
140- pyqwest raises the builtin ``ConnectionError`` only before the request
141- was written, so retrying exactly these failures can never replay a
142- request envd may have received — which could re-run a command or
143- re-deliver events — for unary and streaming RPCs alike. Anything later
144- (``WriteError``/``ReadError``/``StreamError``, error responses) surfaces
145- to the caller; the retry middleware's default policy would otherwise also
146- retry I/O errors and 429/5xx responses for idempotent methods. This
147- replaces httpcore's transport ``retries`` from the previous stack and
148- deliberately drops the vendored client's retry on connections dropped
149- mid-request, which could re-execute a delivered unary RPC like
150- ``SendInput``.
151- """
152- return isinstance (response , ConnectionError )
153-
154-
155127class _RPCCompression (TypedDict ):
156128 send_compression : None
157129 accept_compression : "tuple[()]"
@@ -168,38 +140,3 @@ class _RPCCompression(TypedDict):
168140 "send_compression" : None ,
169141 "accept_compression" : (),
170142}
171-
172-
173- def proxy_to_url (proxy : object ) -> Optional [str ]:
174- """Narrow the ``proxy`` connection option to the proxy URL string pyqwest
175- transports take (scheme http, https, socks5, or socks5h, credentials in
176- the URL userinfo). ``httpx.URL`` and ``httpx.Proxy`` — which the REST
177- client accepts and the vendored envd client used to — are converted when
178- they reduce to such a URL; ``httpx.Proxy`` extras that don't (custom
179- headers, an ssl_context) are rejected rather than silently dropped.
180- """
181- if proxy is None :
182- return None
183- if isinstance (proxy , str ):
184- return proxy
185- if isinstance (proxy , httpx .URL ):
186- return str (proxy )
187- if isinstance (proxy , httpx .Proxy ):
188- if proxy .headers :
189- raise InvalidArgumentException (
190- "Sandbox RPC calls don't support httpx.Proxy custom headers; "
191- "pass credentials in the proxy URL instead, "
192- 'e.g. proxy="http://user:pass@localhost:8030"'
193- )
194- if proxy .ssl_context is not None :
195- raise InvalidArgumentException (
196- "Sandbox RPC calls don't support httpx.Proxy ssl_context"
197- )
198- url = proxy .url
199- if proxy .auth is not None :
200- url = url .copy_with (username = proxy .auth [0 ], password = proxy .auth [1 ])
201- return str (url )
202- raise InvalidArgumentException (
203- "Sandbox RPC calls support only URL-string proxies, "
204- 'e.g. proxy="http://user:pass@localhost:8030"'
205- )
0 commit comments