mirror of
https://github.com/openai/codex.git
synced 2026-05-05 22:01:37 +03:00
feat: migrate to new constraint-based loading strategy
This commit is contained in:
@@ -2,7 +2,6 @@ use super::CONFIG_TOML_FILE;
|
||||
use super::ConfigToml;
|
||||
use crate::config::edit::ConfigEdit;
|
||||
use crate::config::edit::ConfigEditsBuilder;
|
||||
use crate::config_loader::ConfigLayerEntry;
|
||||
use crate::config_loader::ConfigLayerStack;
|
||||
use crate::config_loader::LoaderOverrides;
|
||||
use crate::config_loader::load_config_layers_state;
|
||||
@@ -20,6 +19,7 @@ use codex_app_server_protocol::ConfigWriteResponse;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_app_server_protocol::OverriddenMetadata;
|
||||
use codex_app_server_protocol::WriteStatus;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
@@ -146,7 +146,13 @@ impl ConfigService {
|
||||
Ok(ConfigReadResponse {
|
||||
config,
|
||||
origins: layers.origins(),
|
||||
layers: params.include_layers.then(|| layers.layers_high_to_low()),
|
||||
layers: params.include_layers.then(|| {
|
||||
layers
|
||||
.layers_high_to_low()
|
||||
.iter()
|
||||
.map(|layer| layer.as_layer())
|
||||
.collect()
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -194,11 +200,14 @@ impl ConfigService {
|
||||
expected_version: Option<String>,
|
||||
edits: Vec<(String, JsonValue, MergeStrategy)>,
|
||||
) -> Result<ConfigWriteResponse, ConfigServiceError> {
|
||||
let allowed_path = self.codex_home.join(CONFIG_TOML_FILE);
|
||||
let provided_path = file_path
|
||||
.as_ref()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| allowed_path.clone());
|
||||
let allowed_path =
|
||||
AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, &self.codex_home)
|
||||
.map_err(|err| ConfigServiceError::io("failed to resolve user config path", err))?;
|
||||
let provided_path = match file_path {
|
||||
Some(path) => AbsolutePathBuf::from_absolute_path(PathBuf::from(path))
|
||||
.map_err(|err| ConfigServiceError::io("failed to resolve user config path", err))?,
|
||||
None => allowed_path.clone(),
|
||||
};
|
||||
|
||||
if !paths_match(&allowed_path, &provided_path) {
|
||||
return Err(ConfigServiceError::write(
|
||||
@@ -211,9 +220,16 @@ impl ConfigService {
|
||||
.load_layers_state()
|
||||
.await
|
||||
.map_err(|err| ConfigServiceError::io("failed to load configuration", err))?;
|
||||
let user_layer = layers.get_user_layer().ok_or_else(|| {
|
||||
// TODO(mbolin): Support creating the user layer if it does not exist.
|
||||
ConfigServiceError::write(
|
||||
ConfigWriteErrorCode::UserLayerNotFound,
|
||||
"user layer not found and we do not support creating it yet",
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(expected) = expected_version.as_deref()
|
||||
&& expected != layers.user.version
|
||||
&& expected != user_layer.version
|
||||
{
|
||||
return Err(ConfigServiceError::write(
|
||||
ConfigWriteErrorCode::ConfigVersionConflict,
|
||||
@@ -221,7 +237,7 @@ impl ConfigService {
|
||||
));
|
||||
}
|
||||
|
||||
let mut user_config = layers.user.config.clone();
|
||||
let mut user_config = user_layer.config.clone();
|
||||
let mut parsed_segments = Vec::new();
|
||||
let mut config_edits = Vec::new();
|
||||
|
||||
@@ -273,7 +289,7 @@ impl ConfigService {
|
||||
)
|
||||
})?;
|
||||
|
||||
let updated_layers = layers.with_user_config(user_config.clone());
|
||||
let updated_layers = layers.with_user_config(&provided_path, user_config.clone());
|
||||
let effective = updated_layers.effective_config();
|
||||
validate_config(&effective).map_err(|err| {
|
||||
ConfigServiceError::write(
|
||||
@@ -296,16 +312,19 @@ impl ConfigService {
|
||||
.map(|_| WriteStatus::OkOverridden)
|
||||
.unwrap_or(WriteStatus::Ok);
|
||||
|
||||
let file_path = provided_path
|
||||
.canonicalize()
|
||||
.unwrap_or(provided_path.clone())
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
Ok(ConfigWriteResponse {
|
||||
status,
|
||||
version: updated_layers.user.version.clone(),
|
||||
file_path,
|
||||
version: updated_layers
|
||||
.get_user_layer()
|
||||
.ok_or_else(|| {
|
||||
ConfigServiceError::write(
|
||||
ConfigWriteErrorCode::UserLayerNotFound,
|
||||
"user layer not found in updated layers",
|
||||
)
|
||||
})?
|
||||
.version
|
||||
.clone(),
|
||||
file_path: provided_path,
|
||||
overridden_metadata: overridden,
|
||||
})
|
||||
}
|
||||
@@ -470,15 +489,15 @@ fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn paths_match(expected: &Path, provided: &Path) -> bool {
|
||||
fn paths_match<P: AsRef<Path>, Q: AsRef<Path>>(expected: P, provided: Q) -> bool {
|
||||
if let (Ok(expanded_expected), Ok(expanded_provided)) = (
|
||||
path_utils::normalize_for_path_comparison(expected),
|
||||
path_utils::normalize_for_path_comparison(provided),
|
||||
path_utils::normalize_for_path_comparison(&expected),
|
||||
path_utils::normalize_for_path_comparison(&provided),
|
||||
) {
|
||||
return expanded_expected == expanded_provided;
|
||||
expanded_expected == expanded_provided
|
||||
} else {
|
||||
expected.as_ref() == provided.as_ref()
|
||||
}
|
||||
|
||||
expected == provided
|
||||
}
|
||||
|
||||
fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a TomlValue> {
|
||||
@@ -501,10 +520,25 @@ fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a Tom
|
||||
|
||||
fn override_message(layer: &ConfigLayerSource) -> String {
|
||||
match layer {
|
||||
ConfigLayerSource::Mdm { .. } => "Overridden by managed policy (mdm)".to_string(),
|
||||
ConfigLayerSource::System { .. } => "Overridden by managed config (system)".to_string(),
|
||||
ConfigLayerSource::Mdm { domain, key: _ } => {
|
||||
format!("Overridden by managed policy (MDM): {domain}")
|
||||
}
|
||||
ConfigLayerSource::System { file } => {
|
||||
format!("Overridden by managed config (system): {}", file.display())
|
||||
}
|
||||
ConfigLayerSource::SessionFlags => "Overridden by session flags".to_string(),
|
||||
ConfigLayerSource::User { .. } => "Overridden by user config".to_string(),
|
||||
ConfigLayerSource::User { file } => {
|
||||
format!("Overridden by user config: {}", file.display())
|
||||
}
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => {
|
||||
format!(
|
||||
"Overridden by legacy managed_config.toml: {}",
|
||||
file.display()
|
||||
)
|
||||
}
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {
|
||||
"Overridden by legacy managed configuration from MDM".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +547,10 @@ fn compute_override_metadata(
|
||||
effective: &TomlValue,
|
||||
segments: &[String],
|
||||
) -> Option<OverriddenMetadata> {
|
||||
let user_value = value_at_path(&layers.user.config, segments);
|
||||
let user_value = match layers.get_user_layer() {
|
||||
Some(user_layer) => value_at_path(&user_layer.config, segments),
|
||||
None => return None,
|
||||
};
|
||||
let effective_value = value_at_path(effective, segments);
|
||||
|
||||
if user_value.is_some() && user_value == effective_value {
|
||||
@@ -524,8 +561,7 @@ fn compute_override_metadata(
|
||||
return None;
|
||||
}
|
||||
|
||||
let effective_layer = find_effective_layer(layers, segments);
|
||||
let overriding_layer = effective_layer.unwrap_or_else(|| layers.user.metadata());
|
||||
let overriding_layer = find_effective_layer(layers, segments)?;
|
||||
let message = override_message(&overriding_layer.name);
|
||||
|
||||
Some(OverriddenMetadata {
|
||||
@@ -554,23 +590,13 @@ fn find_effective_layer(
|
||||
layers: &ConfigLayerStack,
|
||||
segments: &[String],
|
||||
) -> Option<ConfigLayerMetadata> {
|
||||
let check =
|
||||
|state: &ConfigLayerEntry| value_at_path(&state.config, segments).map(|_| state.metadata());
|
||||
for layer in layers.layers_high_to_low() {
|
||||
if let Some(meta) = value_at_path(&layer.config, segments).map(|_| layer.metadata()) {
|
||||
return Some(meta);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mdm) = &layers.mdm
|
||||
&& let Some(meta) = check(mdm)
|
||||
{
|
||||
return Some(meta);
|
||||
}
|
||||
if let Some(system) = &layers.system
|
||||
&& let Some(meta) = check(system)
|
||||
{
|
||||
return Some(meta);
|
||||
}
|
||||
if let Some(meta) = check(&layers.session_flags) {
|
||||
return Some(meta);
|
||||
}
|
||||
check(&layers.user)
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -713,18 +739,18 @@ remote_compaction = true
|
||||
.get("approval_policy")
|
||||
.expect("origin")
|
||||
.name,
|
||||
ConfigLayerSource::System {
|
||||
file: managed_file.clone(),
|
||||
}
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
|
||||
file: managed_file.clone()
|
||||
},
|
||||
);
|
||||
let layers = response.layers.expect("layers present");
|
||||
assert_eq!(layers.len(), 2, "expected two layers");
|
||||
assert_eq!(
|
||||
layers.first().unwrap().name,
|
||||
ConfigLayerSource::System { file: managed_file }
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
|
||||
);
|
||||
assert_eq!(layers.get(1).unwrap().name, ConfigLayerSource::SessionFlags);
|
||||
assert_eq!(
|
||||
layers.last().unwrap().name,
|
||||
layers.get(1).unwrap().name,
|
||||
ConfigLayerSource::User { file: user_file }
|
||||
);
|
||||
}
|
||||
@@ -779,7 +805,9 @@ remote_compaction = true
|
||||
.get("approval_policy")
|
||||
.expect("origin")
|
||||
.name,
|
||||
ConfigLayerSource::System { file: managed_file }
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
|
||||
file: managed_file.clone()
|
||||
}
|
||||
);
|
||||
assert_eq!(result.status, WriteStatus::Ok);
|
||||
assert!(result.overridden_metadata.is_none());
|
||||
@@ -909,14 +937,14 @@ remote_compaction = true
|
||||
assert_eq!(response.config.model.as_deref(), Some("system"));
|
||||
assert_eq!(
|
||||
response.origins.get("model").expect("origin").name,
|
||||
ConfigLayerSource::System {
|
||||
file: managed_file.clone(),
|
||||
}
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
|
||||
file: managed_file.clone()
|
||||
},
|
||||
);
|
||||
let layers = response.layers.expect("layers");
|
||||
assert_eq!(
|
||||
layers.first().unwrap().name,
|
||||
ConfigLayerSource::System { file: managed_file }
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
|
||||
);
|
||||
assert_eq!(layers.get(1).unwrap().name, ConfigLayerSource::SessionFlags);
|
||||
assert_eq!(
|
||||
@@ -959,7 +987,7 @@ remote_compaction = true
|
||||
let overridden = result.overridden_metadata.expect("overridden metadata");
|
||||
assert_eq!(
|
||||
overridden.overriding_layer.name,
|
||||
ConfigLayerSource::System { file: managed_file }
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file: managed_file }
|
||||
);
|
||||
assert_eq!(overridden.effective_value, serde_json::json!("never"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user