Files
codex/codex-rs/exec-server/src/environment_config.rs
T
sayan-oai 95c7265e84 Add executor-local config reads to the exec server (#37408)
## What changed

- Add the `environmentConfig/read` RPC for selecting literal TOML paths from executor-local config and requirements layers.
- Return layer precedence, cloud insertion points, source and base-directory metadata, and executor home and hostname context without normalizing path-bearing values.
- Expose the operation through both remote clients and local `Environment` instances, with invalid selectors reported as invalid parameters.

## Testing

- Cover projected remote config reads and rejection of empty selectors.

GitOrigin-RevId: fee15bf833de6bcb2058d405ed5b1d5d928d7218
2026-08-07 08:32:30 +00:00

99 lines
3.6 KiB
Rust

use codex_config::CONFIG_TOML_FILE;
use codex_config::format_config_layer_source;
use codex_config::host_name;
use codex_config::loader::LocalTomlLayerStack;
use codex_config::loader::load_local_config_layers;
use codex_exec_server_protocol::EnvironmentConfigLayer;
use codex_exec_server_protocol::EnvironmentConfigLayerStack;
use codex_exec_server_protocol::EnvironmentConfigReadParams;
use codex_exec_server_protocol::EnvironmentConfigReadResponse;
use codex_file_system::ExecutorFileSystem;
use codex_utils_home_dir::find_codex_home;
use codex_utils_path_uri::PathUri;
#[derive(Debug, thiserror::Error)]
pub(crate) enum ReadEnvironmentConfigError {
#[error("{0}")]
InvalidParams(String),
#[error("{0}")]
Internal(String),
}
pub(crate) async fn read_environment_config(
file_system: &dyn ExecutorFileSystem,
params: EnvironmentConfigReadParams,
) -> Result<EnvironmentConfigReadResponse, ReadEnvironmentConfigError> {
validate_paths(&params)?;
let cwd = params
.cwd
.to_abs_path()
.map_err(|error| ReadEnvironmentConfigError::InvalidParams(error.to_string()))?;
let codex_home = find_codex_home().map_err(|error| {
ReadEnvironmentConfigError::Internal(format!("failed to find Codex home: {error}"))
})?;
let layers = load_local_config_layers(file_system, codex_home.as_path(), &cwd)
.await
.map_err(|error| {
ReadEnvironmentConfigError::Internal(format!(
"failed to load executor-local config: {error}"
))
})?
.project(&params.config_paths, &params.requirements_paths);
Ok(EnvironmentConfigReadResponse {
user_home_dir: dirs::home_dir()
.and_then(|home_dir| PathUri::from_host_native_path(home_dir).ok()),
codex_home_dir: PathUri::from_abs_path(&codex_home),
hostname: host_name(),
config: serialize_layer_stack(layers.config, |source| {
format_config_layer_source(source, CONFIG_TOML_FILE)
})?,
requirements: serialize_layer_stack(layers.requirements, ToString::to_string)?,
})
}
fn validate_paths(params: &EnvironmentConfigReadParams) -> Result<(), ReadEnvironmentConfigError> {
if params.config_paths.is_empty() && params.requirements_paths.is_empty() {
return Err(ReadEnvironmentConfigError::InvalidParams(
"at least one config or requirements path is required".to_string(),
));
}
if params
.config_paths
.iter()
.chain(&params.requirements_paths)
.any(Vec::is_empty)
{
return Err(ReadEnvironmentConfigError::InvalidParams(
"TOML paths must contain at least one key segment".to_string(),
));
}
Ok(())
}
fn serialize_layer_stack<S>(
stack: LocalTomlLayerStack<S>,
source_name: impl Fn(&S) -> String,
) -> Result<EnvironmentConfigLayerStack, ReadEnvironmentConfigError> {
let layers = stack
.layers
.into_iter()
.map(|layer| {
let toml = toml::to_string(&layer.toml).map_err(|error| {
ReadEnvironmentConfigError::Internal(format!(
"failed to serialize executor-local config: {error}"
))
})?;
Ok(EnvironmentConfigLayer {
source: source_name(&layer.source),
base_dir: PathUri::from_abs_path(&layer.base_dir),
toml,
})
})
.collect::<Result<Vec<_>, ReadEnvironmentConfigError>>()?;
Ok(EnvironmentConfigLayerStack {
layers,
cloud_insertion_index: stack.cloud_insertion_index,
})
}