Codex Handbook
app-server/src/config_manager.rs 371 lines
use codex_arg0::Arg0DispatchPaths;use codex_cloud_config::cloud_config_bundle_loader;use codex_config::CloudConfigBundleLoader;use codex_config::ConfigLayerStack;use codex_config::LoaderOverrides;use codex_config::ThreadConfigLoader;use codex_config::loader::load_config_layers_state;use codex_core::config::Config;use codex_core::config::ConfigOverrides;use codex_exec_server::LOCAL_FS;use codex_features::feature_for_key;use codex_login::AuthManager;use codex_login::default_client::set_default_client_residency_requirement;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_json_to_toml::json_to_toml;use std::collections::BTreeMap;use std::collections::BTreeSet;use std::collections::HashMap;use std::path::Path;use std::path::PathBuf;use std::sync::Arc;use std::sync::RwLock;use toml::Value as TomlValue;use tracing::instrument;use tracing::warn;/// Shared app-server entry point for loading effective Codex configuration.#[derive(Clone)]pub(crate) struct ConfigManager {    codex_home: PathBuf,    cli_overrides: Arc<RwLock<Vec<(String, TomlValue)>>>,    runtime_feature_enablement: Arc<RwLock<BTreeMap<String, bool>>>,    loader_overrides: LoaderOverrides,    strict_config: bool,    cloud_config_bundle: Arc<RwLock<CloudConfigBundleLoader>>,    arg0_paths: Arg0DispatchPaths,    thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,}impl ConfigManager {    pub(crate) fn new(        codex_home: PathBuf,        cli_overrides: Vec<(String, TomlValue)>,        loader_overrides: LoaderOverrides,        strict_config: bool,        cloud_config_bundle: CloudConfigBundleLoader,        arg0_paths: Arg0DispatchPaths,        thread_config_loader: Arc<dyn ThreadConfigLoader>,    ) -> Self {        Self {            codex_home,            cli_overrides: Arc::new(RwLock::new(cli_overrides)),            runtime_feature_enablement: Arc::new(RwLock::new(BTreeMap::new())),            loader_overrides,            strict_config,            cloud_config_bundle: Arc::new(RwLock::new(cloud_config_bundle)),            arg0_paths,            thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),        }    }    pub(crate) fn codex_home(&self) -> &Path {        self.codex_home.as_path()    }    pub(crate) fn user_config_path(&self) -> std::io::Result<AbsolutePathBuf> {        self.loader_overrides.user_config_path(self.codex_home())    }    pub(crate) fn current_cli_overrides(&self) -> Vec<(String, TomlValue)> {        self.cli_overrides            .read()            .map(|guard| guard.clone())            .unwrap_or_default()    }    pub(crate) fn current_cloud_config_bundle(&self) -> CloudConfigBundleLoader {        self.cloud_config_bundle            .read()            .map(|guard| guard.clone())            .unwrap_or_default()    }    pub(crate) fn extend_runtime_feature_enablement<I>(&self, enablement: I) -> Result<(), ()>    where        I: IntoIterator<Item = (String, bool)>,    {        let mut runtime_feature_enablement =            self.runtime_feature_enablement.write().map_err(|_| ())?;        runtime_feature_enablement.extend(enablement);        Ok(())    }    pub(crate) fn replace_cloud_config_bundle_loader(        &self,        auth_manager: Arc<AuthManager>,        chatgpt_base_url: String,    ) {        let loader =            cloud_config_bundle_loader(auth_manager, chatgpt_base_url, self.codex_home.clone());        if let Ok(mut guard) = self.cloud_config_bundle.write() {            *guard = loader;        } else {            warn!("failed to update cloud config bundle loader");        }    }    pub(crate) fn replace_thread_config_loader(        &self,        thread_config_loader: Arc<dyn ThreadConfigLoader>,    ) {        if let Ok(mut guard) = self.thread_config_loader.write() {            *guard = thread_config_loader;        } else {            warn!("failed to update thread config loader");        }    }    fn current_thread_config_loader(&self) -> Arc<dyn ThreadConfigLoader> {        self.thread_config_loader            .read()            .map(|guard| Arc::clone(&*guard))            .unwrap_or_else(|_| Arc::new(codex_config::NoopThreadConfigLoader))    }    pub(crate) async fn sync_default_client_residency_requirement(&self) {        match self.load_latest_config(/*fallback_cwd*/ None).await {            Ok(config) => {                set_default_client_residency_requirement(config.enforce_residency.value());            }            Err(err) => warn!(                error = %err,                "failed to sync default client residency requirement after auth refresh"            ),        }    }    pub(crate) async fn load_latest_config(        &self,        fallback_cwd: Option<PathBuf>,    ) -> std::io::Result<Config> {        self.load_with_cli_overrides(            &self.current_cli_overrides(),            /*request_overrides*/ None,            ConfigOverrides::default(),            fallback_cwd,        )        .await    }    pub(crate) async fn load_latest_config_for_thread(        &self,        thread_config: &Config,    ) -> std::io::Result<Config> {        let refreshed_config = self            .load_latest_config(Some(thread_config.cwd.to_path_buf()))            .await?;        let mut config = thread_config            .rebuild_preserving_session_layers(&refreshed_config)            .await?;        self.apply_runtime_feature_enablement(&mut config);        self.apply_arg0_paths(&mut config);        Ok(config)    }    pub(crate) async fn load_default_config(&self) -> std::io::Result<Config> {        let mut config = Config::load_default_with_cli_overrides_for_codex_home(            self.codex_home.clone(),            self.current_cli_overrides(),        )        .await?;        if self.loader_overrides.user_config_path.is_some()            || self.loader_overrides.user_config_profile.is_some()        {            let user_config_path = self.loader_overrides.user_config_path(self.codex_home())?;            config.config_layer_stack = config.config_layer_stack.with_user_config_profile(                &user_config_path,                self.loader_overrides.user_config_profile.as_ref(),                TomlValue::Table(toml::map::Map::new()),            );        }        self.apply_runtime_feature_enablement(&mut config);        self.apply_arg0_paths(&mut config);        Ok(config)    }    pub(crate) async fn load_with_overrides(        &self,        request_overrides: Option<HashMap<String, serde_json::Value>>,        typesafe_overrides: ConfigOverrides,    ) -> std::io::Result<Config> {        self.load_with_cli_overrides(            &self.current_cli_overrides(),            request_overrides,            typesafe_overrides,            /*fallback_cwd*/ None,        )        .await    }    pub(crate) async fn load_for_cwd(        &self,        request_overrides: Option<HashMap<String, serde_json::Value>>,        typesafe_overrides: ConfigOverrides,        cwd: Option<PathBuf>,    ) -> std::io::Result<Config> {        self.load_with_cli_overrides(            &self.current_cli_overrides(),            request_overrides,            typesafe_overrides,            cwd,        )        .await    }    #[instrument(level = "trace", skip_all)]    pub(crate) async fn load_with_cli_overrides(        &self,        cli_overrides: &[(String, TomlValue)],        request_overrides: Option<HashMap<String, serde_json::Value>>,        mut typesafe_overrides: ConfigOverrides,        fallback_cwd: Option<PathBuf>,    ) -> std::io::Result<Config> {        let mut request_overrides = request_overrides.unwrap_or_default();        if let Some(value) = request_overrides.remove("bypass_hook_trust") {            typesafe_overrides.bypass_hook_trust = Some(value.as_bool().ok_or_else(|| {                std::io::Error::new(                    std::io::ErrorKind::InvalidData,                    "`bypass_hook_trust` override must be a boolean",                )            })?);        }        let merged_cli_overrides = cli_overrides            .iter()            .cloned()            .chain(                request_overrides                    .into_iter()                    .map(|(key, value)| (key, json_to_toml(value))),            )            .collect::<Vec<_>>();        let mut config = codex_core::config::ConfigBuilder::default()            .codex_home(self.codex_home.clone())            .cli_overrides(merged_cli_overrides)            .loader_overrides(self.loader_overrides.clone())            .strict_config(self.strict_config)            .harness_overrides(typesafe_overrides)            .fallback_cwd(fallback_cwd)            .cloud_config_bundle(self.current_cloud_config_bundle())            .thread_config_loader(self.current_thread_config_loader())            .build()            .await?;        self.apply_runtime_feature_enablement(&mut config);        self.apply_arg0_paths(&mut config);        Ok(config)    }    pub(crate) async fn load_config_layers_for_cwd(        &self,        cwd: AbsolutePathBuf,    ) -> std::io::Result<ConfigLayerStack> {        self.load_config_layers(Some(cwd)).await    }    pub(crate) async fn load_config_layers(        &self,        cwd: Option<AbsolutePathBuf>,    ) -> std::io::Result<ConfigLayerStack> {        let thread_config_loader = self.current_thread_config_loader();        load_config_layers_state(            LOCAL_FS.as_ref(),            &self.codex_home,            cwd,            &self.current_cli_overrides(),            codex_config::ConfigLoadOptions {                loader_overrides: self.loader_overrides.clone(),                strict_config: self.strict_config,                cloud_config_bundle: self.current_cloud_config_bundle(),            },            thread_config_loader.as_ref(),        )        .await    }    fn apply_runtime_feature_enablement(&self, config: &mut Config) {        apply_runtime_feature_enablement(config, &self.current_runtime_feature_enablement());    }    fn current_runtime_feature_enablement(&self) -> BTreeMap<String, bool> {        self.runtime_feature_enablement            .read()            .map(|guard| guard.clone())            .unwrap_or_default()    }    fn apply_arg0_paths(&self, config: &mut Config) {        config.codex_self_exe = self.arg0_paths.codex_self_exe.clone();        config.codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone();        config.main_execve_wrapper_exe = self.arg0_paths.main_execve_wrapper_exe.clone();    }    #[cfg(test)]    pub(crate) fn new_for_tests(        codex_home: PathBuf,        cli_overrides: Vec<(String, TomlValue)>,        loader_overrides: LoaderOverrides,        cloud_config_bundle: CloudConfigBundleLoader,    ) -> Self {        Self::new(            codex_home,            cli_overrides,            loader_overrides,            /*strict_config*/ false,            cloud_config_bundle,            Arg0DispatchPaths::default(),            Arc::new(codex_config::NoopThreadConfigLoader),        )    }    #[cfg(test)]    pub(crate) fn without_managed_config_for_tests(codex_home: PathBuf) -> Self {        Self::new_for_tests(            codex_home,            Vec::new(),            LoaderOverrides::without_managed_config_for_tests(),            CloudConfigBundleLoader::default(),        )    }}pub(crate) fn protected_feature_keys(config_layer_stack: &ConfigLayerStack) -> BTreeSet<String> {    let mut protected_features = config_layer_stack        .effective_config()        .get("features")        .and_then(toml::Value::as_table)        .map(|features| features.keys().cloned().collect::<BTreeSet<_>>())        .unwrap_or_default();    if let Some(feature_requirements) = config_layer_stack        .requirements_toml()        .feature_requirements        .as_ref()    {        protected_features.extend(feature_requirements.entries.keys().cloned());    }    protected_features}pub(crate) fn apply_runtime_feature_enablement(    config: &mut Config,    runtime_feature_enablement: &BTreeMap<String, bool>,) {    let protected_features = protected_feature_keys(&config.config_layer_stack);    for (name, enabled) in runtime_feature_enablement {        if protected_features.contains(name) {            continue;        }        let Some(feature) = feature_for_key(name) else {            continue;        };        if let Err(err) = config.features.set_enabled(feature, *enabled) {            warn!(                feature = name,                error = %err,                "failed to apply runtime feature enablement"            );        }    }}