This commit is contained in:
celia-oai
2026-06-10 17:46:57 -07:00
parent 06566159f5
commit f0f08c6728
6 changed files with 131 additions and 41 deletions
+47 -3
View File
@@ -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<Sourced<FeatureRequirementsToml>>,
) -> std::io::Result<ConfigTomlLoadResult> {
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]
+34 -9
View File
@@ -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<ConfigToml> {
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<ConfigLoadOptions>,
) -> std::io::Result<ConfigToml> {
) -> std::io::Result<ConfigTomlLoadResult> {
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<AuthKeyringBackendKind> {
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(
+13 -11
View File
@@ -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,
@@ -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<McpAuthStatus> {
/*http_headers*/ None,
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
)
.await
}
+13 -11
View File
@@ -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,
+5 -4
View File
@@ -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;