mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Route exec-server HTTP through configured proxy policy (#35023)
## Why Delegated HTTP requests need to honor the same outbound proxy policy as the Codex process that starts the exec server. ## What changed - Pass the configured `HttpClientFactory` through local and remote exec-server startup and use route-aware client pools for delegated HTTP and local MCP requests. - Preserve per-request timeouts and follow-or-stop redirect behavior while keeping request URLs and sensitive response headers out of diagnostics. ## Testing - Cover configured system-proxy routing across the exec-server transport. - Cover both redirect policies and verify that success and failure logs do not expose request or response secrets. GitOrigin-RevId: 4af6aec1d265c4db62dfcb6e1fb076fb31736137
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
//! - in a remote environment, that means the remote runtime after the
|
||||
//! orchestrator has forwarded `http/request` over JSON-RPC
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::RouteAwareClientPool;
|
||||
use codex_http_client::RouteAwareRequestError;
|
||||
use futures::FutureExt;
|
||||
use futures::StreamExt;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -35,10 +36,12 @@ use crate::rpc::RpcNotificationSender;
|
||||
use crate::rpc::internal_error;
|
||||
use crate::rpc::invalid_params;
|
||||
|
||||
/// `HttpClient` implementation that performs the actual HTTP request with
|
||||
/// `reqwest`.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ReqwestHttpClient;
|
||||
/// HTTP capability implementation backed by the shared route-aware transport.
|
||||
#[derive(Clone)]
|
||||
pub struct ReqwestHttpClient {
|
||||
follow_redirects: RouteAwareClientPool,
|
||||
stop_redirects: RouteAwareClientPool,
|
||||
}
|
||||
|
||||
/// Streaming response state held between the initial HTTP response and
|
||||
/// downstream body-delta forwarding.
|
||||
@@ -50,26 +53,32 @@ pub(crate) struct PendingReqwestHttpBodyStream {
|
||||
/// Validates `http/request` parameters and runs the actual `reqwest` call used
|
||||
/// by the exec-server route and the local [`HttpClient`] backend.
|
||||
pub(crate) struct ReqwestHttpRequestRunner {
|
||||
client: reqwest::Client,
|
||||
client: RouteAwareClientPool,
|
||||
}
|
||||
|
||||
impl ReqwestHttpClient {
|
||||
fn build_client(
|
||||
timeout_ms: Option<u64>,
|
||||
redirect_policy: HttpRedirectPolicy,
|
||||
) -> Result<reqwest::Client, ExecServerError> {
|
||||
let builder = match timeout_ms {
|
||||
None => reqwest::Client::builder(),
|
||||
Some(timeout_ms) => {
|
||||
reqwest::Client::builder().timeout(Duration::from_millis(timeout_ms))
|
||||
}
|
||||
pub fn new(http_client_factory: HttpClientFactory) -> Self {
|
||||
Self {
|
||||
follow_redirects: RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_request_logging(
|
||||
http_client_factory.clone(),
|
||||
// Delegated HTTP targets arbitrary endpoints; route class only labels diagnostics.
|
||||
ClientRouteClass::Other,
|
||||
),
|
||||
stop_redirects:
|
||||
RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_redirects_or_request_logging(
|
||||
http_client_factory,
|
||||
// Proxy routing comes from the factory, not this diagnostic-only route class.
|
||||
ClientRouteClass::Other,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn runner(&self, redirect_policy: HttpRedirectPolicy) -> ReqwestHttpRequestRunner {
|
||||
let client = match redirect_policy {
|
||||
HttpRedirectPolicy::Follow => self.follow_redirects.clone(),
|
||||
HttpRedirectPolicy::Stop => self.stop_redirects.clone(),
|
||||
};
|
||||
let builder = match redirect_policy {
|
||||
HttpRedirectPolicy::Follow => builder,
|
||||
HttpRedirectPolicy::Stop => builder.redirect(reqwest::redirect::Policy::none()),
|
||||
};
|
||||
build_reqwest_client_with_custom_ca(with_chatgpt_cloudflare_cookie_store(builder))
|
||||
.map_err(|error| ExecServerError::HttpRequest(error.to_string()))
|
||||
ReqwestHttpRequestRunner { client }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +88,7 @@ impl HttpClient for ReqwestHttpClient {
|
||||
params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<HttpRequestResponse, ExecServerError>> {
|
||||
async move {
|
||||
let runner = ReqwestHttpRequestRunner::new(params.timeout_ms, params.redirect_policy)
|
||||
.map_err(|error| ExecServerError::HttpRequest(error.message))?;
|
||||
let runner = self.runner(params.redirect_policy);
|
||||
let (response, _) = runner
|
||||
.run(HttpRequestParams {
|
||||
stream_response: false,
|
||||
@@ -98,8 +106,7 @@ impl HttpClient for ReqwestHttpClient {
|
||||
params: HttpRequestParams,
|
||||
) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>> {
|
||||
async move {
|
||||
let runner = ReqwestHttpRequestRunner::new(params.timeout_ms, params.redirect_policy)
|
||||
.map_err(|error| ExecServerError::HttpRequest(error.message))?;
|
||||
let runner = self.runner(params.redirect_policy);
|
||||
let (response, pending_stream) = runner
|
||||
.run(HttpRequestParams {
|
||||
stream_response: true,
|
||||
@@ -122,15 +129,6 @@ impl HttpClient for ReqwestHttpClient {
|
||||
}
|
||||
|
||||
impl ReqwestHttpRequestRunner {
|
||||
pub(crate) fn new(
|
||||
timeout_ms: Option<u64>,
|
||||
redirect_policy: HttpRedirectPolicy,
|
||||
) -> Result<Self, JSONRPCErrorError> {
|
||||
let client = ReqwestHttpClient::build_client(timeout_ms, redirect_policy)
|
||||
.map_err(|error| internal_error(error.to_string()))?;
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub(crate) async fn run(
|
||||
&self,
|
||||
params: HttpRequestParams,
|
||||
@@ -164,6 +162,9 @@ impl ReqwestHttpRequestRunner {
|
||||
if let Some(body) = params.body {
|
||||
request = request.body(body.into_inner());
|
||||
}
|
||||
if let Some(timeout_ms) = params.timeout_ms {
|
||||
request = request.timeout(Duration::from_millis(timeout_ms));
|
||||
}
|
||||
|
||||
let response = match request.send().instrument(request_span.clone()).await {
|
||||
Ok(response) => response,
|
||||
@@ -301,25 +302,18 @@ impl ReqwestHttpRequestRunner {
|
||||
}
|
||||
}
|
||||
|
||||
fn log_send_error(method: &Method, error: reqwest::Error) {
|
||||
let error = error.without_url();
|
||||
let source_chain = error_source_chain(&error);
|
||||
fn log_send_error(method: &Method, error: RouteAwareRequestError) {
|
||||
let error_is_timeout = error.is_timeout();
|
||||
let error_is_connect = error.is_connect();
|
||||
let error = match error {
|
||||
RouteAwareRequestError::Request(error) => error.without_url().to_string(),
|
||||
error => error.to_string(),
|
||||
};
|
||||
tracing::warn!(
|
||||
http_method = method.as_str(),
|
||||
error_is_timeout = error.is_timeout(),
|
||||
error_is_connect = error.is_connect(),
|
||||
error_is_timeout,
|
||||
error_is_connect,
|
||||
error = %error,
|
||||
error_sources = ?source_chain,
|
||||
"http/request send failed"
|
||||
);
|
||||
}
|
||||
|
||||
fn error_source_chain(error: &reqwest::Error) -> Option<String> {
|
||||
let mut sources = Vec::new();
|
||||
let mut source = error.source();
|
||||
while let Some(error) = source {
|
||||
sources.push(error.to_string());
|
||||
source = error.source();
|
||||
}
|
||||
(!sources.is_empty()).then(|| sources.join(": "))
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ impl EnvironmentManager {
|
||||
)])),
|
||||
local_environment: Some(Arc::new(Environment::default_for_tests())),
|
||||
local_runtime_paths: None,
|
||||
// Test-only construction has no application config from which to resolve proxy policy.
|
||||
http_client_factory: HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
}
|
||||
}
|
||||
@@ -163,6 +164,7 @@ impl EnvironmentManager {
|
||||
match Self::from_snapshot(
|
||||
provider.snapshot_inner(),
|
||||
local_runtime_paths,
|
||||
// Test-only construction has no application config from which to resolve proxy policy.
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
) {
|
||||
Ok(manager) => manager,
|
||||
@@ -249,6 +251,7 @@ impl EnvironmentManager {
|
||||
match Self::from_snapshot(
|
||||
snapshot,
|
||||
Some(local_runtime_paths),
|
||||
// Test-only construction has no application config from which to resolve proxy policy.
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
) {
|
||||
Ok(manager) => manager,
|
||||
@@ -274,7 +277,10 @@ impl EnvironmentManager {
|
||||
"local environment requires configured runtime paths".to_string(),
|
||||
)
|
||||
})?;
|
||||
let local_environment = Arc::new(Environment::local(local_runtime_paths));
|
||||
let local_environment = Arc::new(Environment::local(
|
||||
local_runtime_paths,
|
||||
http_client_factory.clone(),
|
||||
));
|
||||
environment_map.insert(
|
||||
LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
Arc::clone(&local_environment),
|
||||
@@ -619,7 +625,10 @@ impl Environment {
|
||||
startup_task: Arc::new(Mutex::new(None)),
|
||||
exec_backend: Arc::new(LocalProcess::default()),
|
||||
filesystem: Arc::new(LocalFileSystem::unsandboxed()),
|
||||
http_client: Arc::new(ReqwestHttpClient),
|
||||
// Test-only construction has no application config from which to resolve proxy policy.
|
||||
http_client: Arc::new(ReqwestHttpClient::new(HttpClientFactory::new(
|
||||
OutboundProxyPolicy::ReqwestDefault,
|
||||
))),
|
||||
local_runtime_paths: None,
|
||||
}
|
||||
}
|
||||
@@ -661,13 +670,20 @@ impl Environment {
|
||||
Ok(match exec_server_url {
|
||||
Some(exec_server_url) => Self::remote_inner(exec_server_url, local_runtime_paths),
|
||||
None => match local_runtime_paths {
|
||||
Some(local_runtime_paths) => Self::local(local_runtime_paths),
|
||||
Some(local_runtime_paths) => Self::local(
|
||||
local_runtime_paths,
|
||||
// This legacy constructor has no resolved application proxy configuration.
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
),
|
||||
None => Self::default_for_tests(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn local(local_runtime_paths: ExecServerRuntimePaths) -> Self {
|
||||
pub(crate) fn local(
|
||||
local_runtime_paths: ExecServerRuntimePaths,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Self {
|
||||
Self {
|
||||
remote_client: None,
|
||||
ready_info: None,
|
||||
@@ -678,7 +694,7 @@ impl Environment {
|
||||
filesystem: Arc::new(LocalFileSystem::with_runtime_paths(
|
||||
local_runtime_paths.clone(),
|
||||
)),
|
||||
http_client: Arc::new(ReqwestHttpClient),
|
||||
http_client: Arc::new(ReqwestHttpClient::new(http_client_factory)),
|
||||
local_runtime_paths: Some(local_runtime_paths),
|
||||
}
|
||||
}
|
||||
@@ -1548,7 +1564,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn local_environment_passes_runtime_paths_to_exec_backend() {
|
||||
let environment = Environment::local(test_runtime_paths());
|
||||
let environment = Environment::local(test_runtime_paths(), legacy_http_client_factory());
|
||||
#[cfg(unix)]
|
||||
let uri = "file://server/share/checkout";
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::time::Instant;
|
||||
|
||||
use codex_api::AuthProvider;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use futures::FutureExt;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderName;
|
||||
@@ -421,6 +422,7 @@ pub struct RemoteEnvironmentConfig {
|
||||
pub name: String,
|
||||
auth_provider: SharedAuthProvider,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RemoteEnvironmentConfig {
|
||||
@@ -439,6 +441,7 @@ impl RemoteEnvironmentConfig {
|
||||
base_url: String,
|
||||
environment_id: String,
|
||||
auth_provider: SharedAuthProvider,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<Self, ExecServerError> {
|
||||
let environment_id = normalize_environment_id(environment_id)?;
|
||||
Ok(Self {
|
||||
@@ -447,6 +450,7 @@ impl RemoteEnvironmentConfig {
|
||||
name: "codex-exec-server".to_string(),
|
||||
auth_provider,
|
||||
telemetry: ExecServerTelemetry::default(),
|
||||
http_client_factory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -472,8 +476,11 @@ pub async fn run_remote_environment(
|
||||
config.auth_provider.clone(),
|
||||
config.telemetry.clone(),
|
||||
)?;
|
||||
let processor =
|
||||
ConnectionProcessor::new_with_telemetry(runtime_paths, config.telemetry.clone());
|
||||
let processor = ConnectionProcessor::new_with_telemetry(
|
||||
runtime_paths,
|
||||
config.telemetry.clone(),
|
||||
config.http_client_factory.clone(),
|
||||
);
|
||||
let identity = NoiseChannelIdentity::generate().map_err(|error| {
|
||||
ExecServerError::Protocol(format!("failed to generate Noise relay identity: {error}"))
|
||||
})?;
|
||||
@@ -672,6 +679,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_api::AuthProvider;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use opentelemetry::trace::TracerProvider as _;
|
||||
@@ -880,12 +888,29 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_environment_config_preserves_http_client_factory_policy() {
|
||||
let config = RemoteEnvironmentConfig::new(
|
||||
"https://registry.example".to_string(),
|
||||
"env-1".to_string(),
|
||||
static_registry_auth_provider(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy),
|
||||
)
|
||||
.expect("config");
|
||||
|
||||
assert_eq!(
|
||||
config.http_client_factory.outbound_proxy_policy(),
|
||||
OutboundProxyPolicy::RespectSystemProxy
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_output_redacts_auth_provider() {
|
||||
let config = RemoteEnvironmentConfig::new(
|
||||
"https://registry.example".to_string(),
|
||||
"env-1".to_string(),
|
||||
static_registry_auth_provider(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)
|
||||
.expect("config");
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::time::Duration;
|
||||
use anyhow::Result;
|
||||
use codex_api::AuthProvider;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -59,6 +61,7 @@ async fn reconnect_reuses_registration_until_url_is_rejected() -> Result<()> {
|
||||
registry.uri(),
|
||||
"environment-requested".to_string(),
|
||||
static_registry_auth_provider(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)?;
|
||||
let environment_task = tokio::spawn(run_remote_environment(
|
||||
config,
|
||||
|
||||
@@ -13,12 +13,20 @@ pub use transport::ExecServerListenUrlParseError;
|
||||
|
||||
use crate::ExecServerRuntimePaths;
|
||||
use crate::ExecServerTelemetry;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
|
||||
pub async fn run_main(
|
||||
listen_url: &str,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
run_main_with_telemetry(listen_url, runtime_paths, ExecServerTelemetry::default()).await
|
||||
run_main_with_telemetry(
|
||||
listen_url,
|
||||
runtime_paths,
|
||||
ExecServerTelemetry::default(),
|
||||
http_client_factory,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
@@ -30,12 +38,15 @@ pub async fn run_main_with_telemetry(
|
||||
listen_url: &str,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
transport::run_transport(listen_url, runtime_paths, telemetry).await
|
||||
transport::run_transport(listen_url, runtime_paths, telemetry, http_client_factory).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use opentelemetry::trace::TracerProvider as _;
|
||||
use opentelemetry_sdk::trace::InMemorySpanExporter;
|
||||
use opentelemetry_sdk::trace::SdkTracerProvider;
|
||||
@@ -65,6 +76,7 @@ mod tests {
|
||||
)
|
||||
.expect("runtime paths"),
|
||||
ExecServerTelemetry::default(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
)
|
||||
.await
|
||||
.expect_err("invalid listen URL should fail");
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_exec_server_protocol::RequestId;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use serde_json::to_value;
|
||||
use std::collections::HashSet;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -13,6 +14,7 @@ use tokio_util::task::TaskTracker;
|
||||
|
||||
use crate::ExecServerRuntimePaths;
|
||||
use crate::client::http_client::PendingReqwestHttpBodyStream;
|
||||
use crate::client::http_client::ReqwestHttpClient;
|
||||
use crate::client::http_client::ReqwestHttpRequestRunner;
|
||||
use crate::protocol::CapabilityRootsDiscoverParams;
|
||||
use crate::protocol::CapabilityRootsDiscoverResponse;
|
||||
@@ -73,6 +75,7 @@ pub(crate) struct ExecServerHandler {
|
||||
background_tasks: TaskTracker,
|
||||
file_system: FileSystemHandler,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
http_client: ReqwestHttpClient,
|
||||
initialize_requested: AtomicBool,
|
||||
initialized: AtomicBool,
|
||||
}
|
||||
@@ -82,6 +85,7 @@ impl ExecServerHandler {
|
||||
session_registry: Arc<SessionRegistry>,
|
||||
notifications: RpcNotificationSender,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Self {
|
||||
Self {
|
||||
session_registry,
|
||||
@@ -92,6 +96,7 @@ impl ExecServerHandler {
|
||||
background_tasks: TaskTracker::new(),
|
||||
file_system: FileSystemHandler::new(runtime_paths.clone()),
|
||||
runtime_paths,
|
||||
http_client: ReqwestHttpClient::new(http_client_factory),
|
||||
initialize_requested: AtomicBool::new(false),
|
||||
initialized: AtomicBool::new(false),
|
||||
}
|
||||
@@ -222,7 +227,9 @@ impl ExecServerHandler {
|
||||
if stream_response {
|
||||
self.reserve_http_body_stream(&http_request_id).await?;
|
||||
}
|
||||
let response = ReqwestHttpRequestRunner::new(params.timeout_ms, params.redirect_policy)?
|
||||
let response = self
|
||||
.http_client
|
||||
.runner(params.redirect_policy)
|
||||
.run(params)
|
||||
.await;
|
||||
if response.is_err() && stream_response {
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -88,6 +90,7 @@ async fn initialized_handler() -> Arc<ExecServerHandler> {
|
||||
registry,
|
||||
RpcNotificationSender::new(outgoing_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
let initialize_response = handler
|
||||
.initialize(InitializeParams {
|
||||
@@ -166,6 +169,7 @@ async fn long_poll_read_fails_after_session_resume() {
|
||||
Arc::clone(®istry),
|
||||
RpcNotificationSender::new(first_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
let initialize_response = first_handler
|
||||
.initialize(InitializeParams {
|
||||
@@ -206,6 +210,7 @@ async fn long_poll_read_fails_after_session_resume() {
|
||||
registry,
|
||||
RpcNotificationSender::new(second_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
second_handler
|
||||
.initialize(InitializeParams {
|
||||
@@ -239,6 +244,7 @@ async fn active_session_resume_is_rejected() {
|
||||
Arc::clone(®istry),
|
||||
RpcNotificationSender::new(first_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
let initialize_response = first_handler
|
||||
.initialize(InitializeParams {
|
||||
@@ -253,6 +259,7 @@ async fn active_session_resume_is_rejected() {
|
||||
registry,
|
||||
RpcNotificationSender::new(second_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
let err = second_handler
|
||||
.initialize(InitializeParams {
|
||||
@@ -281,6 +288,7 @@ async fn output_and_exit_are_retained_after_notification_receiver_closes() {
|
||||
SessionRegistry::new(crate::ExecServerTelemetry::default()),
|
||||
RpcNotificationSender::new(outgoing_tx),
|
||||
test_runtime_paths(),
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
));
|
||||
handler
|
||||
.initialize(InitializeParams {
|
||||
|
||||
@@ -21,28 +21,38 @@ use crate::server::registry::build_router;
|
||||
use crate::server::session_registry::SessionRegistry;
|
||||
use crate::telemetry::ConnectionTransport;
|
||||
use crate::telemetry::ExecServerTelemetry;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ConnectionProcessor {
|
||||
session_registry: Arc<SessionRegistry>,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
}
|
||||
|
||||
impl ConnectionProcessor {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
|
||||
Self::new_with_telemetry(runtime_paths, ExecServerTelemetry::default())
|
||||
Self::new_with_telemetry(
|
||||
runtime_paths,
|
||||
ExecServerTelemetry::default(),
|
||||
codex_http_client::HttpClientFactory::new(
|
||||
codex_http_client::OutboundProxyPolicy::ReqwestDefault,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_telemetry(
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Self {
|
||||
Self {
|
||||
session_registry: SessionRegistry::new(telemetry.clone()),
|
||||
runtime_paths,
|
||||
telemetry,
|
||||
http_client_factory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +66,7 @@ impl ConnectionProcessor {
|
||||
Arc::clone(&self.session_registry),
|
||||
self.runtime_paths.clone(),
|
||||
self.telemetry.clone(),
|
||||
self.http_client_factory.clone(),
|
||||
transport,
|
||||
)
|
||||
.await;
|
||||
@@ -71,6 +82,7 @@ async fn run_connection(
|
||||
session_registry: Arc<SessionRegistry>,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
transport: ConnectionTransport,
|
||||
) {
|
||||
let _connection_metrics = telemetry.connection_started(transport);
|
||||
@@ -90,6 +102,7 @@ async fn run_connection(
|
||||
session_registry,
|
||||
notifications,
|
||||
runtime_paths,
|
||||
http_client_factory,
|
||||
));
|
||||
|
||||
let outbound_task = tokio::spawn(async move {
|
||||
@@ -530,6 +543,9 @@ mod tests {
|
||||
registry,
|
||||
test_runtime_paths(),
|
||||
crate::ExecServerTelemetry::default(),
|
||||
codex_http_client::HttpClientFactory::new(
|
||||
codex_http_client::OutboundProxyPolicy::ReqwestDefault,
|
||||
),
|
||||
crate::telemetry::ConnectionTransport::Stdio,
|
||||
));
|
||||
(client_writer, BufReader::new(client_reader).lines(), task)
|
||||
|
||||
@@ -12,6 +12,7 @@ use axum::response::IntoResponse;
|
||||
use axum::response::Response;
|
||||
use axum::routing::any;
|
||||
use axum::routing::get;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use std::io::Write as _;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::io;
|
||||
@@ -83,20 +84,32 @@ pub(crate) async fn run_transport(
|
||||
listen_url: &str,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
match parse_listen_url(listen_url)? {
|
||||
ExecServerListenTransport::WebSocket(bind_address) => {
|
||||
run_websocket_listener(bind_address, runtime_paths, telemetry).await
|
||||
run_websocket_listener(bind_address, runtime_paths, telemetry, http_client_factory)
|
||||
.await
|
||||
}
|
||||
ExecServerListenTransport::Stdio => {
|
||||
run_stdio_connection(runtime_paths, telemetry, http_client_factory).await
|
||||
}
|
||||
ExecServerListenTransport::Stdio => run_stdio_connection(runtime_paths, telemetry).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_stdio_connection(
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
run_stdio_connection_with_io(io::stdin(), io::stdout(), runtime_paths, telemetry).await
|
||||
run_stdio_connection_with_io(
|
||||
io::stdin(),
|
||||
io::stdout(),
|
||||
runtime_paths,
|
||||
telemetry,
|
||||
http_client_factory,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_stdio_connection_with_io<R, W>(
|
||||
@@ -104,12 +117,14 @@ async fn run_stdio_connection_with_io<R, W>(
|
||||
writer: W,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + 'static,
|
||||
W: AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
let processor = ConnectionProcessor::new_with_telemetry(runtime_paths, telemetry);
|
||||
let processor =
|
||||
ConnectionProcessor::new_with_telemetry(runtime_paths, telemetry, http_client_factory);
|
||||
tracing::info!("codex-exec-server listening on stdio");
|
||||
processor
|
||||
.run_connection(
|
||||
@@ -126,10 +141,12 @@ async fn run_websocket_listener(
|
||||
bind_address: SocketAddr,
|
||||
runtime_paths: ExecServerRuntimePaths,
|
||||
telemetry: ExecServerTelemetry,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let listener = TcpListener::bind(bind_address).await?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
let processor = ConnectionProcessor::new_with_telemetry(runtime_paths, telemetry);
|
||||
let processor =
|
||||
ConnectionProcessor::new_with_telemetry(runtime_paths, telemetry, http_client_factory);
|
||||
info!("codex-exec-server listening on ws://{local_addr}");
|
||||
println!("ws://{local_addr}");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
@@ -62,6 +62,9 @@ async fn stdio_listen_transport_serves_initialize() {
|
||||
server_writer,
|
||||
test_runtime_paths(),
|
||||
crate::ExecServerTelemetry::default(),
|
||||
codex_http_client::HttpClientFactory::new(
|
||||
codex_http_client::OutboundProxyPolicy::ReqwestDefault,
|
||||
),
|
||||
));
|
||||
let mut client_lines = BufReader::new(client_reader).lines();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user