mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
core: route Responses API through system proxy (#31335)
## Why `features.respect_system_proxy` already routes authentication traffic through the OS proxy APIs, but it does not affect the primary inference path. That leaves users behind OS-managed proxies unable to send normal Responses API requests even after login succeeds. This PR is the first product-path migration onto the route-aware transport introduced in #31323 and refined in #31331. It also establishes the construction pattern for later migrations: the effective feature state is resolved once into a required HTTP client factory rather than represented by an optional per-call setting. The scope remains limited to the two HTTP Responses endpoints; WebSockets, model discovery, memories, realtime, and file uploads remain follow-up migrations. ## What changed - Replace the optional proxy marker with an explicit `OutboundProxyPolicy::{ReqwestDefault, RespectSystemProxy}` and a required `HttpClientFactory`. The policy has no default, and the lower-level route-aware reqwest builder is now private. - Have `Config` construct the factory from the effective feature state and require every `ModelClient` constructor to receive it. There is no optional setter or implicit `None` fallback. - Build HTTP clients for `/responses` and `/responses/compact` with `ClientRouteClass::Api`, using the complete destination URL so PAC rules can make URL-specific decisions. - Layer route-aware selection onto Codex's existing default headers, Cloudflare cookie store, custom CA handling, and sandbox no-proxy behavior. - Add an integration test that loads `features.respect_system_proxy` through `config.toml`, creates a real Codex session, and verifies that both a normal Responses turn and remote compaction reach an isolated local proxy. ## Review guide 1. `http-client/src/outbound_proxy.rs` defines the mandatory policy/factory boundary and keeps route resolution private. 2. `core/src/config/mod.rs`, `core/src/session/session.rs`, and `core/src/client.rs` show the compile-time invariant: effective config creates the factory, and `ModelClient` cannot be constructed without one. 3. `login/src/auth/default_client.rs` preserves existing default-client behavior while accepting the required factory for migrated routes. 4. `core/src/client.rs` switches only streaming Responses and remote compaction HTTP transports to the API route class. 5. `core/tests/suite/responses_api_system_proxy.rs` is the behavioral regression boundary. Its Linux subprocess deliberately sets the CGI marker that disables reqwest's implicit environment-proxy handling, so the test fails if session wiring or either Responses call site falls back to the default client. ## Test plan - `cargo check --tests -p codex-http-client -p codex-login -p codex-core` - `just test -p codex-login` - `just test -p codex-core respect_system_proxy_feature_resolves_enabled` - Existing `compact_uses_bearer_after_agent_identity_session_fallback` coverage passes with the new transport construction. - New Linux integration coverage: `responses_and_compact_use_enabled_system_proxy` - `just bazel-lock-check` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/31335). * #31342 * __->__ #31335
This commit is contained in:
1 parent
b9b934e99b
commit
6afcf26d5d
17 files changed
+166
-38
No files matched your search
@@ -8,8 +8,9 @@ use codex_http_client::BuildCustomCaTransportError;
|
||||
use codex_http_client::BuildRouteAwareHttpClientError;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClient;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
pub use codex_http_client::RequestBuilder as CodexRequestBuilder;
|
||||
use codex_http_client::build_reqwest_client_for_route;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_terminal_detection::user_agent;
|
||||
@@ -235,6 +236,35 @@ pub fn try_build_reqwest_client() -> Result<reqwest::Client, BuildCustomCaTransp
|
||||
build_reqwest_client_with_custom_ca(default_reqwest_client_builder())
|
||||
}
|
||||
|
||||
/// Builds the default Codex reqwest client for a concrete outbound route.
|
||||
///
|
||||
/// When route-aware proxy handling is disabled, or the client is running inside the Codex
|
||||
/// sandbox, this preserves the default client's existing proxy behavior. Otherwise it resolves
|
||||
/// the destination through the shared system/PAC-aware routing policy.
|
||||
pub fn build_default_reqwest_client_for_route(
|
||||
http_client_factory: &HttpClientFactory,
|
||||
request_url: &str,
|
||||
route_class: ClientRouteClass,
|
||||
) -> Result<reqwest::Client, BuildRouteAwareHttpClientError> {
|
||||
if matches!(
|
||||
http_client_factory.outbound_proxy_policy(),
|
||||
OutboundProxyPolicy::ReqwestDefault
|
||||
) {
|
||||
return Ok(build_reqwest_client());
|
||||
}
|
||||
if is_sandboxed() {
|
||||
// Preserve the sandbox's existing no-proxy policy; sandboxed command egress is routed
|
||||
// separately through network-proxy.
|
||||
return Ok(build_reqwest_client());
|
||||
}
|
||||
|
||||
http_client_factory.build_reqwest_client(
|
||||
default_reqwest_client_builder(),
|
||||
request_url,
|
||||
route_class,
|
||||
)
|
||||
}
|
||||
|
||||
fn default_reqwest_client_builder() -> reqwest::ClientBuilder {
|
||||
let mut builder = reqwest::Client::builder().default_headers(default_headers());
|
||||
if is_sandboxed() {
|
||||
@@ -248,11 +278,10 @@ pub(crate) fn build_raw_auth_reqwest_client(
|
||||
endpoint: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
) -> Result<reqwest::Client, BuildRouteAwareHttpClientError> {
|
||||
build_reqwest_client_for_route(
|
||||
auth_http_client_factory(auth_route_config).build_reqwest_client(
|
||||
reqwest::Client::builder(),
|
||||
endpoint,
|
||||
ClientRouteClass::Auth,
|
||||
auth_route_config.map(AuthRouteConfig::route_config),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -261,20 +290,10 @@ pub(crate) fn build_default_auth_reqwest_client(
|
||||
endpoint: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
) -> Result<reqwest::Client, BuildRouteAwareHttpClientError> {
|
||||
let Some(route_config) = auth_route_config.map(AuthRouteConfig::route_config) else {
|
||||
return Ok(build_reqwest_client());
|
||||
};
|
||||
|
||||
if is_sandboxed() {
|
||||
// Preserve the sandbox's existing no-proxy policy; sandboxed command egress is routed
|
||||
// separately through network-proxy.
|
||||
return Ok(build_reqwest_client());
|
||||
}
|
||||
build_reqwest_client_for_route(
|
||||
default_reqwest_client_builder(),
|
||||
build_default_reqwest_client_for_route(
|
||||
&auth_http_client_factory(auth_route_config),
|
||||
endpoint,
|
||||
ClientRouteClass::Auth,
|
||||
Some(route_config),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -286,6 +305,13 @@ pub(crate) fn create_default_auth_client(
|
||||
build_default_auth_reqwest_client(endpoint, auth_route_config).map(HttpClient::new)
|
||||
}
|
||||
|
||||
fn auth_http_client_factory(auth_route_config: Option<&AuthRouteConfig>) -> HttpClientFactory {
|
||||
auth_route_config.map_or_else(
|
||||
|| HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
|config| config.http_client_factory().clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn default_headers() -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("originator", originator().header_value);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use codex_http_client::OutboundProxyConfig;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
|
||||
/// Auth-layer adapter around client-owned proxy policy.
|
||||
///
|
||||
@@ -6,17 +7,17 @@ use codex_http_client::OutboundProxyConfig;
|
||||
/// client layer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuthRouteConfig {
|
||||
route_config: OutboundProxyConfig,
|
||||
http_client_factory: HttpClientFactory,
|
||||
}
|
||||
|
||||
impl AuthRouteConfig {
|
||||
pub fn respect_system_proxy() -> Self {
|
||||
Self {
|
||||
route_config: OutboundProxyConfig::respect_system_proxy(),
|
||||
http_client_factory: HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn route_config(&self) -> &OutboundProxyConfig {
|
||||
&self.route_config
|
||||
pub(crate) fn http_client_factory(&self) -> &HttpClientFactory {
|
||||
&self.http_client_factory
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user