mirror of
https://github.com/openai/codex.git
synced 2026-11-01 00:15:56 +00:00
## Why The earlier PRs add stdio transport support and the config-backed environment provider, but the feature remains inert until normal Codex entrypoints construct `EnvironmentManager` with enough context to discover `CODEX_HOME/environments.toml`. This final stack PR activates the provider while preserving the legacy `CODEX_EXEC_SERVER_URL` fallback when no environments file exists. **Stack position:** this is PR 5 of 5. It is the product wiring PR that activates the configured environment provider added in PR 4. ## What Changed - Thread `codex_home` into `EnvironmentManagerArgs`. - Change `EnvironmentManager::new(...)` to load the provider from `CODEX_HOME`. - Preserve legacy behavior by falling back to `DefaultEnvironmentProvider::from_env()` when `environments.toml` is absent. - Make `environments.toml`-backed managers start new threads with all configured environments, default first, while keeping the legacy env-var path single-default. - Update the app-server, TUI, exec, MCP server, connector, prompt-debug, and thread-manager-sample callsites to pass `codex_home` and handle provider-loading errors. ## Self-Review Notes - The multi-environment startup path is intentionally tied to the `environments.toml` provider. Using `>1` configured environment as the only signal would also expand the legacy `CODEX_EXEC_SERVER_URL` provider because it keeps `local` addressable alongside `remote`. - The startup environment list is still derived inside `EnvironmentManager`; the provider only says whether its snapshot should start new threads with all configured environments. - The thread-manager sample was updated to pass the current `ThreadManager::new(...)` installation id argument so the stack compiles under Bazel. ## Stack - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server listener - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server client transport - 3. https://github.com/openai/codex/pull/20665 - Make environment providers own default selection - 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME environments TOML provider - **5. This PR:** https://github.com/openai/codex/pull/20667 - Load configured environments from CODEX_HOME Split from original draft: https://github.com/openai/codex/pull/20508 ## Validation - `just fmt` - `git diff --check` - `bazel build --config=remote --strategy=remote --remote_download_toplevel //codex-rs/thread-manager-sample:codex-thread-manager-sample` - `bazel test --config=remote --strategy=remote --remote_download_toplevel //codex-rs/exec-server:exec-server-unit-tests` - `bazel test --config=remote --strategy=remote --remote_download_toplevel --test_sharding_strategy=disabled --test_arg=default_thread_environment_selections_use_manager_default_id //codex-rs/core:core-unit-tests` - `bazel test --config=remote --strategy=remote --remote_download_toplevel --test_sharding_strategy=disabled --test_arg=start_thread_uses_all_default_environments_from_codex_home //codex-rs/core:core-unit-tests` ## Documentation This activates `CODEX_HOME/environments.toml`; user-facing documentation should be added before this stack is treated as a documented public workflow. --------- Co-authored-by: Codex <noreply@openai.com>
234 lines
7.8 KiB
Rust
234 lines
7.8 KiB
Rust
use async_trait::async_trait;
|
|
|
|
use crate::Environment;
|
|
use crate::ExecServerError;
|
|
use crate::ExecServerRuntimePaths;
|
|
use crate::environment::CODEX_EXEC_SERVER_URL_ENV_VAR;
|
|
use crate::environment::LOCAL_ENVIRONMENT_ID;
|
|
use crate::environment::REMOTE_ENVIRONMENT_ID;
|
|
|
|
/// Lists the concrete environments available to Codex.
|
|
///
|
|
/// Implementations own a startup snapshot containing both the available
|
|
/// environment list in configured order and the default environment
|
|
/// selection. Providers that want the local environment to be addressable by
|
|
/// id should include it explicitly in the returned list.
|
|
#[async_trait]
|
|
pub trait EnvironmentProvider: Send + Sync {
|
|
/// Returns the provider-owned environment startup snapshot.
|
|
async fn snapshot(
|
|
&self,
|
|
local_runtime_paths: &ExecServerRuntimePaths,
|
|
) -> Result<EnvironmentProviderSnapshot, ExecServerError>;
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct EnvironmentProviderSnapshot {
|
|
pub environments: Vec<(String, Environment)>,
|
|
pub default: EnvironmentDefault,
|
|
}
|
|
|
|
#[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,
|
|
local_runtime_paths: &ExecServerRuntimePaths,
|
|
) -> EnvironmentProviderSnapshot {
|
|
let mut environments = vec![(
|
|
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());
|
|
|
|
if let Some(exec_server_url) = exec_server_url {
|
|
environments.push((
|
|
REMOTE_ENVIRONMENT_ID.to_string(),
|
|
Environment::remote_inner(exec_server_url, Some(local_runtime_paths.clone())),
|
|
));
|
|
}
|
|
|
|
let has_remote = environments
|
|
.iter()
|
|
.any(|(id, _environment)| id == REMOTE_ENVIRONMENT_ID);
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl EnvironmentProvider for DefaultEnvironmentProvider {
|
|
async fn snapshot(
|
|
&self,
|
|
local_runtime_paths: &ExecServerRuntimePaths,
|
|
) -> Result<EnvironmentProviderSnapshot, ExecServerError> {
|
|
Ok(self.snapshot_inner(local_runtime_paths))
|
|
}
|
|
}
|
|
|
|
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::*;
|
|
use crate::ExecServerRuntimePaths;
|
|
|
|
fn test_runtime_paths() -> ExecServerRuntimePaths {
|
|
ExecServerRuntimePaths::new(
|
|
std::env::current_exe().expect("current exe"),
|
|
/*codex_linux_sandbox_exe*/ None,
|
|
)
|
|
.expect("runtime paths")
|
|
}
|
|
|
|
#[tokio::test]
|
|
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
|
|
.snapshot(&runtime_paths)
|
|
.await
|
|
.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
|
|
assert_eq!(
|
|
environments[LOCAL_ENVIRONMENT_ID].local_runtime_paths(),
|
|
Some(&runtime_paths)
|
|
);
|
|
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(LOCAL_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
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
|
|
.snapshot(&runtime_paths)
|
|
.await
|
|
.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
|
|
assert!(!environments.contains_key(REMOTE_ENVIRONMENT_ID));
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(LOCAL_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
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
|
|
.snapshot(&runtime_paths)
|
|
.await
|
|
.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
|
|
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 runtime_paths = test_runtime_paths();
|
|
let snapshot = provider
|
|
.snapshot(&runtime_paths)
|
|
.await
|
|
.expect("environments");
|
|
let EnvironmentProviderSnapshot {
|
|
environments,
|
|
default,
|
|
} = snapshot;
|
|
let environments: HashMap<_, _> = environments.into_iter().collect();
|
|
|
|
assert!(!environments[LOCAL_ENVIRONMENT_ID].is_remote());
|
|
let remote_environment = &environments[REMOTE_ENVIRONMENT_ID];
|
|
assert!(remote_environment.is_remote());
|
|
assert_eq!(
|
|
remote_environment.exec_server_url(),
|
|
Some("ws://127.0.0.1:8765")
|
|
);
|
|
assert_eq!(
|
|
default,
|
|
EnvironmentDefault::EnvironmentId(REMOTE_ENVIRONMENT_ID.to_string())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
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
|
|
.snapshot(&runtime_paths)
|
|
.await
|
|
.expect("environments");
|
|
let environments: HashMap<_, _> = snapshot.environments.into_iter().collect();
|
|
|
|
assert_eq!(
|
|
environments[REMOTE_ENVIRONMENT_ID].exec_server_url(),
|
|
Some("ws://127.0.0.1:8765")
|
|
);
|
|
}
|
|
}
|