diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index c46c24fb3..6047ca472 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1088,6 +1088,7 @@ dependencies = [ "codex-arg0", "codex-backend-client", "codex-chatgpt", + "codex-cloud-requirements", "codex-common", "codex-core", "codex-execpolicy", @@ -1487,6 +1488,7 @@ dependencies = [ "assert_cmd", "clap", "codex-arg0", + "codex-cloud-requirements", "codex-common", "codex-core", "codex-protocol", diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 820edf59c..11c57ebbc 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -19,6 +19,7 @@ workspace = true anyhow = { workspace = true } async-trait = { workspace = true } codex-arg0 = { workspace = true } +codex-cloud-requirements = { workspace = true } codex-common = { workspace = true, features = ["cli"] } codex-core = { workspace = true } codex-backend-client = { workspace = true } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 78f670c04..aae369a19 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -152,6 +152,7 @@ use codex_core::config::ConfigService; use codex_core::config::edit::ConfigEdit; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::types::McpServerTransportConfig; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::default_client::get_codex_user_agent; use codex_core::error::CodexErr; use codex_core::exec::ExecParams; @@ -263,6 +264,7 @@ pub(crate) struct CodexMessageProcessor { codex_linux_sandbox_exe: Option, config: Arc, cli_overrides: Vec<(String, TomlValue)>, + cloud_requirements: CloudRequirementsLoader, conversation_listeners: HashMap>, listener_thread_ids_by_subscription: HashMap, active_login: Arc>>, @@ -281,6 +283,17 @@ pub(crate) enum ApiVersion { V2, } +pub(crate) struct CodexMessageProcessorArgs { + pub(crate) auth_manager: Arc, + pub(crate) thread_manager: Arc, + pub(crate) outgoing: Arc, + pub(crate) codex_linux_sandbox_exe: Option, + pub(crate) config: Arc, + pub(crate) cli_overrides: Vec<(String, TomlValue)>, + pub(crate) cloud_requirements: CloudRequirementsLoader, + pub(crate) feedback: CodexFeedback, +} + impl CodexMessageProcessor { async fn load_thread( &self, @@ -305,15 +318,17 @@ impl CodexMessageProcessor { Ok((thread_id, thread)) } - pub fn new( - auth_manager: Arc, - thread_manager: Arc, - outgoing: Arc, - codex_linux_sandbox_exe: Option, - config: Arc, - cli_overrides: Vec<(String, TomlValue)>, - feedback: CodexFeedback, - ) -> Self { + pub fn new(args: CodexMessageProcessorArgs) -> Self { + let CodexMessageProcessorArgs { + auth_manager, + thread_manager, + outgoing, + codex_linux_sandbox_exe, + config, + cli_overrides, + cloud_requirements, + feedback, + } = args; Self { auth_manager, thread_manager, @@ -321,6 +336,7 @@ impl CodexMessageProcessor { codex_linux_sandbox_exe, config, cli_overrides, + cloud_requirements, conversation_listeners: HashMap::new(), listener_thread_ids_by_subscription: HashMap::new(), active_login: Arc::new(Mutex::new(None)), @@ -333,7 +349,10 @@ impl CodexMessageProcessor { } async fn load_latest_config(&self) -> Result { - Config::load_with_cli_overrides(self.cli_overrides.clone()) + codex_core::config::ConfigBuilder::default() + .cli_overrides(self.cli_overrides.clone()) + .cloud_requirements(self.cloud_requirements.clone()) + .build() .await .map_err(|err| JSONRPCErrorError { code: INTERNAL_ERROR_CODE, @@ -1519,6 +1538,7 @@ impl CodexMessageProcessor { &self.cli_overrides, Some(request_overrides), typesafe_overrides, + &self.cloud_requirements, ) .await { @@ -1603,6 +1623,7 @@ impl CodexMessageProcessor { &self.cli_overrides, config, typesafe_overrides, + &self.cloud_requirements, ) .await { @@ -2350,6 +2371,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -2542,6 +2564,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -3336,6 +3359,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -3524,6 +3548,7 @@ impl CodexMessageProcessor { request_overrides, typesafe_overrides, history_cwd, + &self.cloud_requirements, ) .await { @@ -4794,6 +4819,7 @@ async fn derive_config_from_params( cli_overrides: &[(String, TomlValue)], request_overrides: Option>, typesafe_overrides: ConfigOverrides, + cloud_requirements: &CloudRequirementsLoader, ) -> std::io::Result { let merged_cli_overrides = cli_overrides .iter() @@ -4806,7 +4832,11 @@ async fn derive_config_from_params( ) .collect::>(); - Config::load_with_cli_overrides_and_harness_overrides(merged_cli_overrides, typesafe_overrides) + codex_core::config::ConfigBuilder::default() + .cli_overrides(merged_cli_overrides) + .harness_overrides(typesafe_overrides) + .cloud_requirements(cloud_requirements.clone()) + .build() .await } @@ -4815,6 +4845,7 @@ async fn derive_config_for_cwd( request_overrides: Option>, typesafe_overrides: ConfigOverrides, cwd: Option, + cloud_requirements: &CloudRequirementsLoader, ) -> std::io::Result { let merged_cli_overrides = cli_overrides .iter() @@ -4831,6 +4862,7 @@ async fn derive_config_for_cwd( .cli_overrides(merged_cli_overrides) .harness_overrides(typesafe_overrides) .fallback_cwd(cwd) + .cloud_requirements(cloud_requirements.clone()) .build() .await } diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 920766848..69f2c10df 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -12,6 +12,7 @@ use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::SandboxMode; use codex_core::config::ConfigService; use codex_core::config::ConfigServiceError; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigRequirementsToml; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement; @@ -29,9 +30,15 @@ impl ConfigApi { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, ) -> Self { Self { - service: ConfigService::new(codex_home, cli_overrides, loader_overrides), + service: ConfigService::new( + codex_home, + cli_overrides, + loader_overrides, + cloud_requirements, + ), } } diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 5b3d39704..52ea9325f 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -1,8 +1,11 @@ #![deny(clippy::print_stdout, clippy::print_stderr)] +use codex_cloud_requirements::cloud_requirements_loader; use codex_common::CliConfigOverrides; +use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; use std::io::ErrorKind; @@ -10,6 +13,7 @@ use std::io::Result as IoResult; use std::path::PathBuf; use crate::message_processor::MessageProcessor; +use crate::message_processor::MessageProcessorArgs; use crate::outgoing_message::OutgoingMessage; use crate::outgoing_message::OutgoingMessageSender; use codex_app_server_protocol::ConfigLayerSource; @@ -204,11 +208,32 @@ pub async fn run_main( format!("error parsing -c overrides: {e}"), ) })?; + let cloud_requirements = match ConfigBuilder::default() + .cli_overrides(cli_kv_overrides.clone()) + .loader_overrides(loader_overrides.clone()) + .build() + .await + { + Ok(config) => { + let auth_manager = AuthManager::shared( + config.codex_home.clone(), + false, + config.cli_auth_credentials_store_mode, + ); + cloud_requirements_loader(auth_manager, config.chatgpt_base_url) + } + Err(err) => { + warn!(error = %err, "Failed to preload config for cloud requirements"); + // TODO(gt): Make cloud requirements preload failures blocking once we can fail-closed. + CloudRequirementsLoader::default() + } + }; let loader_overrides_for_config_api = loader_overrides.clone(); let mut config_warnings = Vec::new(); let config = match ConfigBuilder::default() .cli_overrides(cli_kv_overrides.clone()) .loader_overrides(loader_overrides) + .cloud_requirements(cloud_requirements.clone()) .build() .await { @@ -290,15 +315,16 @@ pub async fn run_main( let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx); let cli_overrides: Vec<(String, TomlValue)> = cli_kv_overrides.clone(); let loader_overrides = loader_overrides_for_config_api; - let mut processor = MessageProcessor::new( - outgoing_message_sender, + let mut processor = MessageProcessor::new(MessageProcessorArgs { + outgoing: outgoing_message_sender, codex_linux_sandbox_exe, - std::sync::Arc::new(config), + config: std::sync::Arc::new(config), cli_overrides, loader_overrides, - feedback.clone(), + cloud_requirements: cloud_requirements.clone(), + feedback: feedback.clone(), config_warnings, - ); + }); let mut thread_created_rx = processor.thread_created_receiver(); async move { let mut listen_for_threads = true; diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index ced93c3bd..21b9a7a56 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::codex_message_processor::CodexMessageProcessor; +use crate::codex_message_processor::CodexMessageProcessorArgs; use crate::config_api::ConfigApi; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::outgoing_message::OutgoingMessageSender; @@ -31,6 +32,7 @@ use codex_core::auth::ExternalAuthRefreshReason; use codex_core::auth::ExternalAuthRefresher; use codex_core::auth::ExternalAuthTokens; use codex_core::config::Config; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::LoaderOverrides; use codex_core::default_client::SetOriginatorError; use codex_core::default_client::USER_AGENT_SUFFIX; @@ -106,18 +108,31 @@ pub(crate) struct MessageProcessor { config_warnings: Vec, } +pub(crate) struct MessageProcessorArgs { + pub(crate) outgoing: OutgoingMessageSender, + pub(crate) codex_linux_sandbox_exe: Option, + pub(crate) config: Arc, + pub(crate) cli_overrides: Vec<(String, TomlValue)>, + pub(crate) loader_overrides: LoaderOverrides, + pub(crate) cloud_requirements: CloudRequirementsLoader, + pub(crate) feedback: CodexFeedback, + pub(crate) config_warnings: Vec, +} + impl MessageProcessor { /// Create a new `MessageProcessor`, retaining a handle to the outgoing /// `Sender` so handlers can enqueue messages to be written to stdout. - pub(crate) fn new( - outgoing: OutgoingMessageSender, - codex_linux_sandbox_exe: Option, - config: Arc, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, - feedback: CodexFeedback, - config_warnings: Vec, - ) -> Self { + pub(crate) fn new(args: MessageProcessorArgs) -> Self { + let MessageProcessorArgs { + outgoing, + codex_linux_sandbox_exe, + config, + cli_overrides, + loader_overrides, + cloud_requirements, + feedback, + config_warnings, + } = args; let outgoing = Arc::new(outgoing); let auth_manager = AuthManager::shared( config.codex_home.clone(), @@ -133,16 +148,22 @@ impl MessageProcessor { auth_manager.clone(), SessionSource::VSCode, )); - let codex_message_processor = CodexMessageProcessor::new( + let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs { auth_manager, thread_manager, - outgoing.clone(), + outgoing: outgoing.clone(), codex_linux_sandbox_exe, - Arc::clone(&config), - cli_overrides.clone(), + config: Arc::clone(&config), + cli_overrides: cli_overrides.clone(), + cloud_requirements: cloud_requirements.clone(), feedback, + }); + let config_api = ConfigApi::new( + config.codex_home.clone(), + cli_overrides, + loader_overrides, + cloud_requirements, ); - let config_api = ConfigApi::new(config.codex_home.clone(), cli_overrides, loader_overrides); Self { outgoing, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 580f5de9f..761e5307b 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -366,7 +366,7 @@ pub struct ConfigBuilder { cli_overrides: Option>, harness_overrides: Option, loader_overrides: Option, - cloud_requirements: Option, + cloud_requirements: CloudRequirementsLoader, fallback_cwd: Option, } @@ -392,7 +392,7 @@ impl ConfigBuilder { } pub fn cloud_requirements(mut self, cloud_requirements: CloudRequirementsLoader) -> Self { - self.cloud_requirements = Some(cloud_requirements); + self.cloud_requirements = cloud_requirements; self } @@ -523,7 +523,7 @@ pub async fn load_config_as_toml_with_cli_overrides( Some(cwd.clone()), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -627,7 +627,7 @@ pub async fn load_global_mcp_servers( cwd, &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let merged_toml = config_layer_stack.effective_config(); @@ -2627,9 +2627,14 @@ profile = "project" }; let cwd = AbsolutePathBuf::try_from(codex_home.path())?; - let config_layer_stack = - load_config_layers_state(codex_home.path(), Some(cwd), &Vec::new(), overrides, None) - .await?; + let config_layer_stack = load_config_layers_state( + codex_home.path(), + Some(cwd), + &Vec::new(), + overrides, + CloudRequirementsLoader::default(), + ) + .await?; let cfg = deserialize_config_toml_with_base( config_layer_stack.effective_config(), codex_home.path(), @@ -2756,7 +2761,7 @@ profile = "project" Some(cwd), &[("model".to_string(), TomlValue::String("cli".to_string()))], overrides, - None, + CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/core/src/config/service.rs index 82583ee8a..72393f805 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/core/src/config/service.rs @@ -2,6 +2,7 @@ use super::CONFIG_TOML_FILE; use super::ConfigToml; use crate::config::edit::ConfigEdit; use crate::config::edit::ConfigEditsBuilder; +use crate::config_loader::CloudRequirementsLoader; use crate::config_loader::ConfigLayerEntry; use crate::config_loader::ConfigLayerStack; use crate::config_loader::ConfigLayerStackOrdering; @@ -109,6 +110,7 @@ pub struct ConfigService { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, } impl ConfigService { @@ -116,11 +118,13 @@ impl ConfigService { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, ) -> Self { Self { codex_home, cli_overrides, loader_overrides, + cloud_requirements, } } @@ -129,6 +133,7 @@ impl ConfigService { codex_home, cli_overrides: Vec::new(), loader_overrides: LoaderOverrides::default(), + cloud_requirements: CloudRequirementsLoader::default(), } } @@ -146,6 +151,7 @@ impl ConfigService { .cli_overrides(self.cli_overrides.clone()) .loader_overrides(self.loader_overrides.clone()) .fallback_cwd(Some(cwd.to_path_buf())) + .cloud_requirements(self.cloud_requirements.clone()) .build() .await .map_err(|err| { @@ -376,7 +382,7 @@ impl ConfigService { cwd, &self.cli_overrides, self.loader_overrides.clone(), - None, + self.cloud_requirements.clone(), ) .await } @@ -814,6 +820,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let response = service @@ -896,6 +903,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let result = service @@ -1000,6 +1008,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let error = service @@ -1048,6 +1057,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let response = service @@ -1095,6 +1105,7 @@ remote_compaction = true managed_preferences_base64: None, macos_managed_config_requirements_base64: None, }, + CloudRequirementsLoader::default(), ); let result = service diff --git a/codex-rs/core/src/config_loader/cloud_requirements.rs b/codex-rs/core/src/config_loader/cloud_requirements.rs index 7c5e3a294..3487cc326 100644 --- a/codex-rs/core/src/config_loader/cloud_requirements.rs +++ b/codex-rs/core/src/config_loader/cloud_requirements.rs @@ -32,6 +32,12 @@ impl fmt::Debug for CloudRequirementsLoader { } } +impl Default for CloudRequirementsLoader { + fn default() -> Self { + Self::new(async { None }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 838db0d5f..ce220a51e 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -101,7 +101,7 @@ pub async fn load_config_layers_state( cwd: Option, cli_overrides: &[(String, TomlValue)], overrides: LoaderOverrides, - cloud_requirements: Option, // TODO(gt): Once exec and app-server are wired up, we can remove the option. + cloud_requirements: CloudRequirementsLoader, ) -> io::Result { let mut config_requirements_toml = ConfigRequirementsWithSources::default(); @@ -114,9 +114,7 @@ pub async fn load_config_layers_state( ) .await?; - if let Some(loader) = cloud_requirements - && let Some(requirements) = loader.get().await - { + if let Some(requirements) = cloud_requirements.get().await { config_requirements_toml .merge_unset_fields(RequirementSource::CloudRequirements, requirements); } diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index fd4898c2c..d2b0cf25f 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -69,7 +69,7 @@ async fn returns_config_error_for_invalid_user_config_toml() { Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await .expect_err("expected error"); @@ -99,7 +99,7 @@ async fn returns_config_error_for_invalid_managed_config_toml() { Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect_err("expected error"); @@ -188,7 +188,7 @@ extra = true Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load config"); @@ -225,7 +225,7 @@ async fn returns_empty_when_all_layers_missing() { Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load layers"); @@ -323,7 +323,7 @@ flag = false Some(cwd), &[] as &[(String, TomlValue)], overrides, - None, + CloudRequirementsLoader::default(), ) .await .expect("load config"); @@ -363,7 +363,7 @@ allowed_sandbox_modes = ["read-only"] ), ), }, - None, + CloudRequirementsLoader::default(), ) .await?; @@ -424,7 +424,7 @@ allowed_approval_policies = ["never"] ), ), }, - None, + CloudRequirementsLoader::default(), ) .await?; @@ -546,7 +546,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()> Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - Some(cloud_requirements), + cloud_requirements, ) .await?; @@ -599,7 +599,7 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> { Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -731,7 +731,7 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -769,7 +769,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -819,7 +819,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -888,7 +888,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< Some(cwd.clone()), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers_untrusted: Vec<_> = layers_untrusted @@ -926,7 +926,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers_unknown: Vec<_> = layers_unknown @@ -987,7 +987,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io:: Some(cwd.clone()), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; let project_layers: Vec<_> = layers @@ -1043,7 +1043,7 @@ async fn cli_overrides_with_relative_paths_do_not_break_trust_check() -> std::io Some(cwd), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; @@ -1085,7 +1085,7 @@ async fn project_root_markers_supports_alternate_markers() -> std::io::Result<() Some(cwd), &[] as &[(String, TomlValue)], LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/core/src/skills/manager.rs b/codex-rs/core/src/skills/manager.rs index f71ecfffa..3b100991f 100644 --- a/codex-rs/core/src/skills/manager.rs +++ b/codex-rs/core/src/skills/manager.rs @@ -10,6 +10,7 @@ use tracing::warn; use crate::config::Config; use crate::config::types::SkillsConfig; +use crate::config_loader::CloudRequirementsLoader; use crate::config_loader::LoaderOverrides; use crate::config_loader::load_config_layers_state; use crate::skills::SkillLoadOutcome; @@ -88,7 +89,7 @@ impl SkillsManager { Some(cwd_abs), &cli_overrides, LoaderOverrides::default(), - None, + CloudRequirementsLoader::default(), ) .await { diff --git a/codex-rs/exec-server/src/posix.rs b/codex-rs/exec-server/src/posix.rs index 3ec623330..7f9ce569c 100644 --- a/codex-rs/exec-server/src/posix.rs +++ b/codex-rs/exec-server/src/posix.rs @@ -241,7 +241,7 @@ async fn load_exec_policy() -> anyhow::Result { cwd, &cli_overrides, overrides, - None, + codex_core::config_loader::CloudRequirementsLoader::default(), ) .await?; diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index d4a64772e..862d8d169 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -19,6 +19,7 @@ workspace = true anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } codex-arg0 = { workspace = true } +codex-cloud-requirements = { workspace = true } codex-common = { workspace = true, features = [ "cli", "elapsed", diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index f7176c13b..396cde190 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -13,6 +13,7 @@ pub mod exec_events; pub use cli::Cli; pub use cli::Command; pub use cli::ReviewArgs; +use codex_cloud_requirements::cloud_requirements_loader; use codex_common::oss::ensure_oss_provider_ready; use codex_common::oss::get_default_model_for_oss_provider; use codex_common::oss::ollama_chat_deprecation_notice; @@ -24,6 +25,7 @@ use codex_core::OLLAMA_OSS_PROVIDER_ID; use codex_core::ThreadManager; use codex_core::auth::enforce_login_restrictions; 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_overrides; @@ -159,41 +161,52 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any // we load config.toml here to determine project state. #[allow(clippy::print_stderr)] - let config_toml = { - let codex_home = match find_codex_home() { - Ok(codex_home) => codex_home, - Err(err) => { - eprintln!("Error finding codex home: {err}"); - std::process::exit(1); - } - }; - - match load_config_as_toml_with_cli_overrides( - &codex_home, - &config_cwd, - cli_kv_overrides.clone(), - ) - .await - { - Ok(config_toml) => config_toml, - Err(err) => { - let config_error = err - .get_ref() - .and_then(|err| err.downcast_ref::()) - .map(ConfigLoadError::config_error); - if let Some(config_error) = config_error { - eprintln!( - "Error loading config.toml:\n{}", - format_config_error_with_source(config_error) - ); - } else { - eprintln!("Error loading config.toml: {err}"); - } - std::process::exit(1); - } + let codex_home = match find_codex_home() { + Ok(codex_home) => codex_home, + Err(err) => { + eprintln!("Error finding codex home: {err}"); + std::process::exit(1); } }; + #[allow(clippy::print_stderr)] + let config_toml = match load_config_as_toml_with_cli_overrides( + &codex_home, + &config_cwd, + cli_kv_overrides.clone(), + ) + .await + { + Ok(config_toml) => config_toml, + Err(err) => { + let config_error = err + .get_ref() + .and_then(|err| err.downcast_ref::()) + .map(ConfigLoadError::config_error); + if let Some(config_error) = config_error { + eprintln!( + "Error loading config.toml:\n{}", + format_config_error_with_source(config_error) + ); + } else { + eprintln!("Error loading config.toml: {err}"); + } + std::process::exit(1); + } + }; + + let cloud_auth_manager = AuthManager::shared( + codex_home.clone(), + false, + config_toml.cli_auth_credentials_store.unwrap_or_default(), + ); + let chatgpt_base_url = config_toml + .chatgpt_base_url + .clone() + .unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()); + // TODO(gt): Make cloud requirements failures blocking once we can fail-closed. + let cloud_requirements = cloud_requirements_loader(cloud_auth_manager, chatgpt_base_url); + let model_provider = if oss { let resolved = resolve_oss_provider( oss_provider.as_deref(), @@ -246,8 +259,12 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any additional_writable_roots: add_dir, }; - let config = - Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?; + let config = ConfigBuilder::default() + .cli_overrides(cli_kv_overrides) + .harness_overrides(overrides) + .cloud_requirements(cloud_requirements) + .build() + .await?; if let Err(err) = enforce_login_restrictions(&config) { eprintln!("{err}"); diff --git a/codex-rs/network-proxy/src/state.rs b/codex-rs/network-proxy/src/state.rs index b0c8fdfbc..20c328c75 100644 --- a/codex-rs/network-proxy/src/state.rs +++ b/codex-rs/network-proxy/src/state.rs @@ -11,6 +11,7 @@ use codex_core::config::CONFIG_TOML_FILE; use codex_core::config::Constrained; use codex_core::config::ConstraintError; use codex_core::config::find_codex_home; +use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStack; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; @@ -31,10 +32,15 @@ pub(crate) async fn build_config_state() -> Result { let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?; let cli_overrides = Vec::new(); let overrides = LoaderOverrides::default(); - let config_layer_stack = - load_config_layers_state(&codex_home, None, &cli_overrides, overrides, None) - .await - .context("failed to load Codex config")?; + let config_layer_stack = load_config_layers_state( + &codex_home, + None, + &cli_overrides, + overrides, + CloudRequirementsLoader::default(), + ) + .await + .context("failed to load Codex config")?; let cfg_path = codex_home.join(CONFIG_TOML_FILE);