Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions codex-rs/app-server/src/config_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use codex_app_server_protocol::ConfigLayer as ApiConfigLayer;
use codex_app_server_protocol::ConfigLayerMetadata as ApiConfigLayerMetadata;
use codex_app_server_protocol::ConfigLayerSource as ApiConfigLayerSource;
use codex_config::ConfigLayer;
use codex_config::ConfigLayerMetadata;
use codex_config::ConfigLayerSource;

/// Converts a config-layer source owned by `codex-config` into the app-server wire type owned by
/// `codex-app-server-protocol`.
///
/// The types stay separate so app-server protocol ownership does not leak into the config domain
/// crate. Because this crate owns neither type, Rust's orphan rules require an explicit conversion
/// function instead of a `From` implementation.
pub(crate) fn config_layer_source_to_api(source: ConfigLayerSource) -> ApiConfigLayerSource {
Comment thread
anp-oai marked this conversation as resolved.
match source {
ConfigLayerSource::Mdm { domain, key } => ApiConfigLayerSource::Mdm { domain, key },
ConfigLayerSource::System { file } => ApiConfigLayerSource::System { file },
ConfigLayerSource::EnterpriseManaged { id, name } => {
ApiConfigLayerSource::EnterpriseManaged { id, name }
}
ConfigLayerSource::User { file, profile } => ApiConfigLayerSource::User { file, profile },
ConfigLayerSource::Project { dot_codex_folder } => {
ApiConfigLayerSource::Project { dot_codex_folder }
}
ConfigLayerSource::SessionFlags => ApiConfigLayerSource::SessionFlags,
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => {
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile { file }
}
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {
ApiConfigLayerSource::LegacyManagedConfigTomlFromMdm
}
}
}

/// Converts config-layer metadata owned by `codex-config` into the app-server wire type owned by
/// `codex-app-server-protocol`.
///
/// The types stay separate so app-server protocol ownership does not leak into the config domain
/// crate. Because this crate owns neither type, Rust's orphan rules require an explicit conversion
/// function instead of a `From` implementation.
pub(crate) fn config_layer_metadata_to_api(
Comment thread
anp-oai marked this conversation as resolved.
metadata: ConfigLayerMetadata,
) -> ApiConfigLayerMetadata {
ApiConfigLayerMetadata {
name: config_layer_source_to_api(metadata.name),
version: metadata.version,
}
}

/// Converts a config layer owned by `codex-config` into the app-server wire type owned by
/// `codex-app-server-protocol`.
///
/// The types stay separate so app-server protocol ownership does not leak into the config domain
/// crate. Because this crate owns neither type, Rust's orphan rules require an explicit conversion
/// function instead of a `From` implementation.
pub(crate) fn config_layer_to_api(layer: ConfigLayer) -> ApiConfigLayer {
Comment thread
anp-oai marked this conversation as resolved.
ApiConfigLayer {
name: config_layer_source_to_api(layer.name),
version: layer.version,
config: layer.config,
disabled_reason: layer.disabled_reason,
}
}
16 changes: 11 additions & 5 deletions codex-rs/app-server/src/config_manager_service.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::config_layer::config_layer_metadata_to_api;
use crate::config_layer::config_layer_to_api;
use crate::config_manager::ConfigManager;
use codex_app_server_protocol::Config as ApiConfig;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigLayerMetadata;
use codex_app_server_protocol::ConfigLayerSource;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigReadResponse;
use codex_app_server_protocol::ConfigValueWriteParams;
Expand All @@ -13,6 +13,8 @@ use codex_app_server_protocol::OverriddenMetadata;
use codex_app_server_protocol::WriteStatus;
use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerMetadata;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ConfigRequirementsToml;
Expand Down Expand Up @@ -136,15 +138,19 @@ impl ConfigManager {

Ok(ConfigReadResponse {
config,
origins: layers.origins(),
origins: layers
.origins()
.into_iter()
.map(|(path, metadata)| (path, config_layer_metadata_to_api(metadata)))
.collect(),
layers: params.include_layers.then(|| {
layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.iter()
.map(|layer| layer.as_layer())
.map(|layer| config_layer_to_api(layer.as_layer()))
.collect()
}),
})
Expand Down Expand Up @@ -671,7 +677,7 @@ fn compute_override_metadata(

Some(OverriddenMetadata {
message,
overriding_layer,
overriding_layer: config_layer_metadata_to_api(overriding_layer),
effective_value: effective_value
.and_then(|value| serde_json::to_value(value).ok())
.unwrap_or(JsonValue::Null),
Expand Down
28 changes: 16 additions & 12 deletions codex-rs/app-server/src/config_manager_service_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use codex_app_server_protocol::AppConfig;
use codex_app_server_protocol::AppToolApproval;
use codex_app_server_protocol::AppsConfig;
use codex_app_server_protocol::AskForApproval;
use codex_app_server_protocol::ConfigLayerSource as ApiConfigLayerSource;
use codex_config::CloudConfigBundleLoader;
use codex_config::LoaderOverrides;
use codex_config::test_support::CloudConfigBundleFixture;
Expand Down Expand Up @@ -374,7 +375,7 @@ async fn read_includes_origins_and_layers() {
.get("approval_policy")
.expect("origin")
.name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: managed_file.clone()
},
);
Expand All @@ -383,7 +384,7 @@ async fn read_includes_origins_and_layers() {
// top of the stack; ignore it so this test stays focused on file/user/system ordering.
let layers = if matches!(
layers.first().map(|layer| &layer.name),
Some(ConfigLayerSource::LegacyManagedConfigTomlFromMdm)
Some(ApiConfigLayerSource::LegacyManagedConfigTomlFromMdm)
) {
&layers[1..]
} else {
Expand All @@ -392,20 +393,20 @@ async fn read_includes_origins_and_layers() {
assert_eq!(layers.len(), 3, "expected three layers");
assert_eq!(
layers.first().unwrap().name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: managed_file.clone()
}
);
assert_eq!(
layers.get(1).unwrap().name,
ConfigLayerSource::User {
ApiConfigLayerSource::User {
file: user_file.clone(),
profile: None,
}
);
assert!(matches!(
layers.get(2).unwrap().name,
ConfigLayerSource::System { .. }
ApiConfigLayerSource::System { .. }
));
}

Expand Down Expand Up @@ -505,7 +506,7 @@ async fn write_value_reports_override() {
.get("approval_policy")
.expect("origin")
.name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: managed_file.clone()
}
);
Expand Down Expand Up @@ -776,7 +777,7 @@ async fn read_reports_managed_overrides_user_and_session_flags() {
assert_eq!(response.config.model.as_deref(), Some("system"));
assert_eq!(
response.origins.get("model").expect("origin").name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: managed_file.clone()
},
);
Expand All @@ -785,20 +786,23 @@ async fn read_reports_managed_overrides_user_and_session_flags() {
// top of the stack; ignore it so this test stays focused on file/session/user ordering.
let layers = if matches!(
layers.first().map(|layer| &layer.name),
Some(ConfigLayerSource::LegacyManagedConfigTomlFromMdm)
Some(ApiConfigLayerSource::LegacyManagedConfigTomlFromMdm)
) {
&layers[1..]
} else {
layers.as_slice()
};
assert_eq!(
layers.first().unwrap().name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
);
assert_eq!(
layers.get(1).unwrap().name,
ApiConfigLayerSource::SessionFlags
);
assert_eq!(layers.get(1).unwrap().name, ConfigLayerSource::SessionFlags);
assert_eq!(
layers.get(2).unwrap().name,
ConfigLayerSource::User {
ApiConfigLayerSource::User {
file: user_file,
profile: None
}
Expand Down Expand Up @@ -836,7 +840,7 @@ async fn write_value_reports_managed_override() {
let overridden = result.overridden_metadata.expect("overridden metadata");
assert_eq!(
overridden.overriding_layer.name,
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
ApiConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
);
assert_eq!(overridden.effective_value, serde_json::json!("never"));
}
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/app-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ use crate::transport::start_remote_control;
use crate::transport::start_stdio_connection;
use crate::transport::start_websocket_acceptor;
use codex_analytics::AppServerRpcTransport;
use codex_app_server_protocol::ConfigLayerSource;
use codex_app_server_protocol::ConfigWarningNotification;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::TextPosition as AppTextPosition;
use codex_app_server_protocol::TextRange as AppTextRange;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLoadError;
use codex_config::TextRange as CoreTextRange;
use codex_core::ExecPolicyError;
Expand Down Expand Up @@ -86,6 +86,7 @@ mod auth_mode;
mod bespoke_event_handling;
mod command_exec;
mod config;
mod config_layer;
mod config_manager;
mod config_manager_service;
mod connection_cleanup;
Expand Down
1 change: 0 additions & 1 deletion codex-rs/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ workspace = true
[dependencies]
anyhow = { workspace = true }
base64 = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-execpolicy = { workspace = true }
codex-features = { workspace = true }
codex-file-system = { workspace = true }
Expand Down
74 changes: 73 additions & 1 deletion codex-rs/config/src/config_layer_source.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
use codex_app_server_protocol::ConfigLayerSource;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde_json::Value as JsonValue;

/// Provenance for one layer in the effective Codex configuration.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigLayerSource {
/// Managed preferences delivered by MDM.
Mdm { domain: String, key: String },
/// Host-wide configuration loaded from a file.
System { file: AbsolutePathBuf },
/// Configuration delivered by an enterprise cloud bundle.
EnterpriseManaged { id: String, name: String },
/// User configuration, optionally augmented by a selected profile.
User {
file: AbsolutePathBuf,
profile: Option<String>,
},
/// Configuration loaded from a project's `.codex` directory.
Project { dot_codex_folder: AbsolutePathBuf },
/// Overrides supplied for the current session.
SessionFlags,
/// Legacy managed configuration loaded from a file.
LegacyManagedConfigTomlFromFile { file: AbsolutePathBuf },
/// Legacy managed configuration delivered by MDM.
LegacyManagedConfigTomlFromMdm,
}

impl ConfigLayerSource {
/// A setting from a layer with a higher precedence overrides a setting
/// from a layer with a lower precedence.
pub fn precedence(&self) -> i16 {
match self {
ConfigLayerSource::Mdm { .. } => 0,
ConfigLayerSource::System { .. } => 10,
ConfigLayerSource::EnterpriseManaged { .. } => 15,
ConfigLayerSource::User { profile, .. } => {
if profile.is_some() {
21
} else {
20
}
}
ConfigLayerSource::Project { .. } => 25,
ConfigLayerSource::SessionFlags => 30,
ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => 40,
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => 50,
}
}
}

/// Compares [`ConfigLayerSource`] by precedence, so `A < B` means settings
/// from layer `A` will be overridden by settings from layer `B`.
impl PartialOrd for ConfigLayerSource {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.precedence().cmp(&other.precedence()))
}
}

/// Identity and version information for a configuration layer.
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigLayerMetadata {
pub name: ConfigLayerSource,
pub version: String,
}

/// A materialized configuration layer and its provenance.
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigLayer {
pub name: ConfigLayerSource,
pub version: String,
pub config: JsonValue,
pub disabled_reason: Option<String>,
}

pub fn format_config_layer_source(source: &ConfigLayerSource, config_toml_file: &str) -> String {
match source {
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/config/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//! rendering them in a user-friendly way.

use crate::ConfigLayerEntry;
use crate::ConfigLayerSource;
use crate::ConfigLayerStack;
use crate::ConfigLayerStackOrdering;
use crate::format_config_layer_source;
use codex_app_server_protocol::ConfigLayerSource;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use serde::de::DeserializeOwned;
use serde_path_to_error::Path as SerdePath;
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/config/src/fingerprint.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use codex_app_server_protocol::ConfigLayerMetadata;
use crate::ConfigLayerMetadata;
use serde_json::Value as JsonValue;
use sha2::Digest;
use sha2::Sha256;
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ pub use cloud_config_layers::CloudConfigFragment;
pub use cloud_config_layers::CloudConfigFragmentSource;
pub use cloud_config_layers::CloudConfigLayerError;
pub use cloud_config_layers::cloud_config_layers_from_fragments;
pub use codex_app_server_protocol::ConfigLayerSource;
pub use codex_protocol::config_types::ProfileV2Name;
pub use codex_protocol::config_types::ProfileV2NameParseError;
pub use codex_utils_absolute_path::AbsolutePathBuf;
pub use config_layer_source::ConfigLayer;
pub use config_layer_source::ConfigLayerMetadata;
pub use config_layer_source::ConfigLayerSource;
pub use config_layer_source::format_config_layer_source;
pub use config_requirements::AppRequirementToml;
pub use config_requirements::AppToolRequirementToml;
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/config/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod tests;
use self::layer_io::LoadedConfigLayers;
use crate::CONFIG_TOML_FILE;
use crate::CloudConfigBundleLayers;
use crate::ConfigLayerSource;
use crate::ProfileV2Name;
use crate::RequirementsLayerEntry;
use crate::compose_requirements;
Expand All @@ -31,7 +32,6 @@ use crate::strict_config::ignored_toml_value_field;
use crate::strict_config::unknown_feature_toml_value_field;
use crate::thread_config::ThreadConfigContext;
use crate::thread_config::ThreadConfigLoader;
use codex_app_server_protocol::ConfigLayerSource;
use codex_file_system::ExecutorFileSystem;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::config_types::ApprovalsReviewer;
Expand Down
Loading