diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 6b2ea8d0a..731342e3c 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -52,6 +52,7 @@ impl MessageProcessor { config.cli_auth_credentials_store_mode, ); let thread_manager = Arc::new(ThreadManager::new( + config.codex_home.clone(), auth_manager.clone(), SessionSource::VSCode, )); diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index 7d7e7b0bc..71a542912 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -32,11 +32,7 @@ use crate::token_data::parse_id_token; use crate::util::try_parse_error_message; use codex_client::CodexHttpClient; use codex_protocol::account::PlanType as AccountPlanType; -#[cfg(any(test, feature = "test-support"))] -use once_cell::sync::Lazy; use serde_json::Value; -#[cfg(any(test, feature = "test-support"))] -use tempfile::TempDir; use thiserror::Error; #[derive(Debug, Clone)] @@ -66,9 +62,6 @@ const REFRESH_TOKEN_UNKNOWN_MESSAGE: &str = const REFRESH_TOKEN_URL: &str = "https://auth.openai.com/oauth/token"; pub const REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR: &str = "CODEX_REFRESH_TOKEN_URL_OVERRIDE"; -#[cfg(any(test, feature = "test-support"))] -static TEST_AUTH_TEMP_DIRS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); - #[derive(Debug, Error)] pub enum RefreshTokenError { #[error("{0}")] @@ -672,18 +665,12 @@ impl AuthManager { } #[cfg(any(test, feature = "test-support"))] - #[expect(clippy::expect_used)] /// Create an AuthManager with a specific CodexAuth, for testing only. pub fn from_auth_for_testing(auth: CodexAuth) -> Arc { let cached = CachedAuth { auth: Some(auth) }; - let temp_dir = tempfile::tempdir().expect("temp codex home"); - let codex_home = temp_dir.path().to_path_buf(); - TEST_AUTH_TEMP_DIRS - .lock() - .expect("lock test codex homes") - .push(temp_dir); + Arc::new(Self { - codex_home, + codex_home: PathBuf::from("non-existent"), inner: RwLock::new(cached), enable_codex_api_key_env: false, auth_credentials_store_mode: AuthCredentialsStoreMode::File, @@ -707,10 +694,6 @@ impl AuthManager { self.inner.read().ok().and_then(|c| c.auth.clone()) } - pub fn codex_home(&self) -> &Path { - &self.codex_home - } - /// Force a reload of the auth information from auth.json. Returns /// whether the auth value changed. pub fn reload(&self) -> bool { diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ac879d3e6..98b34d51a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -3479,7 +3479,10 @@ mod tests { let conversation_id = ThreadId::default(); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); + let models_manager = Arc::new(ModelsManager::new( + config.codex_home.clone(), + auth_manager.clone(), + )); let agent_control = AgentControl::default(); let exec_policy = ExecPolicyManager::default(); let agent_status = Arc::new(RwLock::new(AgentStatus::PendingInit)); @@ -3570,7 +3573,10 @@ mod tests { let conversation_id = ThreadId::default(); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); + let models_manager = Arc::new(ModelsManager::new( + config.codex_home.clone(), + auth_manager.clone(), + )); let agent_control = AgentControl::default(); let exec_policy = ExecPolicyManager::default(); let agent_status = Arc::new(RwLock::new(AgentStatus::PendingInit)); diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index c9185f0ff..87ff1b76d 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -47,8 +47,7 @@ pub struct ModelsManager { impl ModelsManager { /// Construct a manager scoped to the provided `AuthManager`. - pub fn new(auth_manager: Arc) -> Self { - let codex_home = auth_manager.codex_home().to_path_buf(); + pub fn new(codex_home: PathBuf, auth_manager: Arc) -> Self { Self { local_models: builtin_model_presets(auth_manager.get_auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), @@ -62,8 +61,11 @@ impl ModelsManager { #[cfg(any(test, feature = "test-support"))] /// Construct a manager scoped to the provided `AuthManager` with a specific provider. Used for integration tests. - pub fn with_provider(auth_manager: Arc, provider: ModelProviderInfo) -> Self { - let codex_home = auth_manager.codex_home().to_path_buf(); + pub fn with_provider( + codex_home: PathBuf, + auth_manager: Arc, + provider: ModelProviderInfo, + ) -> Self { Self { local_models: builtin_model_presets(auth_manager.get_auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), @@ -422,7 +424,8 @@ mod tests { let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); - let manager = ModelsManager::with_provider(auth_manager, provider); + let manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); manager .refresh_available_models_with_cache(&config) @@ -481,7 +484,8 @@ mod tests { AuthCredentialsStoreMode::File, )); let provider = provider_for(server.uri()); - let manager = ModelsManager::with_provider(auth_manager, provider); + let manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); manager .refresh_available_models_with_cache(&config) @@ -535,7 +539,8 @@ mod tests { AuthCredentialsStoreMode::File, )); let provider = provider_for(server.uri()); - let manager = ModelsManager::with_provider(auth_manager, provider); + let manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); manager .refresh_available_models_with_cache(&config) @@ -605,7 +610,8 @@ mod tests { let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); - let mut manager = ModelsManager::with_provider(auth_manager, provider); + let mut manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); manager.cache_ttl = Duration::ZERO; manager @@ -653,10 +659,12 @@ mod tests { #[test] fn build_available_models_picks_default_after_hiding_hidden_models() { + let codex_home = tempdir().expect("temp dir"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let provider = provider_for("http://example.test".to_string()); - let mut manager = ModelsManager::with_provider(auth_manager, provider); + let mut manager = + ModelsManager::with_provider(codex_home.path().to_path_buf(), auth_manager, provider); manager.local_models = Vec::new(); let hidden_model = remote_model_with_visibility("hidden", "Hidden", 0, "hide"); diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 08f432a59..742de1c53 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -59,14 +59,19 @@ pub(crate) struct ThreadManagerState { } impl ThreadManager { - pub fn new(auth_manager: Arc, session_source: SessionSource) -> Self { + pub fn new( + codex_home: PathBuf, + auth_manager: Arc, + session_source: SessionSource, + ) -> Self { Self { state: Arc::new(ThreadManagerState { threads: Arc::new(RwLock::new(HashMap::new())), - models_manager: Arc::new(ModelsManager::new(auth_manager.clone())), - skills_manager: Arc::new(SkillsManager::new( - auth_manager.codex_home().to_path_buf(), + models_manager: Arc::new(ModelsManager::new( + codex_home.clone(), + auth_manager.clone(), )), + skills_manager: Arc::new(SkillsManager::new(codex_home)), auth_manager, session_source, }), @@ -94,17 +99,16 @@ impl ThreadManager { provider: ModelProviderInfo, codex_home: PathBuf, ) -> Self { - let auth_manager = AuthManager::from_auth_for_testing_with_home(auth, codex_home); + let auth_manager = AuthManager::from_auth_for_testing(auth); Self { state: Arc::new(ThreadManagerState { threads: Arc::new(RwLock::new(HashMap::new())), models_manager: Arc::new(ModelsManager::with_provider( + codex_home.clone(), auth_manager.clone(), provider, )), - skills_manager: Arc::new(SkillsManager::new( - auth_manager.codex_home().to_path_buf(), - )), + skills_manager: Arc::new(SkillsManager::new(codex_home)), auth_manager, session_source: SessionSource::Exec, }), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 7e0a6e3fa..0e5fe0d9b 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -574,7 +574,11 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { Ok(None) => panic!("No CodexAuth found in codex_home"), Err(e) => panic!("Failed to load CodexAuth: {e}"), }; - let thread_manager = ThreadManager::new(auth_manager, SessionSource::Exec); + let thread_manager = ThreadManager::new( + codex_home.path().to_path_buf(), + auth_manager, + SessionSource::Exec, + ); let NewThread { thread: codex, .. } = thread_manager .start_thread(config) .await diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index d77ec7d38..507af97f2 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -320,6 +320,7 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> { ..built_in_model_providers()["openai"].clone() }; let manager = ModelsManager::with_provider( + codex_home.path().to_path_buf(), codex_core::auth::AuthManager::from_auth_for_testing(auth), provider, ); @@ -377,6 +378,7 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { ..built_in_model_providers()["openai"].clone() }; let manager = ModelsManager::with_provider( + codex_home.path().to_path_buf(), codex_core::auth::AuthManager::from_auth_for_testing(auth), provider, ); diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index b82e371d2..89be1ac6c 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -286,7 +286,11 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any true, config.cli_auth_credentials_store_mode, ); - let thread_manager = ThreadManager::new(auth_manager.clone(), SessionSource::Exec); + let thread_manager = ThreadManager::new( + config.codex_home.clone(), + auth_manager.clone(), + SessionSource::Exec, + ); let default_model = thread_manager .get_models_manager() .get_model(&config.model, &config) diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 955dc0603..dcf5411a0 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -58,7 +58,11 @@ impl MessageProcessor { false, config.cli_auth_credentials_store_mode, ); - let thread_manager = Arc::new(ThreadManager::new(auth_manager, SessionSource::Mcp)); + let thread_manager = Arc::new(ThreadManager::new( + config.codex_home.clone(), + auth_manager, + SessionSource::Mcp, + )); Self { outgoing, initialized: false, diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index cfb50a996..32223f18e 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -344,7 +344,11 @@ impl App { let (app_event_tx, mut app_event_rx) = unbounded_channel(); let app_event_tx = AppEventSender::new(app_event_tx); - let thread_manager = Arc::new(ThreadManager::new(auth_manager.clone(), SessionSource::Cli)); + let thread_manager = Arc::new(ThreadManager::new( + config.codex_home.clone(), + auth_manager.clone(), + SessionSource::Cli, + )); let mut model = thread_manager .get_models_manager() .get_model(&config.model, &config) diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 03f11c050..cf53b7fac 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -366,6 +366,7 @@ async fn make_chatwidget_manual( skills: None, }); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test")); + let codex_home = cfg.codex_home.clone(); let widget = ChatWidget { app_event_tx, codex_op_tx: op_tx, @@ -374,7 +375,7 @@ async fn make_chatwidget_manual( config: cfg, model: resolved_model.clone(), auth_manager: auth_manager.clone(), - models_manager: Arc::new(ModelsManager::new(auth_manager)), + models_manager: Arc::new(ModelsManager::new(codex_home, auth_manager)), session_header: SessionHeader::new(resolved_model), initial_user_message: None, token_info: None, @@ -415,7 +416,10 @@ async fn make_chatwidget_manual( fn set_chatgpt_auth(chat: &mut ChatWidget) { chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); - chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone())); + chat.models_manager = Arc::new(ModelsManager::new( + chat.config.codex_home.clone(), + chat.auth_manager.clone(), + )); } pub(crate) async fn make_chatwidget_manual_with_sender() -> ( diff --git a/codex-rs/tui2/src/app.rs b/codex-rs/tui2/src/app.rs index 5bea13a97..ead4135a4 100644 --- a/codex-rs/tui2/src/app.rs +++ b/codex-rs/tui2/src/app.rs @@ -407,7 +407,11 @@ impl App { let (app_event_tx, mut app_event_rx) = unbounded_channel(); let app_event_tx = AppEventSender::new(app_event_tx); - let thread_manager = Arc::new(ThreadManager::new(auth_manager.clone(), SessionSource::Cli)); + let thread_manager = Arc::new(ThreadManager::new( + config.codex_home.clone(), + auth_manager.clone(), + SessionSource::Cli, + )); let mut model = thread_manager .get_models_manager() .get_model(&config.model, &config) diff --git a/codex-rs/tui2/src/chatwidget/tests.rs b/codex-rs/tui2/src/chatwidget/tests.rs index 01b6e0ed5..09f5073e7 100644 --- a/codex-rs/tui2/src/chatwidget/tests.rs +++ b/codex-rs/tui2/src/chatwidget/tests.rs @@ -363,6 +363,7 @@ async fn make_chatwidget_manual( skills: None, }); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test")); + let codex_home = cfg.codex_home.clone(); let widget = ChatWidget { app_event_tx, codex_op_tx: op_tx, @@ -371,7 +372,7 @@ async fn make_chatwidget_manual( config: cfg, model: resolved_model.clone(), auth_manager: auth_manager.clone(), - models_manager: Arc::new(ModelsManager::new(auth_manager)), + models_manager: Arc::new(ModelsManager::new(codex_home, auth_manager)), session_header: SessionHeader::new(resolved_model), initial_user_message: None, token_info: None, @@ -410,7 +411,10 @@ async fn make_chatwidget_manual( fn set_chatgpt_auth(chat: &mut ChatWidget) { chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); - chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone())); + chat.models_manager = Arc::new(ModelsManager::new( + chat.config.codex_home.clone(), + chat.auth_manager.clone(), + )); } pub(crate) async fn make_chatwidget_manual_with_sender() -> (