mirror of
https://github.com/openai/codex.git
synced 2026-09-01 14:39:21 +00:00
## Why Remote environment connections need to honor Codex's effective outbound proxy policy, including when a rendezvous connection reconnects. ## What changed - Pass the configured `HttpClientFactory` into remote environment transports and use `WebSocketConnector` for exec-server and rendezvous WebSockets. - Resolve proxy routes asynchronously so these connections can use the configured system proxy. - Add connector options that preserve Tungstenite's default TLS behavior and enable `TCP_NODELAY` for latency-sensitive rendezvous traffic. ## Testing - Verify prepared remote environments connect through a configured system proxy. - Verify initial and reconnected encrypted relay peers use the system proxy. - Cover default TLS selection and opt-in `TCP_NODELAY` behavior in the WebSocket client. GitOrigin-RevId: 8a8da2116e37cb3a891269d0c0b037986fecdd3c
212 lines
7.5 KiB
Rust
212 lines
7.5 KiB
Rust
use std::future::Future;
|
|
use std::pin::Pin;
|
|
|
|
use crate::ExecServerError;
|
|
use crate::client_api::DEFAULT_REMOTE_EXEC_SERVER_CONNECT_TIMEOUT;
|
|
use crate::client_api::ExecServerTransportParams;
|
|
use crate::environment::CODEX_EXEC_SERVER_URL_ENV_VAR;
|
|
use crate::environment::LOCAL_ENVIRONMENT_ID;
|
|
use crate::environment::REMOTE_ENVIRONMENT_ID;
|
|
|
|
/// Lists the remote environment transports available to Codex.
|
|
///
|
|
/// Implementations own a startup snapshot containing both the available
|
|
/// environment transport list in configured order and the default environment
|
|
/// selection. Providers return transport descriptions before the effective HTTP
|
|
/// policy is available; `include_local` controls whether `EnvironmentManager`
|
|
/// should add the local environment when the snapshot is built.
|
|
pub trait EnvironmentProvider: Send + Sync {
|
|
/// Returns the provider-owned environment startup snapshot.
|
|
fn snapshot(&self) -> EnvironmentProviderFuture<'_>;
|
|
}
|
|
|
|
pub type EnvironmentProviderFuture<'a> =
|
|
Pin<Box<dyn Future<Output = Result<EnvironmentProviderSnapshot, ExecServerError>> + Send + 'a>>;
|
|
|
|
#[derive(Clone)]
|
|
pub struct EnvironmentProviderSnapshot {
|
|
pub(crate) environments: Vec<(String, ExecServerTransportParams)>,
|
|
pub default: EnvironmentDefault,
|
|
pub include_local: bool,
|
|
}
|
|
|
|
impl std::fmt::Debug for EnvironmentProviderSnapshot {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let environment_ids: Vec<_> = self.environments.iter().map(|(id, _)| id).collect();
|
|
f.debug_struct("EnvironmentProviderSnapshot")
|
|
.field("environments", &environment_ids)
|
|
.field("default", &self.default)
|
|
.field("include_local", &self.include_local)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum EnvironmentDefault {
|
|
Disabled,
|
|
EnvironmentId(String),
|
|
}
|
|
|
|
/// Default provider backed by `CODEX_EXEC_SERVER_URL`.
|
|
#[derive(Clone, Debug)]
|
|
pub struct DefaultEnvironmentProvider {
|
|
exec_server_url: Option<String>,
|
|
}
|
|
|
|
impl DefaultEnvironmentProvider {
|
|
/// Builds a provider from an already-read raw `CODEX_EXEC_SERVER_URL` value.
|
|
pub fn new(exec_server_url: Option<String>) -> Self {
|
|
Self { exec_server_url }
|
|
}
|
|
|
|
/// Builds a provider by reading `CODEX_EXEC_SERVER_URL`.
|
|
pub fn from_env() -> Self {
|
|
Self::new(std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok())
|
|
}
|
|
|
|
pub(crate) fn snapshot_inner(&self) -> EnvironmentProviderSnapshot {
|
|
let mut environments = Vec::new();
|
|
let (exec_server_url, disabled) = normalize_exec_server_url(self.exec_server_url.clone());
|
|
|
|
if let Some(exec_server_url) = exec_server_url {
|
|
environments.push((
|
|
REMOTE_ENVIRONMENT_ID.to_string(),
|
|
ExecServerTransportParams::websocket_url(
|
|
exec_server_url,
|
|
DEFAULT_REMOTE_EXEC_SERVER_CONNECT_TIMEOUT,
|
|
),
|
|
));
|
|
}
|
|
|
|
let has_remote = environments
|
|
.iter()
|
|
.any(|(id, _environment)| id == REMOTE_ENVIRONMENT_ID);
|
|
let include_local = !disabled && !has_remote;
|
|
let default = if disabled {
|
|
EnvironmentDefault::Disabled
|
|
} else if has_remote {
|
|
EnvironmentDefault::EnvironmentId(REMOTE_ENVIRONMENT_ID.to_string())
|
|
} else {
|
|
EnvironmentDefault::EnvironmentId(LOCAL_ENVIRONMENT_ID.to_string())
|
|
};
|
|
|
|
EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
include_local,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EnvironmentProvider for DefaultEnvironmentProvider {
|
|
fn snapshot(&self) -> EnvironmentProviderFuture<'_> {
|
|
Box::pin(async { Ok(self.snapshot_inner()) })
|
|
}
|
|
}
|
|
|
|
pub(crate) fn normalize_exec_server_url(exec_server_url: Option<String>) -> (Option<String>, bool) {
|
|
match exec_server_url.as_deref().map(str::trim) {
|
|
None | Some("") => (None, false),
|
|
Some(url) if url.eq_ignore_ascii_case("none") => (None, true),
|
|
Some(url) => (Some(url.to_string()), false),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn default_provider_requests_local_environment_when_url_is_missing() {
|
|
let provider = DefaultEnvironmentProvider::new(/*exec_server_url*/ None);
|
|
let snapshot = provider.snapshot().await.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
include_local,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(include_local);
|
|
assert!(!environments.contains_key(LOCAL_ENVIRONMENT_ID));
|
|
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(LOCAL_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn default_provider_requests_local_environment_when_url_is_empty() {
|
|
let provider = DefaultEnvironmentProvider::new(Some(String::new()));
|
|
let snapshot = provider.snapshot().await.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
include_local,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(include_local);
|
|
assert!(!environments.contains_key(LOCAL_ENVIRONMENT_ID));
|
|
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(LOCAL_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn default_provider_omits_local_environment_for_none_value() {
|
|
let provider = DefaultEnvironmentProvider::new(Some("none".to_string()));
|
|
let snapshot = provider.snapshot().await.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
include_local,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!include_local);
|
|
assert!(!environments.contains_key(LOCAL_ENVIRONMENT_ID));
|
|
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
|
|
assert_eq!(default, EnvironmentDefault::Disabled);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn default_provider_adds_remote_environment_for_websocket_url() {
|
|
let provider = DefaultEnvironmentProvider::new(Some("ws://127.0.0.1:8765".to_string()));
|
|
let snapshot = provider.snapshot().await.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
include_local,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!include_local);
|
|
assert!(!environments.contains_key(LOCAL_ENVIRONMENT_ID));
|
|
assert!(matches!(
|
|
&environments[REMOTE_ENVIRONMENT_ID],
|
|
ExecServerTransportParams::WebSocketUrl { websocket_url, .. }
|
|
if websocket_url == "ws://127.0.0.1:8765"
|
|
));
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(REMOTE_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn normalizes_exec_server_url() {
|
|
assert_eq!(
|
|
normalize_exec_server_url(Some(" ws://127.0.0.1:8765 ".to_string())),
|
|
(Some("ws://127.0.0.1:8765".to_string()), false)
|
|
);
|
|
}
|
|
}
|