mirror of
https://github.com/openai/codex.git
synced 2026-09-13 11:47:17 +00:00
core: scope override reloads to projects
This commit is contained in:
@@ -90,12 +90,8 @@ fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec<LayerMtime> {
|
||||
.iter()
|
||||
.filter_map(|layer| {
|
||||
let path = match &layer.name {
|
||||
ConfigLayerSource::System { file } | ConfigLayerSource::SystemOverride { file } => {
|
||||
Some(file.clone())
|
||||
}
|
||||
ConfigLayerSource::User { file, .. } | ConfigLayerSource::UserOverride { file } => {
|
||||
Some(file.clone())
|
||||
}
|
||||
ConfigLayerSource::System { file } => Some(file.clone()),
|
||||
ConfigLayerSource::User { file, .. } => Some(file.clone()),
|
||||
ConfigLayerSource::Project { dot_codex_folder } => {
|
||||
Some(dot_codex_folder.join(CONFIG_TOML_FILE))
|
||||
}
|
||||
@@ -116,9 +112,9 @@ fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec<LayerMtime> {
|
||||
let optional_override_paths = layers
|
||||
.into_iter()
|
||||
.filter_map(|layer| match &layer.name {
|
||||
ConfigLayerSource::System { file } | ConfigLayerSource::User { file, .. } => file
|
||||
.parent()
|
||||
.map(|parent| parent.join(CONFIG_OVERRIDE_TOML_FILE)),
|
||||
ConfigLayerSource::Project { dot_codex_folder } => {
|
||||
Some(dot_codex_folder.join(CONFIG_OVERRIDE_TOML_FILE))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter(|path| !watched_paths.contains(path))
|
||||
@@ -281,7 +277,6 @@ fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
|
||||
matches!(
|
||||
layer,
|
||||
ConfigLayerSource::User { .. }
|
||||
| ConfigLayerSource::UserOverride { .. }
|
||||
| ConfigLayerSource::Project { .. }
|
||||
| ConfigLayerSource::ProjectOverride { .. }
|
||||
| ConfigLayerSource::SessionFlags
|
||||
|
||||
@@ -8,19 +8,17 @@ use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_proxy_reloader_notices_new_user_override_files() {
|
||||
async fn network_proxy_reloader_notices_new_project_override_files() {
|
||||
let temp = tempdir().expect("create temp dir");
|
||||
let base_file =
|
||||
AbsolutePathBuf::try_from(temp.path().join(CONFIG_TOML_FILE)).expect("base path");
|
||||
let override_file = AbsolutePathBuf::try_from(temp.path().join(CONFIG_OVERRIDE_TOML_FILE))
|
||||
.expect("override path");
|
||||
let dot_codex_folder =
|
||||
AbsolutePathBuf::try_from(temp.path().join(".codex")).expect("project config folder");
|
||||
std::fs::create_dir_all(dot_codex_folder.as_path()).expect("create project config folder");
|
||||
let base_file = dot_codex_folder.join(CONFIG_TOML_FILE);
|
||||
let override_file = dot_codex_folder.join(CONFIG_OVERRIDE_TOML_FILE);
|
||||
std::fs::write(base_file.as_path(), "").expect("write base config");
|
||||
let stack = ConfigLayerStack::new(
|
||||
vec![codex_config::ConfigLayerEntry::new(
|
||||
ConfigLayerSource::User {
|
||||
file: base_file,
|
||||
profile: None,
|
||||
},
|
||||
ConfigLayerSource::Project { dot_codex_folder },
|
||||
toml::Value::Table(Default::default()),
|
||||
)],
|
||||
codex_config::ConfigRequirements::default(),
|
||||
|
||||
@@ -180,7 +180,6 @@ use crate::config::resolve_web_search_mode_for_turn;
|
||||
use crate::context_manager::ContextManager;
|
||||
use crate::context_manager::TotalTokenUsageBreakdown;
|
||||
use crate::thread_rollout_truncation::initial_history_has_prior_user_turns;
|
||||
use codex_config::CONFIG_OVERRIDE_TOML_FILE;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::ConfigLayerSource;
|
||||
use codex_config::ConfigLayerStackOrdering;
|
||||
@@ -1513,11 +1512,6 @@ impl Session {
|
||||
//
|
||||
// Prefer `refresh_runtime_config()` when the host can already provide a materialized
|
||||
// config snapshot. This file-based path exists for legacy local reload flows.
|
||||
enum UserConfigReloadLayer {
|
||||
Base(AbsolutePathBuf),
|
||||
Override(AbsolutePathBuf),
|
||||
}
|
||||
|
||||
let config_toml_paths = {
|
||||
let state = self.state.lock().await;
|
||||
let config = &state.session_configuration.original_config_do_not_use;
|
||||
@@ -1529,47 +1523,25 @@ impl Session {
|
||||
)
|
||||
.into_iter()
|
||||
.filter_map(|layer| match &layer.name {
|
||||
ConfigLayerSource::User { file, .. } => {
|
||||
Some(UserConfigReloadLayer::Base(file.clone()))
|
||||
}
|
||||
ConfigLayerSource::UserOverride { file } => {
|
||||
Some(UserConfigReloadLayer::Override(file.clone()))
|
||||
}
|
||||
ConfigLayerSource::User { file, .. } => Some(file.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let override_path = state
|
||||
.session_configuration
|
||||
.codex_home
|
||||
.join(CONFIG_OVERRIDE_TOML_FILE);
|
||||
let has_override_path = user_config_paths.iter().any(|layer| {
|
||||
matches!(
|
||||
layer,
|
||||
UserConfigReloadLayer::Override(path) if path == &override_path
|
||||
)
|
||||
});
|
||||
let mut user_config_paths = user_config_paths;
|
||||
if !has_override_path && override_path.as_path().exists() {
|
||||
user_config_paths.push(UserConfigReloadLayer::Override(override_path));
|
||||
}
|
||||
if user_config_paths.is_empty() {
|
||||
vec![UserConfigReloadLayer::Base(
|
||||
vec![
|
||||
state
|
||||
.session_configuration
|
||||
.codex_home
|
||||
.join(CONFIG_TOML_FILE),
|
||||
)]
|
||||
]
|
||||
} else {
|
||||
user_config_paths
|
||||
}
|
||||
};
|
||||
|
||||
let mut reloaded_user_configs = Vec::with_capacity(config_toml_paths.len());
|
||||
for reload_layer in config_toml_paths {
|
||||
let config_toml_path = match &reload_layer {
|
||||
UserConfigReloadLayer::Base(path) | UserConfigReloadLayer::Override(path) => path,
|
||||
};
|
||||
let user_config = match std::fs::read_to_string(config_toml_path.as_path()) {
|
||||
for config_toml_path in config_toml_paths {
|
||||
let user_config = match std::fs::read_to_string(&config_toml_path) {
|
||||
Ok(contents) => match toml::from_str::<toml::Value>(&contents) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
@@ -1585,21 +1557,16 @@ impl Session {
|
||||
return;
|
||||
}
|
||||
};
|
||||
reloaded_user_configs.push((reload_layer, user_config));
|
||||
reloaded_user_configs.push((config_toml_path, user_config));
|
||||
}
|
||||
|
||||
let next_config = {
|
||||
let state = self.state.lock().await;
|
||||
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
|
||||
for (reload_layer, user_config) in reloaded_user_configs {
|
||||
config.config_layer_stack = match reload_layer {
|
||||
UserConfigReloadLayer::Base(config_toml_path) => config
|
||||
.config_layer_stack
|
||||
.with_user_config(&config_toml_path, user_config),
|
||||
UserConfigReloadLayer::Override(config_toml_path) => config
|
||||
.config_layer_stack
|
||||
.with_user_override_config(&config_toml_path, user_config),
|
||||
};
|
||||
for (config_toml_path, user_config) in reloaded_user_configs {
|
||||
config.config_layer_stack = config
|
||||
.config_layer_stack
|
||||
.with_user_config(&config_toml_path, user_config);
|
||||
}
|
||||
config.tool_suggest =
|
||||
resolve_tool_suggest_config_from_layer_stack(&config.config_layer_stack);
|
||||
|
||||
@@ -1269,112 +1269,6 @@ async fn reload_user_config_layer_updates_base_and_selected_profile_layers() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_user_config_layer_refreshes_user_override_layers() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let codex_home = session.codex_home().await;
|
||||
std::fs::create_dir_all(&codex_home).expect("create codex home");
|
||||
let config_toml_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let override_path = codex_home.join(codex_config::CONFIG_OVERRIDE_TOML_FILE);
|
||||
std::fs::write(&config_toml_path, "model = \"base\"\n").expect("write base config");
|
||||
std::fs::write(&override_path, "model = \"override-old\"\n").expect("write override");
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load override config");
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.session_configuration.original_config_do_not_use = Arc::new(config);
|
||||
}
|
||||
std::fs::write(&override_path, "model = \"override-new\"\n").expect("update override");
|
||||
|
||||
session.reload_user_config_layer().await;
|
||||
|
||||
let config = session.get_config().await;
|
||||
assert_eq!(
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_user_config()
|
||||
.expect("merged user config")
|
||||
.get("model")
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("override-new")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_user_config_layer_discovers_new_user_override_layers() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let codex_home = session.codex_home().await;
|
||||
std::fs::create_dir_all(&codex_home).expect("create codex home");
|
||||
let config_toml_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let override_path = codex_home.join(codex_config::CONFIG_OVERRIDE_TOML_FILE);
|
||||
std::fs::write(&config_toml_path, "model = \"base\"\n").expect("write base config");
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load base config");
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.session_configuration.original_config_do_not_use = Arc::new(config);
|
||||
}
|
||||
std::fs::write(&override_path, "model = \"override-new\"\n").expect("write override");
|
||||
|
||||
session.reload_user_config_layer().await;
|
||||
|
||||
let config = session.get_config().await;
|
||||
assert_eq!(
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_user_config()
|
||||
.expect("merged user config")
|
||||
.get("model")
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("override-new")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_user_config_layer_keeps_selected_non_profile_user_above_new_override() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let codex_home = session.codex_home().await;
|
||||
std::fs::create_dir_all(&codex_home).expect("create codex home");
|
||||
let base_config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let override_path = codex_home.join(codex_config::CONFIG_OVERRIDE_TOML_FILE);
|
||||
let selected_config_path = codex_home.join("selected.toml");
|
||||
std::fs::write(&base_config_path, "model = \"base\"\n").expect("write base config");
|
||||
std::fs::write(&selected_config_path, "model = \"selected\"\n").expect("write selected config");
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.loader_overrides(LoaderOverrides {
|
||||
user_config_path: Some(selected_config_path.abs()),
|
||||
..LoaderOverrides::without_managed_config_for_tests()
|
||||
})
|
||||
.build()
|
||||
.await
|
||||
.expect("load selected config");
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.session_configuration.original_config_do_not_use = Arc::new(config);
|
||||
}
|
||||
std::fs::write(&override_path, "model = \"override\"\n").expect("write override");
|
||||
|
||||
session.reload_user_config_layer().await;
|
||||
|
||||
let config = session.get_config().await;
|
||||
assert_eq!(
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_user_config()
|
||||
.expect("merged user config")
|
||||
.get("model")
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("selected")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reload_user_config_layer_refreshes_hooks() -> anyhow::Result<()> {
|
||||
let session = make_session_with_config(|config| {
|
||||
|
||||
Reference in New Issue
Block a user