From f0f08c6728ac4d829de2acd9c5a22768841463fe Mon Sep 17 00:00:00 2001 From: celia-oai Date: Tue, 9 Jun 2026 14:45:33 -0700 Subject: [PATCH] changes --- codex-rs/core/src/config/config_tests.rs | 50 +++++++++++++++++-- codex-rs/core/src/config/mod.rs | 43 ++++++++++++---- codex-rs/exec/src/lib.rs | 24 +++++---- .../tests/streamable_http_oauth_startup.rs | 22 ++++++-- codex-rs/tui/src/lib.rs | 24 +++++---- codex-rs/tui/src/session_archive_commands.rs | 9 ++-- 6 files changed, 131 insertions(+), 41 deletions(-) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index d393653249..c7000e6043 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -6,8 +6,13 @@ use crate::config::edit::apply_blocking; use assert_matches::assert_matches; use codex_config::CONFIG_TOML_FILE; use codex_config::ConfigLayerEntry; +use codex_config::ConfigLayerStack; +use codex_config::ConfigRequirements; +use codex_config::ConfigRequirementsToml; +use codex_config::FeatureRequirementsToml; use codex_config::ProfileV2Name; use codex_config::RequirementSource; +use codex_config::Sourced; use codex_config::config_toml::AgentRoleToml; use codex_config::config_toml::AgentsToml; use codex_config::config_toml::AutoReviewToml; @@ -5108,7 +5113,8 @@ async fn feature_table_overrides_legacy_flags() -> std::io::Result<()> { } #[test] -fn auth_keyring_backend_kind_from_config_toml_uses_secret_auth_storage_feature() { +fn resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature() +-> std::io::Result<()> { let config_toml = ConfigToml { features: Some(FeaturesToml::from(BTreeMap::from([( "secret_auth_storage".to_string(), @@ -5117,7 +5123,10 @@ fn auth_keyring_backend_kind_from_config_toml_uses_secret_auth_storage_feature() ..Default::default() }; assert_eq!( - auth_keyring_backend_kind_from_config_toml(&config_toml), + resolve_bootstrap_auth_keyring_backend_kind(&config_toml_load_result( + config_toml, + /*feature_requirements*/ None, + )?)?, AuthKeyringBackendKind::Secrets ); @@ -5129,9 +5138,44 @@ fn auth_keyring_backend_kind_from_config_toml_uses_secret_auth_storage_feature() ..Default::default() }; assert_eq!( - auth_keyring_backend_kind_from_config_toml(&config_toml), + resolve_bootstrap_auth_keyring_backend_kind(&config_toml_load_result( + config_toml.clone(), + /*feature_requirements*/ None, + )?)?, AuthKeyringBackendKind::Direct ); + + let requirements = Sourced::new( + FeatureRequirementsToml { + entries: BTreeMap::from([("secret_auth_storage".to_string(), true)]), + }, + RequirementSource::Unknown, + ); + assert_eq!( + resolve_bootstrap_auth_keyring_backend_kind(&config_toml_load_result( + config_toml, + Some(requirements), + )?)?, + AuthKeyringBackendKind::Secrets + ); + + Ok(()) +} + +fn config_toml_load_result( + config_toml: ConfigToml, + feature_requirements: Option>, +) -> std::io::Result { + let mut requirements = ConfigRequirements::default(); + requirements.feature_requirements = feature_requirements; + Ok(ConfigTomlLoadResult { + config_toml, + config_layer_stack: ConfigLayerStack::new( + Vec::new(), + requirements, + ConfigRequirementsToml::default(), + )?, + }) } #[tokio::test] diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 1c93ad5db0..fbca75dc71 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1624,20 +1624,31 @@ pub async fn load_config_as_toml_with_cli_and_loader_overrides( cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, ) -> std::io::Result { - load_config_as_toml_with_cli_and_load_options(codex_home, cwd, cli_overrides, loader_overrides) + load_config_toml_with_layer_stack(codex_home, cwd, cli_overrides, loader_overrides) .await + .map(|result| result.config_toml) +} + +/// Partially loaded config plus the layer stack used to derive it. +/// +/// This is intended for startup paths that must inspect raw config before a +/// full [`Config`] can be constructed, but still need access to managed +/// requirements loaded with the config layers. +pub struct ConfigTomlLoadResult { + pub config_toml: ConfigToml, + pub config_layer_stack: ConfigLayerStack, } /// DEPRECATED for most callers: prefer [Config::load_with_cli_overrides()] or /// [ConfigBuilder] because working with [ConfigToml] directly means /// [ConfigRequirements] have not been applied yet, which risks skipping /// required constraints. -pub async fn load_config_as_toml_with_cli_and_load_options( +pub async fn load_config_toml_with_layer_stack( codex_home: &Path, cwd: Option<&AbsolutePathBuf>, cli_overrides: Vec<(String, TomlValue)>, options: impl Into, -) -> std::io::Result { +) -> std::io::Result { let config_layer_stack = load_config_layers_state( LOCAL_FS.as_ref(), codex_home, @@ -1654,16 +1665,20 @@ pub async fn load_config_as_toml_with_cli_and_load_options( e })?; - Ok(cfg) + Ok(ConfigTomlLoadResult { + config_toml: cfg, + config_layer_stack, + }) } -/// Resolve the auth keyring backend from a partially loaded config. +/// Resolve the auth keyring backend from a partially loaded bootstrap config. /// /// This is intended for startup paths that must read auth before managed cloud /// requirements can be loaded and before a full [`Config`] exists. -pub fn auth_keyring_backend_kind_from_config_toml( - config_toml: &ConfigToml, -) -> AuthKeyringBackendKind { +pub fn resolve_bootstrap_auth_keyring_backend_kind( + bootstrap_config: &ConfigTomlLoadResult, +) -> std::io::Result { + let config_toml = &bootstrap_config.config_toml; let features = Features::from_sources( FeatureConfigSource { features: config_toml.features.as_ref(), @@ -1672,7 +1687,17 @@ pub fn auth_keyring_backend_kind_from_config_toml( FeatureConfigSource::default(), FeatureOverrides::default(), ); - auth_keyring_backend_kind_from_secret_auth_storage(features.enabled(Feature::SecretAuthStorage)) + let managed_features = ManagedFeatures::from_configured( + features, + bootstrap_config + .config_layer_stack + .requirements() + .feature_requirements + .clone(), + )?; + Ok(auth_keyring_backend_kind_from_secret_auth_storage( + managed_features.enabled(Feature::SecretAuthStorage), + )) } fn auth_keyring_backend_kind_from_secret_auth_storage( diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 4f08419ad3..fd60862181 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -62,9 +62,10 @@ use codex_core::check_execpolicy_for_warnings; use codex_core::config::Config; use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; -use codex_core::config::auth_keyring_backend_kind_from_config_toml; +use codex_core::config::ConfigTomlLoadResult; 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_with_layer_stack; +use codex_core::config::resolve_bootstrap_auth_keyring_backend_kind; use codex_core::config::resolve_oss_provider; use codex_core::config::resolve_profile_v2_config_path; use codex_core::find_thread_meta_by_name_str; @@ -332,7 +333,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 = load_bootstrap_config_or_exit( &codex_home, Some(&config_cwd), cli_kv_overrides.clone(), @@ -341,6 +342,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result CloudConfigBundleLoader::default(), ) .await; + let bootstrap_config_toml = &bootstrap_config.config_toml; let chatgpt_base_url = bootstrap_config_toml .chatgpt_base_url @@ -352,7 +354,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result bootstrap_config_toml .cli_auth_credentials_store .unwrap_or_default(), - auth_keyring_backend_kind_from_config_toml(&bootstrap_config_toml), + resolve_bootstrap_auth_keyring_backend_kind(&bootstrap_config)?, chatgpt_base_url, ) .await; @@ -361,12 +363,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result let run_cloud_config_bundle = cloud_config_bundle.clone(); let model_provider = if oss { - let config_toml_with_cloud_config; + let bootstrap_config_with_cloud_config; let config_toml_for_oss = if oss_provider.is_none() { // 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( + bootstrap_config_with_cloud_config = load_bootstrap_config_or_exit( &codex_home, Some(&config_cwd), cli_kv_overrides.clone(), @@ -375,9 +377,9 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result cloud_config_bundle.clone(), ) .await; - &config_toml_with_cloud_config + &bootstrap_config_with_cloud_config.config_toml } else { - &bootstrap_config_toml + bootstrap_config_toml }; let resolved = resolve_oss_provider(oss_provider.as_deref(), config_toml_for_oss); @@ -612,15 +614,15 @@ where } #[allow(clippy::print_stderr)] -async fn load_config_toml_or_exit( +async fn load_bootstrap_config_or_exit( codex_home: &Path, cwd: Option<&AbsolutePathBuf>, cli_kv_overrides: Vec<(String, codex_config::TomlValue)>, 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( +) -> ConfigTomlLoadResult { + match load_config_toml_with_layer_stack( codex_home, cwd, cli_kv_overrides, diff --git a/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs b/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs index 2b1d1dfc1c..866daa67a4 100644 --- a/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs +++ b/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs @@ -158,7 +158,12 @@ async fn persisted_credentials_auth_status_child() -> anyhow::Result<()> { token_response: WrappedOAuthTokenResponse(response), expires_at: Some(0), }; - save_oauth_tokens(SERVER_NAME, &tokens, OAuthCredentialsStoreMode::File)?; + save_oauth_tokens( + SERVER_NAME, + &tokens, + OAuthCredentialsStoreMode::File, + AuthKeyringBackendKind::default(), + )?; let status = auth_status(UNREFRESHABLE_SERVER_URL).await?; assert_eq!(status, McpAuthStatus::NotLoggedIn); @@ -179,7 +184,12 @@ async fn persisted_credentials_auth_status_child() -> anyhow::Result<()> { token_response: WrappedOAuthTokenResponse(response), expires_at: Some(now.saturating_add(/*rhs*/ 60_000)), }; - save_oauth_tokens(SERVER_NAME, &tokens, OAuthCredentialsStoreMode::File)?; + save_oauth_tokens( + SERVER_NAME, + &tokens, + OAuthCredentialsStoreMode::File, + AuthKeyringBackendKind::default(), + )?; let status = auth_status(UNEXPIRED_SERVER_URL).await?; assert_eq!(status, McpAuthStatus::OAuth); @@ -197,7 +207,12 @@ async fn persisted_credentials_auth_status_child() -> anyhow::Result<()> { token_response: WrappedOAuthTokenResponse(response), expires_at: Some(0), }; - save_oauth_tokens(SERVER_NAME, &tokens, OAuthCredentialsStoreMode::File)?; + save_oauth_tokens( + SERVER_NAME, + &tokens, + OAuthCredentialsStoreMode::File, + AuthKeyringBackendKind::default(), + )?; let status = auth_status(REFRESHABLE_SERVER_URL).await?; assert_eq!(status, McpAuthStatus::OAuth); @@ -212,6 +227,7 @@ async fn auth_status(server_url: &str) -> anyhow::Result { /*http_headers*/ None, /*env_http_headers*/ None, OAuthCredentialsStoreMode::File, + AuthKeyringBackendKind::default(), ) .await } diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 27d7c9a426..38d81ddfbe 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -7,8 +7,9 @@ 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::auth_keyring_backend_kind_from_config_toml; -use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options; +use crate::legacy_core::config::ConfigTomlLoadResult; +use crate::legacy_core::config::load_config_toml_with_layer_stack; +use crate::legacy_core::config::resolve_bootstrap_auth_keyring_backend_kind; use crate::legacy_core::config::resolve_oss_provider; use crate::legacy_core::config::resolve_profile_v2_config_path; use crate::legacy_core::format_exec_policy_error_with_source; @@ -1009,7 +1010,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 = load_bootstrap_config_or_exit( &codex_home, config_cwd.as_ref(), cli_kv_overrides.clone(), @@ -1018,6 +1019,7 @@ pub async fn run_main( CloudConfigBundleLoader::default(), ) .await; + let bootstrap_config_toml = &bootstrap_config.config_toml; let chatgpt_base_url = bootstrap_config_toml .chatgpt_base_url @@ -1029,7 +1031,7 @@ pub async fn run_main( bootstrap_config_toml .cli_auth_credentials_store .unwrap_or_default(), - auth_keyring_backend_kind_from_config_toml(&bootstrap_config_toml), + resolve_bootstrap_auth_keyring_backend_kind(&bootstrap_config)?, chatgpt_base_url, ) .await; @@ -1042,12 +1044,12 @@ pub async fn run_main( let mut manually_selected_oss_provider = None; let model_provider_override = if cli.oss { - let config_toml_with_cloud_config; + let bootstrap_config_with_cloud_config; let config_toml_for_oss = if cli.oss_provider.is_none() { // 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( + bootstrap_config_with_cloud_config = load_bootstrap_config_or_exit( &codex_home, config_cwd.as_ref(), cli_kv_overrides.clone(), @@ -1056,9 +1058,9 @@ pub async fn run_main( cloud_config_bundle.clone(), ) .await; - &config_toml_with_cloud_config + &bootstrap_config_with_cloud_config.config_toml } else { - &bootstrap_config_toml + bootstrap_config_toml }; let resolved = resolve_oss_provider(cli.oss_provider.as_deref(), config_toml_for_oss); @@ -1999,15 +2001,15 @@ async fn load_config_or_exit_with_fallback_cwd( } #[allow(clippy::print_stderr)] -async fn load_config_toml_or_exit( +async fn load_bootstrap_config_or_exit( codex_home: &Path, cwd: Option<&AbsolutePathBuf>, cli_kv_overrides: Vec<(String, codex_config::TomlValue)>, 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( +) -> ConfigTomlLoadResult { + match load_config_toml_with_layer_stack( codex_home, cwd, cli_kv_overrides, diff --git a/codex-rs/tui/src/session_archive_commands.rs b/codex-rs/tui/src/session_archive_commands.rs index 4da186259b..1ff4c5a3ab 100644 --- a/codex-rs/tui/src/session_archive_commands.rs +++ b/codex-rs/tui/src/session_archive_commands.rs @@ -9,8 +9,8 @@ use crate::Cli; use crate::app_server_session::AppServerSession; use crate::legacy_core::config::ConfigBuilder; use crate::legacy_core::config::ConfigOverrides; -use crate::legacy_core::config::auth_keyring_backend_kind_from_config_toml; -use crate::legacy_core::config::load_config_as_toml_with_cli_and_load_options; +use crate::legacy_core::config::load_config_toml_with_layer_stack; +use crate::legacy_core::config::resolve_bootstrap_auth_keyring_backend_kind; use crate::legacy_core::config::resolve_oss_provider; use crate::legacy_core::config::resolve_profile_v2_config_path; use codex_app_server_protocol::Thread as AppServerThread; @@ -246,7 +246,7 @@ async fn start_app_server_for_archive_command( loader_overrides.user_config_profile = Some(profile_v2.clone()); } - let config_toml = load_config_as_toml_with_cli_and_load_options( + let bootstrap_config = load_config_toml_with_layer_stack( codex_home.as_path(), config_cwd.as_ref(), cli_kv_overrides.clone(), @@ -258,6 +258,7 @@ async fn start_app_server_for_archive_command( ) .await .wrap_err("failed to load config.toml")?; + let config_toml = &bootstrap_config.config_toml; let chatgpt_base_url = config_toml .chatgpt_base_url .clone() @@ -266,7 +267,7 @@ async fn start_app_server_for_archive_command( codex_home.to_path_buf(), /*enable_codex_api_key_env*/ false, config_toml.cli_auth_credentials_store.unwrap_or_default(), - auth_keyring_backend_kind_from_config_toml(&config_toml), + resolve_bootstrap_auth_keyring_backend_kind(&bootstrap_config)?, chatgpt_base_url, ) .await;