diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 4de35cc5ad..74c6c93e5b 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1639,6 +1639,26 @@ pub async fn load_config_as_toml_with_cli_and_load_options( cli_overrides: Vec<(String, TomlValue)>, options: impl Into, ) -> std::io::Result { + let (config_toml, _) = load_config_toml_and_requirements_with_cli_and_load_options( + codex_home, + cwd, + cli_overrides, + options, + ) + .await?; + Ok(config_toml) +} + +/// Loads merged config TOML and its normalized managed requirements from one layer stack. +/// +/// Bootstrap callers use this before cloud config is available so early +/// decisions can still enforce local managed requirements. +pub async fn load_config_toml_and_requirements_with_cli_and_load_options( + codex_home: &Path, + cwd: Option<&AbsolutePathBuf>, + cli_overrides: Vec<(String, TomlValue)>, + options: impl Into, +) -> std::io::Result<(ConfigToml, ConfigRequirements)> { let config_layer_stack = load_config_layers_state( LOCAL_FS.as_ref(), codex_home, @@ -1649,13 +1669,14 @@ pub async fn load_config_as_toml_with_cli_and_load_options( ) .await?; + let requirements = config_layer_stack.requirements().clone(); let merged_toml = config_layer_stack.effective_config(); let cfg = deserialize_config_toml_with_base(merged_toml, codex_home).map_err(|e| { tracing::error!("Failed to deserialize overridden config: {e}"); e })?; - Ok(cfg) + Ok((cfg, requirements)) } pub fn deserialize_config_toml_with_base( diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 8c53251eac..b3dd5f3277 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -63,7 +63,7 @@ use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; -use codex_core::config::load_config_as_toml_with_cli_and_load_options; +use codex_core::config::load_config_toml_and_requirements_with_cli_and_load_options; use codex_core::config::resolve_bootstrap_system_proxy_config; use codex_core::config::resolve_oss_provider; use codex_core::config::resolve_profile_v2_config_path; @@ -334,7 +334,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result ..Default::default() }; - let bootstrap_config_toml = load_config_toml_or_exit( + let (bootstrap_config_toml, bootstrap_requirements) = load_config_toml_or_exit( &codex_home, Some(&config_cwd), cli_kv_overrides.clone(), @@ -348,7 +348,10 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result .chatgpt_base_url .clone() .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); - let system_proxy_config = resolve_bootstrap_system_proxy_config(&bootstrap_config_toml); + let system_proxy_config = resolve_bootstrap_system_proxy_config( + &bootstrap_config_toml, + bootstrap_requirements.feature_requirements.as_ref(), + )?; let auth_route_config = bootstrap_auth_route_config_from_system_proxy_config(system_proxy_config.as_ref()); let cloud_config_bundle = cloud_config_bundle_loader_for_storage_with_auth_route_config( @@ -371,7 +374,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result // The first load intentionally skips cloud config so we can read // auth/base-url settings needed to fetch the bundle. If OSS mode // needs a default provider from config, reload with the bundle. - config_toml_with_cloud_config = load_config_toml_or_exit( + let (reloaded_config_toml, _) = load_config_toml_or_exit( &codex_home, Some(&config_cwd), cli_kv_overrides.clone(), @@ -380,6 +383,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result cloud_config_bundle.clone(), ) .await; + config_toml_with_cloud_config = reloaded_config_toml; &config_toml_with_cloud_config } else { &bootstrap_config_toml @@ -630,8 +634,11 @@ async fn load_config_toml_or_exit( loader_overrides: LoaderOverrides, strict_config: bool, cloud_config_bundle: CloudConfigBundleLoader, -) -> codex_config::config_toml::ConfigToml { - match load_config_as_toml_with_cli_and_load_options( +) -> ( + codex_config::config_toml::ConfigToml, + codex_config::ConfigRequirements, +) { + match load_config_toml_and_requirements_with_cli_and_load_options( codex_home, cwd, cli_kv_overrides, @@ -643,7 +650,7 @@ async fn load_config_toml_or_exit( ) .await { - Ok(config_toml) => config_toml, + Ok(config_toml_and_requirements) => config_toml_and_requirements, Err(err) => { let config_error = err .get_ref() diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index e630147235..9f5748fb69 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -7,7 +7,7 @@ use crate::legacy_core::check_execpolicy_for_warnings; use crate::legacy_core::config::Config; use crate::legacy_core::config::ConfigBuilder; use crate::legacy_core::config::ConfigOverrides; -use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options; +use crate::legacy_core::config::load_config_toml_and_requirements_with_cli_and_load_options; use crate::legacy_core::config::resolve_bootstrap_system_proxy_config; use crate::legacy_core::config::resolve_oss_provider; use crate::legacy_core::config::resolve_profile_v2_config_path; @@ -1008,7 +1008,7 @@ pub async fn run_main( loader_overrides.user_config_profile = Some(profile_v2.clone()); } - let bootstrap_config_toml = load_config_toml_or_exit( + let (bootstrap_config_toml, bootstrap_requirements) = load_config_toml_or_exit( &codex_home, config_cwd.as_ref(), cli_kv_overrides.clone(), @@ -1022,7 +1022,10 @@ pub async fn run_main( .chatgpt_base_url .clone() .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); - let system_proxy_config = resolve_bootstrap_system_proxy_config(&bootstrap_config_toml); + let system_proxy_config = resolve_bootstrap_system_proxy_config( + &bootstrap_config_toml, + bootstrap_requirements.feature_requirements.as_ref(), + )?; let auth_route_config = bootstrap_auth_route_config_from_system_proxy_config(system_proxy_config.as_ref()); let cloud_config_bundle = cloud_config_bundle_loader_for_storage_with_auth_route_config( @@ -1049,7 +1052,7 @@ pub async fn run_main( // The first load intentionally skips cloud config so we can read // auth/base-url settings needed to fetch the bundle. If OSS mode // needs a default provider from config, reload with the bundle. - config_toml_with_cloud_config = load_config_toml_or_exit( + let (reloaded_config_toml, _) = load_config_toml_or_exit( &codex_home, config_cwd.as_ref(), cli_kv_overrides.clone(), @@ -1058,6 +1061,7 @@ pub async fn run_main( cloud_config_bundle.clone(), ) .await; + config_toml_with_cloud_config = reloaded_config_toml; &config_toml_with_cloud_config } else { &bootstrap_config_toml @@ -2030,8 +2034,11 @@ async fn load_config_toml_or_exit( loader_overrides: LoaderOverrides, strict_config: bool, cloud_config_bundle: CloudConfigBundleLoader, -) -> codex_config::config_toml::ConfigToml { - match load_config_as_toml_with_cli_and_load_options( +) -> ( + codex_config::config_toml::ConfigToml, + codex_config::ConfigRequirements, +) { + match load_config_toml_and_requirements_with_cli_and_load_options( codex_home, cwd, cli_kv_overrides, @@ -2043,7 +2050,7 @@ async fn load_config_toml_or_exit( ) .await { - Ok(config_toml) => config_toml, + Ok(config_toml_and_requirements) => config_toml_and_requirements, Err(err) => { let config_error = err .get_ref()