Compare commits

...

4 Commits

Author SHA1 Message Date
starr-openai
7933a9adf3 Store static environment URL on resolver
Co-authored-by: Codex <noreply@openai.com>
2026-04-28 13:20:30 -07:00
starr-openai
a66bb6303d Avoid expect in static environment config
Co-authored-by: Codex <noreply@openai.com>
2026-04-28 13:14:32 -07:00
starr-openai
900ad27d28 Add lazy environment resolution demo
Thread provider-owned environment resolvers through EnvironmentConfiguration so EnvironmentManager can cache stable Environment handles while remote clients resolve websocket URLs on first use.

Co-authored-by: Codex <noreply@openai.com>
2026-04-28 13:03:58 -07:00
starr-openai
430fde2ab1 Add environment provider snapshot
Separate provider-owned environment configuration listing from EnvironmentManager's concrete environment cache. The default provider preserves the existing CODEX_EXEC_SERVER_URL behavior while the manager keeps local environment ownership internal.

Co-authored-by: Codex <noreply@openai.com>
2026-04-28 11:45:35 -07:00
4 changed files with 596 additions and 41 deletions

View File

@@ -25,6 +25,8 @@ use crate::client_api::ExecServerClientConnectOptions;
use crate::client_api::HttpClient;
use crate::client_api::RemoteExecServerConnectArgs;
use crate::connection::JsonRpcConnection;
use crate::environment_provider::EnvironmentResolver;
use crate::environment_provider::normalize_remote_exec_server_url;
use crate::process::ExecProcessEvent;
use crate::process::ExecProcessEventLog;
use crate::process::ExecProcessEventReceiver;
@@ -180,14 +182,16 @@ pub struct ExecServerClient {
#[derive(Clone)]
pub(crate) struct LazyRemoteExecServerClient {
websocket_url: String,
environment_id: String,
resolver: Arc<dyn EnvironmentResolver>,
client: Arc<OnceCell<ExecServerClient>>,
}
impl LazyRemoteExecServerClient {
pub(crate) fn new(websocket_url: String) -> Self {
pub(crate) fn new(environment_id: String, resolver: Arc<dyn EnvironmentResolver>) -> Self {
Self {
websocket_url,
environment_id,
resolver,
client: Arc::new(OnceCell::new()),
}
}
@@ -195,8 +199,13 @@ impl LazyRemoteExecServerClient {
pub(crate) async fn get(&self) -> Result<ExecServerClient, ExecServerError> {
self.client
.get_or_try_init(|| async {
let resolved = self.resolver.resolve().await?;
let websocket_url = normalize_remote_exec_server_url(
self.environment_id.as_str(),
resolved.exec_server_url,
)?;
ExecServerClient::connect_websocket(RemoteExecServerConnectArgs {
websocket_url: self.websocket_url.clone(),
websocket_url,
client_name: "codex-environment".to_string(),
connect_timeout: Duration::from_secs(5),
initialize_timeout: Duration::from_secs(5),

View File

@@ -7,6 +7,11 @@ use crate::ExecutorFileSystem;
use crate::HttpClient;
use crate::client::LazyRemoteExecServerClient;
use crate::client::http_client::ReqwestHttpClient;
use crate::environment_provider::DefaultEnvironmentProvider;
use crate::environment_provider::EnvironmentConfiguration;
use crate::environment_provider::EnvironmentConfigurations;
use crate::environment_provider::EnvironmentProvider;
use crate::environment_provider::normalize_exec_server_url;
use crate::local_file_system::LocalFileSystem;
use crate::local_process::LocalProcess;
use crate::process::ExecBackend;
@@ -17,11 +22,9 @@ pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL";
/// Owns the execution/filesystem environments available to the Codex runtime.
///
/// `EnvironmentManager` is a shared registry for concrete environments. It
/// always creates a local environment under [`LOCAL_ENVIRONMENT_ID`]. When
/// `CODEX_EXEC_SERVER_URL` is set to a websocket URL, it also creates a remote
/// environment under [`REMOTE_ENVIRONMENT_ID`] and makes that the default
/// environment. Otherwise the local environment is the default.
/// `EnvironmentManager` is a shared registry for concrete environments. Its
/// default constructor preserves the `CODEX_EXEC_SERVER_URL` behavior by using
/// [`DefaultEnvironmentProvider`] to produce the environment configurations.
///
/// Setting `CODEX_EXEC_SERVER_URL=none` disables environment access by leaving
/// the default environment unset while still keeping the local environment
@@ -82,25 +85,43 @@ impl EnvironmentManager {
exec_server_url,
local_runtime_paths,
} = args;
let (exec_server_url, environment_disabled) = normalize_exec_server_url(exec_server_url);
let provider = DefaultEnvironmentProvider::new(exec_server_url);
Self::from_configurations(provider.environment_configurations(), local_runtime_paths)
}
/// Builds a manager from a provider-supplied startup snapshot.
pub async fn from_provider<P>(
provider: &P,
local_runtime_paths: ExecServerRuntimePaths,
) -> Result<Self, ExecServerError>
where
P: EnvironmentProvider + ?Sized,
{
Ok(Self::from_configurations(
provider.get_environments().await?,
local_runtime_paths,
))
}
fn from_configurations(
environment_configurations: EnvironmentConfigurations,
local_runtime_paths: ExecServerRuntimePaths,
) -> Self {
let default_environment = environment_configurations
.default_environment_id()
.map(str::to_string);
let mut environments = HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Arc::new(Environment::local(local_runtime_paths.clone())),
)]);
let default_environment = if environment_disabled {
None
} else {
match exec_server_url {
Some(exec_server_url) => {
environments.insert(
REMOTE_ENVIRONMENT_ID.to_string(),
Arc::new(Environment::remote(exec_server_url, local_runtime_paths)),
);
Some(REMOTE_ENVIRONMENT_ID.to_string())
}
None => Some(LOCAL_ENVIRONMENT_ID.to_string()),
}
};
for (id, configuration) in environment_configurations.into_environments() {
let environment = Environment::remote_inner(
id.clone(),
configuration,
Some(local_runtime_paths.clone()),
);
environments.insert(id, Arc::new(environment));
}
Self {
default_environment,
@@ -140,6 +161,7 @@ impl EnvironmentManager {
/// paths used by filesystem helpers.
#[derive(Clone)]
pub struct Environment {
is_remote: bool,
exec_server_url: Option<String>,
exec_backend: Arc<dyn ExecBackend>,
filesystem: Arc<dyn ExecutorFileSystem>,
@@ -151,6 +173,7 @@ impl Environment {
/// Builds a test-only local environment without configured sandbox helper paths.
pub fn default_for_tests() -> Self {
Self {
is_remote: false,
exec_server_url: None,
exec_backend: Arc::new(LocalProcess::default()),
filesystem: Arc::new(LocalFileSystem::unsandboxed()),
@@ -196,7 +219,11 @@ impl Environment {
}
Ok(match exec_server_url {
Some(exec_server_url) => Self::remote_inner(exec_server_url, local_runtime_paths),
Some(exec_server_url) => Self::remote_inner(
REMOTE_ENVIRONMENT_ID.to_string(),
EnvironmentConfiguration::normalized_static_url(exec_server_url),
local_runtime_paths,
),
None => match local_runtime_paths {
Some(local_runtime_paths) => Self::local(local_runtime_paths),
None => Self::default_for_tests(),
@@ -206,6 +233,7 @@ impl Environment {
fn local(local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self {
is_remote: false,
exec_server_url: None,
exec_backend: Arc::new(LocalProcess::default()),
filesystem: Arc::new(LocalFileSystem::with_runtime_paths(
@@ -216,21 +244,20 @@ impl Environment {
}
}
fn remote(exec_server_url: String, local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self::remote_inner(exec_server_url, Some(local_runtime_paths))
}
fn remote_inner(
exec_server_url: String,
environment_id: String,
configuration: EnvironmentConfiguration,
local_runtime_paths: Option<ExecServerRuntimePaths>,
) -> Self {
let client = LazyRemoteExecServerClient::new(exec_server_url.clone());
let exec_server_url = configuration.static_exec_server_url().map(str::to_string);
let client = LazyRemoteExecServerClient::new(environment_id, configuration.resolver());
let exec_backend: Arc<dyn ExecBackend> = Arc::new(RemoteProcess::new(client.clone()));
let filesystem: Arc<dyn ExecutorFileSystem> =
Arc::new(RemoteFileSystem::new(client.clone()));
Self {
exec_server_url: Some(exec_server_url),
is_remote: true,
exec_server_url,
exec_backend,
filesystem,
http_client: Arc::new(client),
@@ -239,10 +266,13 @@ impl Environment {
}
pub fn is_remote(&self) -> bool {
self.exec_server_url.is_some()
self.is_remote
}
/// Returns the remote exec-server URL when this environment is remote.
/// Returns the statically configured remote exec-server URL when known.
///
/// Dynamically resolved remote environments return `None` here and resolve
/// their URL on first remote client use.
pub fn exec_server_url(&self) -> Option<&str> {
self.exec_server_url.as_deref()
}
@@ -264,24 +294,23 @@ impl Environment {
}
}
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 std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use super::Environment;
use super::EnvironmentManager;
use super::EnvironmentManagerArgs;
use super::LOCAL_ENVIRONMENT_ID;
use super::REMOTE_ENVIRONMENT_ID;
use crate::EnvironmentConfiguration;
use crate::EnvironmentConfigurations;
use crate::ExecServerRuntimePaths;
use crate::ProcessId;
use futures::FutureExt;
use pretty_assertions::assert_eq;
fn test_runtime_paths() -> ExecServerRuntimePaths {
@@ -380,6 +409,91 @@ mod tests {
));
}
#[tokio::test]
async fn environment_manager_builds_from_provider_configurations() {
let manager = EnvironmentManager::from_configurations(
EnvironmentConfigurations::new(
Some(REMOTE_ENVIRONMENT_ID.to_string()),
HashMap::from([(
REMOTE_ENVIRONMENT_ID.to_string(),
EnvironmentConfiguration::static_url("ws://127.0.0.1:8765".to_string())
.expect("static environment configuration"),
)]),
)
.expect("environment configurations"),
test_runtime_paths(),
);
assert_eq!(
manager.default_environment_id(),
Some(REMOTE_ENVIRONMENT_ID)
);
assert!(
manager
.get_environment(REMOTE_ENVIRONMENT_ID)
.expect("remote environment")
.is_remote()
);
assert!(
!manager
.get_environment(LOCAL_ENVIRONMENT_ID)
.expect("local environment")
.is_remote()
);
}
#[tokio::test]
async fn environment_manager_does_not_resolve_dynamic_environment_when_building_cache() {
let resolve_count = Arc::new(AtomicUsize::new(0));
let configuration = EnvironmentConfiguration::from_resolver_fn({
let resolve_count = Arc::clone(&resolve_count);
move || {
let resolve_count = Arc::clone(&resolve_count);
async move {
resolve_count.fetch_add(1, Ordering::SeqCst);
Ok(crate::ResolvedEnvironment {
exec_server_url: "ws://127.0.0.1:8765".to_string(),
})
}
.boxed()
}
});
let manager = EnvironmentManager::from_configurations(
EnvironmentConfigurations::new(
Some(REMOTE_ENVIRONMENT_ID.to_string()),
HashMap::from([(REMOTE_ENVIRONMENT_ID.to_string(), configuration)]),
)
.expect("environment configurations"),
test_runtime_paths(),
);
assert_eq!(resolve_count.load(Ordering::SeqCst), 0);
assert!(
manager
.get_environment(REMOTE_ENVIRONMENT_ID)
.expect("remote environment")
.is_remote()
);
assert_eq!(resolve_count.load(Ordering::SeqCst), 0);
}
#[tokio::test]
async fn environment_manager_inserts_local_environment() {
let manager = EnvironmentManager::from_configurations(
EnvironmentConfigurations::disabled(),
test_runtime_paths(),
);
assert_eq!(manager.default_environment_id(), None);
assert!(
!manager
.get_environment(LOCAL_ENVIRONMENT_ID)
.expect("local environment")
.is_remote()
);
}
#[tokio::test]
async fn environment_manager_carries_local_runtime_paths() {
let runtime_paths = test_runtime_paths();

View File

@@ -0,0 +1,425 @@
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use async_trait::async_trait;
use futures::future::BoxFuture;
use crate::ExecServerError;
use crate::environment::CODEX_EXEC_SERVER_URL_ENV_VAR;
use crate::environment::LOCAL_ENVIRONMENT_ID;
use crate::environment::REMOTE_ENVIRONMENT_ID;
/// Resolved connection details for a provider-supplied environment.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedEnvironment {
pub exec_server_url: String,
}
/// Resolves provider-supplied environment connection details on demand.
#[async_trait]
pub trait EnvironmentResolver: Send + Sync + fmt::Debug {
/// Returns the static exec-server URL for resolvers that do not need
/// asynchronous lookup.
fn static_exec_server_url(&self) -> Option<&str> {
None
}
async fn resolve(&self) -> Result<ResolvedEnvironment, ExecServerError>;
}
/// Provider-supplied environment definition consumed by `EnvironmentManager`.
#[derive(Clone, Debug)]
pub struct EnvironmentConfiguration {
resolver: Arc<dyn EnvironmentResolver>,
}
impl EnvironmentConfiguration {
pub fn static_url(exec_server_url: String) -> Result<Self, ExecServerError> {
let exec_server_url = normalize_remote_exec_server_url("<static>", exec_server_url)?;
Ok(Self::normalized_static_url(exec_server_url))
}
pub(crate) fn normalized_static_url(exec_server_url: String) -> Self {
debug_assert!(matches!(
normalize_remote_exec_server_url("<static>", exec_server_url.clone()),
Ok(normalized) if normalized == exec_server_url
));
Self {
resolver: Arc::new(StaticEnvironmentResolver { exec_server_url }),
}
}
pub fn with_resolver<R>(resolver: R) -> Self
where
R: EnvironmentResolver + 'static,
{
Self {
resolver: Arc::new(resolver),
}
}
pub fn from_resolver_fn<F>(resolver: F) -> Self
where
F: Fn() -> BoxFuture<'static, Result<ResolvedEnvironment, ExecServerError>>
+ Send
+ Sync
+ 'static,
{
Self::with_resolver(FnEnvironmentResolver {
resolver: Arc::new(resolver),
})
}
pub async fn resolve(&self) -> Result<ResolvedEnvironment, ExecServerError> {
self.resolver.resolve().await
}
pub(crate) fn static_exec_server_url(&self) -> Option<&str> {
self.resolver.static_exec_server_url()
}
pub(crate) fn resolver(&self) -> Arc<dyn EnvironmentResolver> {
Arc::clone(&self.resolver)
}
}
#[derive(Debug)]
struct StaticEnvironmentResolver {
exec_server_url: String,
}
#[async_trait]
impl EnvironmentResolver for StaticEnvironmentResolver {
fn static_exec_server_url(&self) -> Option<&str> {
Some(&self.exec_server_url)
}
async fn resolve(&self) -> Result<ResolvedEnvironment, ExecServerError> {
Ok(ResolvedEnvironment {
exec_server_url: self.exec_server_url.clone(),
})
}
}
struct FnEnvironmentResolver {
resolver: Arc<
dyn Fn() -> BoxFuture<'static, Result<ResolvedEnvironment, ExecServerError>> + Send + Sync,
>,
}
impl fmt::Debug for FnEnvironmentResolver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FnEnvironmentResolver")
.finish_non_exhaustive()
}
}
#[async_trait]
impl EnvironmentResolver for FnEnvironmentResolver {
async fn resolve(&self) -> Result<ResolvedEnvironment, ExecServerError> {
(self.resolver)().await
}
}
/// Provider-supplied environment snapshot consumed by `EnvironmentManager`.
#[derive(Clone, Debug)]
pub struct EnvironmentConfigurations {
default_environment_id: Option<String>,
environments: HashMap<String, EnvironmentConfiguration>,
}
impl EnvironmentConfigurations {
pub fn new(
default_environment_id: Option<String>,
environments: HashMap<String, EnvironmentConfiguration>,
) -> Result<Self, ExecServerError> {
for id in environments.keys() {
if id.is_empty() {
return Err(ExecServerError::Protocol(
"environment configuration id cannot be empty".to_string(),
));
}
if id == LOCAL_ENVIRONMENT_ID {
return Err(ExecServerError::Protocol(format!(
"provider environment configurations must not include `{LOCAL_ENVIRONMENT_ID}`"
)));
}
}
if let Some(default_environment_id) = default_environment_id.as_deref()
&& default_environment_id != LOCAL_ENVIRONMENT_ID
&& !environments.contains_key(default_environment_id)
{
return Err(ExecServerError::Protocol(format!(
"default environment id `{default_environment_id}` has no environment configuration"
)));
}
Ok(Self {
default_environment_id,
environments,
})
}
pub fn disabled() -> Self {
Self {
default_environment_id: None,
environments: HashMap::new(),
}
}
pub fn local_default() -> Self {
Self {
default_environment_id: Some(LOCAL_ENVIRONMENT_ID.to_string()),
environments: HashMap::new(),
}
}
fn remote_default(exec_server_url: String) -> Self {
Self {
default_environment_id: Some(REMOTE_ENVIRONMENT_ID.to_string()),
environments: HashMap::from([(
REMOTE_ENVIRONMENT_ID.to_string(),
EnvironmentConfiguration::normalized_static_url(exec_server_url),
)]),
}
}
pub fn default_environment_id(&self) -> Option<&str> {
self.default_environment_id.as_deref()
}
pub fn into_environments(self) -> HashMap<String, EnvironmentConfiguration> {
self.environments
}
}
/// Lists the concrete environment configurations available to Codex.
///
/// Implementations should return the provider-owned portion of the startup
/// snapshot that `EnvironmentManager` will cache. The local environment is
/// always supplied by `EnvironmentManager`; providers only need to set
/// `local` as the default when they want local to be selected by default.
/// Remote configurations carry their own resolver so providers can choose
/// between static URLs and dynamic, on-demand endpoint lookup.
#[async_trait]
pub trait EnvironmentProvider: Send + Sync {
/// Returns the environment configurations available for a new manager.
async fn get_environments(&self) -> Result<EnvironmentConfigurations, ExecServerError>;
}
/// 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 environment_configurations(&self) -> EnvironmentConfigurations {
let (exec_server_url, environment_disabled) =
normalize_exec_server_url(self.exec_server_url.clone());
if let Some(exec_server_url) = exec_server_url {
EnvironmentConfigurations::remote_default(exec_server_url)
} else if !environment_disabled {
EnvironmentConfigurations::local_default()
} else {
EnvironmentConfigurations::disabled()
}
}
}
#[async_trait]
impl EnvironmentProvider for DefaultEnvironmentProvider {
async fn get_environments(&self) -> Result<EnvironmentConfigurations, ExecServerError> {
Ok(self.environment_configurations())
}
}
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),
}
}
pub(crate) fn normalize_remote_exec_server_url(
environment_id: &str,
exec_server_url: String,
) -> Result<String, ExecServerError> {
match normalize_exec_server_url(Some(exec_server_url)) {
(Some(exec_server_url), false) => Ok(exec_server_url),
(None, false) | (None, true) | (Some(_), true) => Err(ExecServerError::Protocol(format!(
"environment configuration `{environment_id}` must resolve to a remote exec-server URL"
))),
}
}
#[cfg(test)]
mod tests {
use futures::FutureExt;
use pretty_assertions::assert_eq;
use super::*;
fn assert_local_default(configurations: EnvironmentConfigurations) {
assert_eq!(
configurations.default_environment_id(),
Some(LOCAL_ENVIRONMENT_ID)
);
assert!(configurations.environments.is_empty());
}
fn assert_disabled(configurations: EnvironmentConfigurations) {
assert_eq!(configurations.default_environment_id(), None);
assert!(configurations.environments.is_empty());
}
#[tokio::test]
async fn default_provider_uses_local_environment_when_url_is_missing() {
let provider = DefaultEnvironmentProvider::new(/*exec_server_url*/ None);
assert_local_default(provider.get_environments().await.expect("environments"));
}
#[tokio::test]
async fn default_provider_uses_local_environment_when_url_is_empty() {
let provider = DefaultEnvironmentProvider::new(Some(String::new()));
assert_local_default(provider.get_environments().await.expect("environments"));
}
#[tokio::test]
async fn default_provider_disables_default_environment_for_none_value() {
let provider = DefaultEnvironmentProvider::new(Some("none".to_string()));
assert_disabled(provider.get_environments().await.expect("environments"));
}
#[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 configurations = provider.get_environments().await.expect("environments");
assert_eq!(
configurations.default_environment_id(),
Some(REMOTE_ENVIRONMENT_ID)
);
let environment = configurations
.environments
.get(REMOTE_ENVIRONMENT_ID)
.expect("remote configuration");
assert_eq!(
environment.static_exec_server_url(),
Some("ws://127.0.0.1:8765")
);
assert_eq!(
environment.resolve().await.expect("resolved environment"),
ResolvedEnvironment {
exec_server_url: "ws://127.0.0.1:8765".to_string(),
}
);
}
#[test]
fn environment_configurations_rejects_local_provider_entry() {
let err = EnvironmentConfigurations::new(
Some(LOCAL_ENVIRONMENT_ID.to_string()),
HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
EnvironmentConfiguration::static_url("ws://127.0.0.1:8765".to_string())
.expect("static environment configuration"),
)]),
)
.expect_err("local provider entry should fail");
assert_eq!(
err.to_string(),
"exec-server protocol error: provider environment configurations must not include `local`"
);
}
#[test]
fn environment_configurations_rejects_missing_default() {
let err =
EnvironmentConfigurations::new(Some(REMOTE_ENVIRONMENT_ID.to_string()), HashMap::new())
.expect_err("missing default should fail");
assert_eq!(
err.to_string(),
"exec-server protocol error: default environment id `remote` has no environment configuration"
);
}
#[test]
fn static_environment_configuration_rejects_empty_exec_server_url() {
let err =
EnvironmentConfiguration::static_url(String::new()).expect_err("empty URL should fail");
assert_eq!(
err.to_string(),
"exec-server protocol error: environment configuration `<static>` must resolve to a remote exec-server URL"
);
}
#[test]
fn static_environment_configuration_rejects_disabled_exec_server_url() {
let err = EnvironmentConfiguration::static_url("none".to_string())
.expect_err("disabled URL should fail");
assert_eq!(
err.to_string(),
"exec-server protocol error: environment configuration `<static>` must resolve to a remote exec-server URL"
);
}
#[tokio::test]
async fn static_environment_configuration_normalizes_exec_server_url() {
let configuration =
EnvironmentConfiguration::static_url(" ws://127.0.0.1:8765 ".to_string())
.expect("environment configurations");
assert_eq!(
configuration.static_exec_server_url(),
Some("ws://127.0.0.1:8765")
);
assert_eq!(
configuration.resolve().await.expect("resolved environment"),
ResolvedEnvironment {
exec_server_url: "ws://127.0.0.1:8765".to_string(),
}
);
}
#[tokio::test]
async fn environment_configuration_can_resolve_from_closure() {
let configuration = EnvironmentConfiguration::from_resolver_fn(|| {
async {
Ok(ResolvedEnvironment {
exec_server_url: "ws://127.0.0.1:8765".to_string(),
})
}
.boxed()
});
assert_eq!(configuration.static_exec_server_url(), None);
assert_eq!(
configuration.resolve().await.expect("resolved environment"),
ResolvedEnvironment {
exec_server_url: "ws://127.0.0.1:8765".to_string(),
}
);
}
}

View File

@@ -2,6 +2,7 @@ mod client;
mod client_api;
mod connection;
mod environment;
mod environment_provider;
mod fs_helper;
mod fs_helper_main;
mod fs_sandbox;
@@ -38,6 +39,12 @@ pub use environment::EnvironmentManager;
pub use environment::EnvironmentManagerArgs;
pub use environment::LOCAL_ENVIRONMENT_ID;
pub use environment::REMOTE_ENVIRONMENT_ID;
pub use environment_provider::DefaultEnvironmentProvider;
pub use environment_provider::EnvironmentConfiguration;
pub use environment_provider::EnvironmentConfigurations;
pub use environment_provider::EnvironmentProvider;
pub use environment_provider::EnvironmentResolver;
pub use environment_provider::ResolvedEnvironment;
pub use fs_helper::CODEX_FS_HELPER_ARG1;
pub use fs_helper_main::main as run_fs_helper_main;
pub use local_file_system::LOCAL_FS;