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
This commit is contained in:
sayan-oai
2026-08-07 08:32:30 +00:00
committed by copyberry
parent 957f8eedee
commit 95c7265e84
12 changed files with 348 additions and 2 deletions
+10
View File
@@ -45,6 +45,7 @@ use crate::process::ExecProcessEventReceiver;
use crate::protocol::CAPABILITY_ROOTS_DISCOVER_METHOD;
use crate::protocol::CapabilityRootsDiscoverParams;
use crate::protocol::CapabilityRootsDiscoverResponse;
use crate::protocol::ENVIRONMENT_CONFIG_READ_METHOD;
use crate::protocol::ENVIRONMENT_INFO_METHOD;
use crate::protocol::ENVIRONMENT_STATUS_METHOD;
use crate::protocol::EXEC_CLOSED_METHOD;
@@ -55,6 +56,8 @@ use crate::protocol::EXEC_READ_METHOD;
use crate::protocol::EXEC_SIGNAL_METHOD;
use crate::protocol::EXEC_TERMINATE_METHOD;
use crate::protocol::EXEC_WRITE_METHOD;
use crate::protocol::EnvironmentConfigReadParams;
use crate::protocol::EnvironmentConfigReadResponse;
use crate::protocol::EnvironmentInfo;
use crate::protocol::EnvironmentStatus;
use crate::protocol::ExecClosedNotification;
@@ -736,6 +739,13 @@ impl ExecServerClient {
)
}
pub async fn read_environment_config(
&self,
params: EnvironmentConfigReadParams,
) -> Result<EnvironmentConfigReadResponse, ExecServerError> {
self.call(ENVIRONMENT_CONFIG_READ_METHOD, &params).await
}
pub async fn environment_status(&self) -> Result<EnvironmentStatus, ExecServerError> {
// Health checks only reuse an existing RPC connection and never initiate recovery.
let rpc_client = self.rpc_client_without_recovery()?;
+16
View File
@@ -12,6 +12,8 @@ use codex_protocol::capabilities::SelectedCapabilityRoot;
use crate::CapabilityRootsDiscoverParams;
use crate::CapabilityRootsDiscoverResponse;
use crate::EnvironmentConfigReadParams;
use crate::EnvironmentConfigReadResponse;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecutorFileSystem;
@@ -24,6 +26,7 @@ use crate::client_api::DEFAULT_REMOTE_EXEC_SERVER_CONNECT_TIMEOUT;
use crate::client_api::ExecServerTransportParams;
use crate::environment_bootstrap::PreparedEnvironmentManager;
use crate::environment_bootstrap::PreparedEnvironmentSource;
use crate::environment_config::read_environment_config;
use crate::environment_provider::DefaultEnvironmentProvider;
use crate::environment_provider::EnvironmentDefault;
use crate::environment_provider::EnvironmentProvider;
@@ -881,6 +884,19 @@ impl Environment {
}
}
/// Reads selected executor-local configuration fields for this environment.
pub async fn read_environment_config(
&self,
params: EnvironmentConfigReadParams,
) -> Result<EnvironmentConfigReadResponse, ExecServerError> {
match &self.remote_client {
Some(client) => client.get().await?.read_environment_config(params).await,
None => read_environment_config(self.filesystem.as_ref(), params)
.await
.map_err(|error| ExecServerError::Protocol(error.to_string())),
}
}
/// Discovers plugin and skill manifests through the environment's high-level discovery API.
pub async fn discover_capability_roots(
&self,
@@ -0,0 +1,98 @@
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,
})
}
+5
View File
@@ -7,6 +7,7 @@ mod client_transport;
mod connection;
mod environment;
mod environment_bootstrap;
mod environment_config;
mod environment_provider;
mod environment_registry;
mod environment_toml;
@@ -123,6 +124,10 @@ pub use protocol::CapabilityTextFile;
pub use protocol::DiscoveredPluginFiles;
pub use protocol::DiscoveredSkillFiles;
pub use protocol::EnvironmentCapabilities;
pub use protocol::EnvironmentConfigLayer;
pub use protocol::EnvironmentConfigLayerStack;
pub use protocol::EnvironmentConfigReadParams;
pub use protocol::EnvironmentConfigReadResponse;
pub use protocol::EnvironmentInfo;
pub use protocol::EnvironmentStatus;
pub use protocol::EnvironmentStatusKind;
@@ -16,8 +16,12 @@ use crate::ExecServerRuntimePaths;
use crate::client::http_client::PendingRouteAwareHttpBodyStream;
use crate::client::http_client::RouteAwareHttpClient;
use crate::client::http_client::RouteAwareHttpRequestRunner;
use crate::environment_config::ReadEnvironmentConfigError;
use crate::environment_config::read_environment_config;
use crate::protocol::CapabilityRootsDiscoverParams;
use crate::protocol::CapabilityRootsDiscoverResponse;
use crate::protocol::EnvironmentConfigReadParams;
use crate::protocol::EnvironmentConfigReadResponse;
use crate::protocol::EnvironmentInfo;
use crate::protocol::EnvironmentStatus;
use crate::protocol::EnvironmentStatusKind;
@@ -175,6 +179,19 @@ impl ExecServerHandler {
Ok(EnvironmentInfo::local())
}
pub(crate) async fn environment_config_read(
&self,
params: EnvironmentConfigReadParams,
) -> Result<EnvironmentConfigReadResponse, JSONRPCErrorError> {
self.require_initialized_for("environment config")?;
read_environment_config(crate::LOCAL_FS.as_ref(), params)
.await
.map_err(|error| match error {
ReadEnvironmentConfigError::InvalidParams(message) => invalid_params(message),
ReadEnvironmentConfigError::Internal(message) => internal_error(message),
})
}
pub(crate) fn environment_status(&self) -> Result<EnvironmentStatus, JSONRPCErrorError> {
self.require_initialized_for("environment status")?;
Ok(EnvironmentStatus {
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::protocol::CAPABILITY_ROOTS_DISCOVER_METHOD;
use crate::protocol::CapabilityRootsDiscoverParams;
use crate::protocol::ENVIRONMENT_CONFIG_READ_METHOD;
use crate::protocol::ENVIRONMENT_INFO_METHOD;
use crate::protocol::ENVIRONMENT_STATUS_METHOD;
use crate::protocol::EXEC_METHOD;
@@ -9,6 +10,7 @@ use crate::protocol::EXEC_READ_METHOD;
use crate::protocol::EXEC_SIGNAL_METHOD;
use crate::protocol::EXEC_TERMINATE_METHOD;
use crate::protocol::EXEC_WRITE_METHOD;
use crate::protocol::EnvironmentConfigReadParams;
use crate::protocol::ExecParams;
use crate::protocol::FS_CANONICALIZE_METHOD;
use crate::protocol::FS_CLOSE_METHOD;
@@ -74,6 +76,12 @@ pub(crate) fn build_router() -> RpcRouter<ExecServerHandler> {
ENVIRONMENT_INFO_METHOD,
|handler: Arc<ExecServerHandler>, _params: ()| async move { handler.environment_info() },
);
router.request(
ENVIRONMENT_CONFIG_READ_METHOD,
|handler: Arc<ExecServerHandler>, params: EnvironmentConfigReadParams| async move {
handler.environment_config_read(params).await
},
);
router.request(
ENVIRONMENT_STATUS_METHOD,
|handler: Arc<ExecServerHandler>, _params: ()| async move { handler.environment_status() },