Split provider environments from default id

Remove the EnvironmentProviderSnapshot wrapper. Providers now expose environments and the selected default id directly, while EnvironmentManager validates that the default id exists in the returned environment map.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
starr-openai
2026-05-04 13:03:44 -07:00
parent 7e7d8698c3
commit 9ddbb8b10e
2 changed files with 93 additions and 118 deletions

View File

@@ -10,7 +10,6 @@ use crate::client::http_client::ReqwestHttpClient;
use crate::client_api::ExecServerTransportParams;
use crate::environment_provider::DefaultEnvironmentProvider;
use crate::environment_provider::EnvironmentProvider;
use crate::environment_provider::EnvironmentProviderSnapshot;
use crate::environment_provider::normalize_exec_server_url;
use crate::local_file_system::LocalFileSystem;
use crate::local_process::LocalProcess;
@@ -24,7 +23,8 @@ pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL";
///
/// `EnvironmentManager` is a shared registry for concrete environments. Its
/// default constructor preserves the legacy `CODEX_EXEC_SERVER_URL` behavior
/// while provider-based construction accepts a provider-supplied snapshot.
/// while provider-based construction accepts a provider-supplied environment
/// list and default id.
///
/// Setting `CODEX_EXEC_SERVER_URL=none` disables environment access by leaving
/// the default environment unset while still keeping an explicit local
@@ -73,11 +73,7 @@ impl EnvironmentManager {
/// Builds a test-only manager with environment access disabled.
pub fn disabled_for_tests(local_runtime_paths: ExecServerRuntimePaths) -> Self {
let snapshot = EnvironmentProviderSnapshot {
environments: HashMap::new(),
default_environment_id: None,
};
match Self::from_provider_snapshot(snapshot, local_runtime_paths) {
match Self::from_provider_parts(HashMap::new(), None, local_runtime_paths) {
Ok(manager) => manager,
Err(err) => panic!("disabled test environment manager: {err}"),
}
@@ -106,14 +102,17 @@ impl EnvironmentManager {
local_runtime_paths: ExecServerRuntimePaths,
) -> Self {
let provider = DefaultEnvironmentProvider::new(exec_server_url);
let snapshot = provider.snapshot(&local_runtime_paths);
match Self::from_provider_snapshot(snapshot, local_runtime_paths) {
match Self::from_provider_parts(
provider.environments(&local_runtime_paths),
provider.default_id(),
local_runtime_paths,
) {
Ok(manager) => manager,
Err(err) => panic!("default provider should create valid environments: {err}"),
}
}
/// Builds a manager from a provider-supplied startup snapshot.
/// Builds a manager from a provider-supplied environment list and default.
pub async fn from_provider<P>(
provider: &P,
local_runtime_paths: ExecServerRuntimePaths,
@@ -121,20 +120,16 @@ impl EnvironmentManager {
where
P: EnvironmentProvider + ?Sized,
{
let snapshot = provider
.get_environment_snapshot(&local_runtime_paths)
.await?;
Self::from_provider_snapshot(snapshot, local_runtime_paths)
let environments = provider.get_environments(&local_runtime_paths).await?;
let default_environment_id = provider.default_environment_id();
Self::from_provider_parts(environments, default_environment_id, local_runtime_paths)
}
fn from_provider_snapshot(
snapshot: EnvironmentProviderSnapshot,
fn from_provider_parts(
environments: HashMap<String, Environment>,
default_environment_id: Option<String>,
local_runtime_paths: ExecServerRuntimePaths,
) -> Result<Self, ExecServerError> {
let EnvironmentProviderSnapshot {
environments,
default_environment_id,
} = snapshot;
for id in environments.keys() {
if id.is_empty() {
return Err(ExecServerError::Protocol(
@@ -433,16 +428,14 @@ mod tests {
}
#[tokio::test]
async fn environment_manager_builds_from_provider_snapshot() {
let manager = EnvironmentManager::from_provider_snapshot(
EnvironmentProviderSnapshot {
environments: HashMap::from([(
REMOTE_ENVIRONMENT_ID.to_string(),
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
.expect("remote environment"),
)]),
default_environment_id: Some(REMOTE_ENVIRONMENT_ID.to_string()),
},
async fn environment_manager_builds_from_provider_parts() {
let manager = EnvironmentManager::from_provider_parts(
HashMap::from([(
REMOTE_ENVIRONMENT_ID.to_string(),
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
.expect("remote environment"),
)]),
Some(REMOTE_ENVIRONMENT_ID.to_string()),
test_runtime_paths(),
)
.expect("environment manager");
@@ -463,11 +456,9 @@ mod tests {
#[tokio::test]
async fn environment_manager_rejects_empty_environment_id() {
let err = EnvironmentManager::from_provider_snapshot(
EnvironmentProviderSnapshot {
environments: HashMap::from([("".to_string(), Environment::default_for_tests())]),
default_environment_id: None,
},
let err = EnvironmentManager::from_provider_parts(
HashMap::from([("".to_string(), Environment::default_for_tests())]),
None,
test_runtime_paths(),
)
.expect_err("empty id should fail");
@@ -480,21 +471,19 @@ mod tests {
#[tokio::test]
async fn environment_manager_uses_explicit_provider_default() {
let manager = EnvironmentManager::from_provider_snapshot(
EnvironmentProviderSnapshot {
environments: HashMap::from([
(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
),
(
"devbox".to_string(),
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
.expect("remote environment"),
),
]),
default_environment_id: Some("devbox".to_string()),
},
let manager = EnvironmentManager::from_provider_parts(
HashMap::from([
(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
),
(
"devbox".to_string(),
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
.expect("remote environment"),
),
]),
Some("devbox".to_string()),
test_runtime_paths(),
)
.expect("manager");
@@ -505,14 +494,12 @@ mod tests {
#[tokio::test]
async fn environment_manager_disables_provider_default() {
let manager = EnvironmentManager::from_provider_snapshot(
EnvironmentProviderSnapshot {
environments: HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
)]),
default_environment_id: None,
},
let manager = EnvironmentManager::from_provider_parts(
HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
)]),
None,
test_runtime_paths(),
)
.expect("manager");
@@ -524,14 +511,12 @@ mod tests {
#[tokio::test]
async fn environment_manager_rejects_unknown_provider_default() {
let err = EnvironmentManager::from_provider_snapshot(
EnvironmentProviderSnapshot {
environments: HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
)]),
default_environment_id: Some("missing".to_string()),
},
let err = EnvironmentManager::from_provider_parts(
HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::default_for_tests(),
)]),
Some("missing".to_string()),
test_runtime_paths(),
)
.expect_err("unknown default should fail");

View File

@@ -11,21 +11,20 @@ use crate::environment::REMOTE_ENVIRONMENT_ID;
/// Lists the concrete environments available to Codex.
///
/// Implementations should return the provider-owned startup snapshot that
/// `EnvironmentManager` will cache. Providers that want the local environment to
/// be addressable by id should include it explicitly in the returned map.
/// Implementations own both the available environment list and the default
/// environment id. Providers that want the local environment to be addressable
/// by id should include it explicitly in the returned map.
#[async_trait]
pub trait EnvironmentProvider: Send + Sync {
/// Returns the environments available for a new manager.
async fn get_environment_snapshot(
async fn get_environments(
&self,
local_runtime_paths: &ExecServerRuntimePaths,
) -> Result<EnvironmentProviderSnapshot, ExecServerError>;
}
) -> Result<HashMap<String, Environment>, ExecServerError>;
pub struct EnvironmentProviderSnapshot {
pub environments: HashMap<String, Environment>,
pub default_environment_id: Option<String>,
/// Returns the provider-selected default environment id, or `None` to
/// disable model-facing environment access.
fn default_environment_id(&self) -> Option<String>;
}
/// Default provider backed by `CODEX_EXEC_SERVER_URL`.
@@ -45,15 +44,15 @@ impl DefaultEnvironmentProvider {
Self::new(std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok())
}
pub(crate) fn snapshot(
pub(crate) fn environments(
&self,
local_runtime_paths: &ExecServerRuntimePaths,
) -> EnvironmentProviderSnapshot {
) -> HashMap<String, Environment> {
let mut environments = HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Environment::local(local_runtime_paths.clone()),
)]);
let (exec_server_url, disabled) = normalize_exec_server_url(self.exec_server_url.clone());
let (exec_server_url, _disabled) = normalize_exec_server_url(self.exec_server_url.clone());
if let Some(exec_server_url) = exec_server_url {
environments.insert(
@@ -62,36 +61,32 @@ impl DefaultEnvironmentProvider {
);
}
let default_environment_id = if disabled {
None
} else {
derived_default_environment_id(&environments)
};
environments
}
EnvironmentProviderSnapshot {
environments,
default_environment_id,
pub(crate) fn default_id(&self) -> Option<String> {
let (exec_server_url, disabled) = normalize_exec_server_url(self.exec_server_url.clone());
if disabled {
None
} else if exec_server_url.is_some() {
Some(REMOTE_ENVIRONMENT_ID.to_string())
} else {
Some(LOCAL_ENVIRONMENT_ID.to_string())
}
}
}
#[async_trait]
impl EnvironmentProvider for DefaultEnvironmentProvider {
async fn get_environment_snapshot(
async fn get_environments(
&self,
local_runtime_paths: &ExecServerRuntimePaths,
) -> Result<EnvironmentProviderSnapshot, ExecServerError> {
Ok(self.snapshot(local_runtime_paths))
) -> Result<HashMap<String, Environment>, ExecServerError> {
Ok(self.environments(local_runtime_paths))
}
}
fn derived_default_environment_id(environments: &HashMap<String, Environment>) -> Option<String> {
if environments.contains_key(REMOTE_ENVIRONMENT_ID) {
Some(REMOTE_ENVIRONMENT_ID.to_string())
} else if environments.contains_key(LOCAL_ENVIRONMENT_ID) {
Some(LOCAL_ENVIRONMENT_ID.to_string())
} else {
None
fn default_environment_id(&self) -> Option<String> {
self.default_id()
}
}
@@ -122,11 +117,10 @@ mod tests {
async fn default_provider_returns_local_environment_when_url_is_missing() {
let provider = DefaultEnvironmentProvider::new(/*exec_server_url*/ None);
let runtime_paths = test_runtime_paths();
let snapshot = provider
.get_environment_snapshot(&runtime_paths)
let environments = provider
.get_environments(&runtime_paths)
.await
.expect("environment snapshot");
let environments = snapshot.environments;
.expect("environments");
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
assert_eq!(
@@ -140,11 +134,10 @@ mod tests {
async fn default_provider_returns_local_environment_when_url_is_empty() {
let provider = DefaultEnvironmentProvider::new(Some(String::new()));
let runtime_paths = test_runtime_paths();
let snapshot = provider
.get_environment_snapshot(&runtime_paths)
let environments = provider
.get_environments(&runtime_paths)
.await
.expect("environment snapshot");
let environments = snapshot.environments;
.expect("environments");
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
@@ -154,26 +147,24 @@ mod tests {
async fn default_provider_returns_local_environment_for_none_value() {
let provider = DefaultEnvironmentProvider::new(Some("none".to_string()));
let runtime_paths = test_runtime_paths();
let snapshot = provider
.get_environment_snapshot(&runtime_paths)
let environments = provider
.get_environments(&runtime_paths)
.await
.expect("environment snapshot");
let environments = snapshot.environments;
.expect("environments");
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
assert_eq!(snapshot.default_environment_id, None);
assert_eq!(provider.default_environment_id(), None);
}
#[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 runtime_paths = test_runtime_paths();
let snapshot = provider
.get_environment_snapshot(&runtime_paths)
let environments = provider
.get_environments(&runtime_paths)
.await
.expect("environment snapshot");
let environments = snapshot.environments;
.expect("environments");
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
let remote_environment = &environments[REMOTE_ENVIRONMENT_ID];
@@ -188,11 +179,10 @@ mod tests {
async fn default_provider_normalizes_exec_server_url() {
let provider = DefaultEnvironmentProvider::new(Some(" ws://127.0.0.1:8765 ".to_string()));
let runtime_paths = test_runtime_paths();
let snapshot = provider
.get_environment_snapshot(&runtime_paths)
let environments = provider
.get_environments(&runtime_paths)
.await
.expect("environment snapshot");
let environments = snapshot.environments;
.expect("environments");
assert_eq!(
environments[REMOTE_ENVIRONMENT_ID].exec_server_url(),