From cca0d534d98348e39171e5ed17bde52d42dc0f93 Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Thu, 12 Feb 2026 21:38:23 -0800 Subject: [PATCH 1/8] show user warning when using default fallback metadata, as it can break things --- codex-rs/core/src/state/session.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index c86f0cdfd22..330b3745ab6 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -31,6 +31,7 @@ pub(crate) struct SessionState { pub(crate) startup_regular_task: Option, pub(crate) active_mcp_tool_selection: Option>, pub(crate) active_connector_selection: HashSet, + warned_unknown_models: HashSet, } impl SessionState { @@ -49,6 +50,7 @@ impl SessionState { startup_regular_task: None, active_mcp_tool_selection: None, active_connector_selection: HashSet::new(), + warned_unknown_models: HashSet::new(), } } @@ -217,6 +219,13 @@ impl SessionState { pub(crate) fn clear_connector_selection(&mut self) { self.active_connector_selection.clear(); } + + /// Marks an unknown model warning as emitted for the provided slug. + /// + /// Returns `true` only the first time a slug is marked during this session. + pub(crate) fn mark_unknown_model_warning_emitted(&mut self, model_slug: &str) -> bool { + self.warned_unknown_models.insert(model_slug.to_string()) + } } // Sometimes new snapshots don't include credits or plan information. @@ -377,6 +386,16 @@ mod tests { assert_eq!(state.get_connector_selection(), HashSet::new()); } + #[tokio::test] + async fn unknown_model_warning_tracking_deduplicates_by_slug() { + let session_configuration = make_session_configuration_for_tests().await; + let mut state = SessionState::new(session_configuration); + + assert!(state.mark_unknown_model_warning_emitted("gpt-alpha")); + assert!(!state.mark_unknown_model_warning_emitted("gpt-alpha")); + assert!(state.mark_unknown_model_warning_emitted("gpt-beta")); + } + #[tokio::test] async fn set_rate_limits_defaults_limit_id_to_codex_when_missing() { let session_configuration = make_session_configuration_for_tests().await; From a4ee69a78e581efad6d4b61e5a53d05791e6d8c4 Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Thu, 12 Feb 2026 23:45:27 -0800 Subject: [PATCH 2/8] simplify --- codex-rs/core/src/state/session.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index 330b3745ab6..c86f0cdfd22 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -31,7 +31,6 @@ pub(crate) struct SessionState { pub(crate) startup_regular_task: Option, pub(crate) active_mcp_tool_selection: Option>, pub(crate) active_connector_selection: HashSet, - warned_unknown_models: HashSet, } impl SessionState { @@ -50,7 +49,6 @@ impl SessionState { startup_regular_task: None, active_mcp_tool_selection: None, active_connector_selection: HashSet::new(), - warned_unknown_models: HashSet::new(), } } @@ -219,13 +217,6 @@ impl SessionState { pub(crate) fn clear_connector_selection(&mut self) { self.active_connector_selection.clear(); } - - /// Marks an unknown model warning as emitted for the provided slug. - /// - /// Returns `true` only the first time a slug is marked during this session. - pub(crate) fn mark_unknown_model_warning_emitted(&mut self, model_slug: &str) -> bool { - self.warned_unknown_models.insert(model_slug.to_string()) - } } // Sometimes new snapshots don't include credits or plan information. @@ -386,16 +377,6 @@ mod tests { assert_eq!(state.get_connector_selection(), HashSet::new()); } - #[tokio::test] - async fn unknown_model_warning_tracking_deduplicates_by_slug() { - let session_configuration = make_session_configuration_for_tests().await; - let mut state = SessionState::new(session_configuration); - - assert!(state.mark_unknown_model_warning_emitted("gpt-alpha")); - assert!(!state.mark_unknown_model_warning_emitted("gpt-alpha")); - assert!(state.mark_unknown_model_warning_emitted("gpt-beta")); - } - #[tokio::test] async fn set_rate_limits_defaults_limit_id_to_codex_when_missing() { let session_configuration = make_session_configuration_for_tests().await; From 66df785ac44b328006e2b15bf3701f5d9ffb3a8b Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Thu, 12 Feb 2026 23:27:54 -0800 Subject: [PATCH 3/8] rm remote_models fflag --- .../app-server/src/codex_message_processor.rs | 3 +- codex-rs/app-server/tests/common/config.rs | 2 +- .../app-server/tests/suite/v2/plan_item.rs | 5 +- codex-rs/app-server/tests/suite/v2/review.rs | 1 - .../tests/suite/v2/thread_resume.rs | 2 - .../app-server/tests/suite/v2/turn_start.rs | 2 +- codex-rs/core/config.schema.json | 2 +- codex-rs/core/src/config/service.rs | 4 +- codex-rs/core/src/features.rs | 4 +- codex-rs/core/src/models_manager/manager.rs | 85 ++++++------------- codex-rs/core/tests/suite/compact.rs | 4 - .../core/tests/suite/model_info_overrides.rs | 5 +- codex-rs/core/tests/suite/model_switching.rs | 2 - codex-rs/core/tests/suite/model_tools.rs | 1 - codex-rs/core/tests/suite/models_cache_ttl.rs | 5 -- .../core/tests/suite/models_etag_responses.rs | 2 - codex-rs/core/tests/suite/personality.rs | 10 --- codex-rs/core/tests/suite/prompt_caching.rs | 1 - codex-rs/core/tests/suite/remote_models.rs | 27 ++---- codex-rs/core/tests/suite/rmcp_client.rs | 3 - codex-rs/core/tests/suite/view_image.rs | 1 - codex-rs/mcp-server/tests/suite/codex_tool.rs | 1 - 22 files changed, 43 insertions(+), 129 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 0439068db9a..ebee7263cfd 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -3608,8 +3608,7 @@ impl CodexMessageProcessor { cursor, include_hidden, } = params; - let mut config = (*config).clone(); - config.features.enable(Feature::RemoteModels); + let config = (*config).clone(); let models = supported_models(thread_manager, &config, include_hidden.unwrap_or(false)).await; let total = models.len(); diff --git a/codex-rs/app-server/tests/common/config.rs b/codex-rs/app-server/tests/common/config.rs index 09471b4a695..bffb35aa025 100644 --- a/codex-rs/app-server/tests/common/config.rs +++ b/codex-rs/app-server/tests/common/config.rs @@ -13,7 +13,7 @@ pub fn write_mock_responses_config_toml( compact_prompt: &str, ) -> std::io::Result<()> { // Phase 1: build the features block for config.toml. - let mut features = BTreeMap::from([(Feature::RemoteModels, false)]); + let mut features = BTreeMap::new(); for (feature, enabled) in feature_flags { features.insert(*feature, *enabled); } diff --git a/codex-rs/app-server/tests/suite/v2/plan_item.rs b/codex-rs/app-server/tests/suite/v2/plan_item.rs index d138954ac4b..881bbb54c8a 100644 --- a/codex-rs/app-server/tests/suite/v2/plan_item.rs +++ b/codex-rs/app-server/tests/suite/v2/plan_item.rs @@ -215,10 +215,7 @@ async fn collect_turn_notifications( } fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let features = BTreeMap::from([ - (Feature::RemoteModels, false), - (Feature::CollaborationModes, true), - ]); + let features = BTreeMap::from([(Feature::CollaborationModes, true)]); let feature_entries = features .into_iter() .map(|(feature, enabled)| { diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index 05397d485d3..219812d6b19 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -437,7 +437,6 @@ sandbox_mode = "read-only" model_provider = "mock_provider" [features] -remote_models = false shell_snapshot = false [model_providers.mock_provider] diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index e001d4a7fd6..3976d3eea6b 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -1008,7 +1008,6 @@ sandbox_mode = "read-only" model_provider = "mock_provider" [features] -remote_models = false personality = true [model_providers.mock_provider] @@ -1038,7 +1037,6 @@ sandbox_mode = "read-only" model_provider = "mock_provider" [features] -remote_models = false personality = true [model_providers.mock_provider] diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 97cb14f445d..2eecfc55905 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -1871,7 +1871,7 @@ fn create_config_toml_with_sandbox( feature_flags: &BTreeMap, sandbox_mode: &str, ) -> std::io::Result<()> { - let mut features = BTreeMap::from([(Feature::RemoteModels, false)]); + let mut features = BTreeMap::new(); for (feature, enabled) in feature_flags { features.insert(*feature, *enabled); } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index c9da5c688bb..f313084ad47 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1762,4 +1762,4 @@ }, "title": "ConfigToml", "type": "object" -} \ No newline at end of file +} diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/core/src/config/service.rs index db6963a2f8c..02a9da23fd0 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/core/src/config/service.rs @@ -777,7 +777,7 @@ unified_exec = true service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), - key_path: "features.remote_models".to_string(), + key_path: "features.personality".to_string(), value: serde_json::json!(true), merge_strategy: MergeStrategy::Replace, expected_version: None, @@ -797,7 +797,7 @@ hide_full_access_warning = true [features] unified_exec = true -remote_models = true +personality = true "#; assert_eq!(updated, expected); Ok(()) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 3cab14d1fa9..8b767f72fba 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -101,7 +101,7 @@ pub enum Feature { WindowsSandbox, /// Use the elevated Windows sandbox pipeline (setup + runner). WindowsSandboxElevated, - /// Refresh remote models and emit AppReady once the list is available. + /// Legacy remote-models flag kept for backward compatibility. RemoteModels, /// Experimental shell snapshotting. ShellSnapshot, @@ -529,7 +529,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::RemoteModels, key: "remote_models", - stage: Stage::Stable, + stage: Stage::Removed, default_enabled: true, }, FeatureSpec { diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index dd769da2e89..4400b7d6315 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -7,7 +7,6 @@ use crate::config::Config; use crate::default_client::build_reqwest_client; use crate::error::CodexErr; use crate::error::Result as CoreResult; -use crate::features::Feature; use crate::model_provider_info::ModelProviderInfo; use crate::models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets; use crate::models_manager::model_info; @@ -79,10 +78,7 @@ impl ModelsManager { config: &Config, refresh_strategy: RefreshStrategy, ) -> Vec { - if let Err(err) = self - .refresh_available_models(config, refresh_strategy) - .await - { + if let Err(err) = self.refresh_available_models(refresh_strategy).await { error!("failed to refresh available models: {err}"); } let remote_models = self.get_remote_models(config).await; @@ -118,10 +114,7 @@ impl ModelsManager { if let Some(model) = model.as_ref() { return model.to_string(); } - if let Err(err) = self - .refresh_available_models(config, refresh_strategy) - .await - { + if let Err(err) = self.refresh_available_models(refresh_strategy).await { error!("failed to refresh available models: {err}"); } let remote_models = self.get_remote_models(config).await; @@ -177,7 +170,7 @@ impl ModelsManager { /// Refresh models if the provided ETag differs from the cached ETag. /// /// Uses `Online` strategy to fetch latest models when ETags differ. - pub(crate) async fn refresh_if_new_etag(&self, etag: String, config: &Config) { + pub(crate) async fn refresh_if_new_etag(&self, etag: String, _config: &Config) { let current_etag = self.get_etag().await; if current_etag.clone().is_some() && current_etag.as_deref() == Some(etag.as_str()) { if let Err(err) = self.cache_manager.renew_cache_ttl().await { @@ -185,23 +178,13 @@ impl ModelsManager { } return; } - if let Err(err) = self - .refresh_available_models(config, RefreshStrategy::Online) - .await - { + if let Err(err) = self.refresh_available_models(RefreshStrategy::Online).await { error!("failed to refresh available models: {err}"); } } /// Refresh available models according to the specified strategy. - async fn refresh_available_models( - &self, - config: &Config, - refresh_strategy: RefreshStrategy, - ) -> CoreResult<()> { - if !config.features.enabled(Feature::RemoteModels) { - return Ok(()); - } + async fn refresh_available_models(&self, refresh_strategy: RefreshStrategy) -> CoreResult<()> { if self.auth_manager.auth_mode() != Some(AuthMode::Chatgpt) { if matches!( refresh_strategy, @@ -336,20 +319,12 @@ impl ModelsManager { merged_presets } - async fn get_remote_models(&self, config: &Config) -> Vec { - if config.features.enabled(Feature::RemoteModels) { - self.remote_models.read().await.clone() - } else { - Vec::new() - } + async fn get_remote_models(&self, _config: &Config) -> Vec { + self.remote_models.read().await.clone() } - fn try_get_remote_models(&self, config: &Config) -> Result, TryLockError> { - if config.features.enabled(Feature::RemoteModels) { - Ok(self.remote_models.try_read()?.clone()) - } else { - Ok(Vec::new()) - } + fn try_get_remote_models(&self, _config: &Config) -> Result, TryLockError> { + Ok(self.remote_models.try_read()?.clone()) } /// Construct a manager with a specific provider for testing. @@ -399,7 +374,6 @@ mod tests { use crate::CodexAuth; use crate::auth::AuthCredentialsStoreMode; use crate::config::ConfigBuilder; - use crate::features::Feature; use crate::model_provider_info::WireApi; use chrono::Utc; use codex_protocol::openai_models::ModelsResponse; @@ -476,12 +450,11 @@ mod tests { #[tokio::test] async fn get_model_info_tracks_fallback_usage() { let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let manager = ModelsManager::new(codex_home.path().to_path_buf(), auth_manager); @@ -520,12 +493,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -536,7 +508,7 @@ mod tests { ); manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("refresh succeeds"); let cached_remote = manager.get_remote_models(&config).await; @@ -577,12 +549,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -593,14 +564,14 @@ mod tests { ); manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("first refresh succeeds"); assert_models_contain(&manager.get_remote_models(&config).await, &remote_models); // Second call should read from cache and avoid the network. manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("cached refresh succeeds"); assert_models_contain(&manager.get_remote_models(&config).await, &remote_models); @@ -624,12 +595,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -640,7 +610,7 @@ mod tests { ); manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("initial refresh succeeds"); @@ -664,7 +634,7 @@ mod tests { .await; manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("second refresh succeeds"); assert_models_contain(&manager.get_remote_models(&config).await, &updated_models); @@ -693,12 +663,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -709,7 +678,7 @@ mod tests { ); manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("initial refresh succeeds"); @@ -733,7 +702,7 @@ mod tests { .await; manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("second refresh succeeds"); assert_models_contain(&manager.get_remote_models(&config).await, &updated_models); @@ -762,12 +731,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -779,7 +747,7 @@ mod tests { manager.cache_manager.set_ttl(Duration::ZERO); manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("initial refresh succeeds"); @@ -794,7 +762,7 @@ mod tests { .await; manager - .refresh_available_models(&config, RefreshStrategy::OnlineIfUncached) + .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("second refresh succeeds"); @@ -834,12 +802,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .build() .await .expect("load default test config"); - config.features.enable(Feature::RemoteModels); let auth_manager = Arc::new(AuthManager::new( codex_home.path().to_path_buf(), false, @@ -853,7 +820,7 @@ mod tests { ); manager - .refresh_available_models(&config, RefreshStrategy::Online) + .refresh_available_models(RefreshStrategy::Online) .await .expect("refresh should no-op without chatgpt auth"); let cached_remote = manager.get_remote_models(&config).await; diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 25278073ec1..22a6c720760 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -5,7 +5,6 @@ use codex_core::built_in_model_providers; use codex_core::compact::SUMMARIZATION_PROMPT; use codex_core::compact::SUMMARY_PREFIX; use codex_core::config::Config; -use codex_core::features::Feature; use codex_core::protocol::AskForApproval; use codex_core::protocol::EventMsg; use codex_core::protocol::ItemCompletedEvent; @@ -1708,7 +1707,6 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() { .with_config(move |config| { config.model_provider = model_provider; set_test_compact_prompt(config); - config.features.enable(Feature::RemoteModels); }); let test = builder.build(&server).await.expect("build test codex"); @@ -1831,7 +1829,6 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() { .with_config(move |config| { config.model_provider = model_provider; set_test_compact_prompt(config); - config.features.enable(Feature::RemoteModels); }); let initial = initial_builder .build(&server) @@ -1885,7 +1882,6 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() { .with_config(move |config| { config.model_provider = model_provider; set_test_compact_prompt(config); - config.features.enable(Feature::RemoteModels); }); let resumed = resumed_builder .resume(&server, home, rollout_path) diff --git a/codex-rs/core/tests/suite/model_info_overrides.rs b/codex-rs/core/tests/suite/model_info_overrides.rs index a9e18f9605b..2b085a004fc 100644 --- a/codex-rs/core/tests/suite/model_info_overrides.rs +++ b/codex-rs/core/tests/suite/model_info_overrides.rs @@ -1,5 +1,4 @@ use codex_core::CodexAuth; -use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_protocol::openai_models::TruncationPolicyConfig; use core_test_support::load_default_config_for_test; @@ -9,8 +8,7 @@ use tempfile::TempDir; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn offline_model_info_without_tool_output_override() { let codex_home = TempDir::new().expect("create temp dir"); - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth_manager = codex_core::test_support::auth_manager_from_auth( CodexAuth::create_dummy_chatgpt_auth_for_testing(), ); @@ -28,7 +26,6 @@ async fn offline_model_info_without_tool_output_override() { async fn offline_model_info_with_tool_output_override() { let codex_home = TempDir::new().expect("create temp dir"); let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); config.tool_output_token_limit = Some(123); let auth_manager = codex_core::test_support::auth_manager_from_auth( CodexAuth::create_dummy_chatgpt_auth_for_testing(), diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index 44d0d8a2fc7..aa5a91cd937 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -266,7 +266,6 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(move |config| { - config.features.enable(Feature::RemoteModels); config.model = Some(image_model_slug.to_string()); }); let test = builder.build(&server).await?; @@ -434,7 +433,6 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some(large_model_slug.to_string()); }); let test = builder.build(&server).await?; diff --git a/codex-rs/core/tests/suite/model_tools.rs b/codex-rs/core/tests/suite/model_tools.rs index 836807881b8..026be98dab8 100644 --- a/codex-rs/core/tests/suite/model_tools.rs +++ b/codex-rs/core/tests/suite/model_tools.rs @@ -36,7 +36,6 @@ async fn collect_tool_identifiers_for_model(model: &str) -> Vec { .with_model(model) // Keep tool expectations stable when the default web_search mode changes. .with_config(|config| { - config.features.enable(Feature::RemoteModels); config .web_search_mode .set(WebSearchMode::Cached) diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 780937c111c..72d88d545cc 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -6,7 +6,6 @@ use chrono::DateTime; use chrono::TimeZone; use chrono::Utc; use codex_core::CodexAuth; -use codex_core::features::Feature; use codex_core::models_manager::manager::RefreshStrategy; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; @@ -57,7 +56,6 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); builder = builder.with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some("gpt-5".to_string()); config.model_provider.request_max_retries = Some(0); config.model_provider.stream_max_retries = Some(1); @@ -160,7 +158,6 @@ async fn uses_cache_when_version_matches() -> Result<()> { write_cache_sync(&cache_path, &cache).expect("write cache"); }) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model_provider.request_max_retries = Some(0); }); @@ -208,7 +205,6 @@ async fn refreshes_when_cache_version_missing() -> Result<()> { write_cache_sync(&cache_path, &cache).expect("write cache"); }) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model_provider.request_max_retries = Some(0); }); @@ -257,7 +253,6 @@ async fn refreshes_when_cache_version_differs() -> Result<()> { write_cache_sync(&cache_path, &cache).expect("write cache"); }) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model_provider.request_max_retries = Some(0); }); diff --git a/codex-rs/core/tests/suite/models_etag_responses.rs b/codex-rs/core/tests/suite/models_etag_responses.rs index 413616b9dc7..13c31cd7f92 100644 --- a/codex-rs/core/tests/suite/models_etag_responses.rs +++ b/codex-rs/core/tests/suite/models_etag_responses.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use anyhow::Result; use codex_core::CodexAuth; -use codex_core::features::Feature; use codex_core::protocol::AskForApproval; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; @@ -48,7 +47,6 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch .with_auth(auth) .with_model("gpt-5") .with_config(|config| { - config.features.enable(Feature::RemoteModels); // Keep this test deterministic: no request retries, and a small stream retry budget. config.model_provider.request_max_retries = Some(0); config.model_provider.stream_max_retries = Some(1); diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 51f99720aa3..4ecee0c95f4 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -81,7 +81,6 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); }); let test = builder.build(&server).await?; @@ -127,7 +126,6 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result< let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.personality = Some(Personality::Friendly); }); @@ -181,7 +179,6 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> { let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.personality = Some(Personality::None); }); @@ -242,7 +239,6 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); }); let test = builder.build(&server).await?; @@ -290,7 +286,6 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> let mut builder = test_codex() .with_model("exp-codex-personality") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); }); let test = builder.build(&server).await?; @@ -386,7 +381,6 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho let mut builder = test_codex() .with_model("exp-codex-personality") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.personality = Some(Personality::Pragmatic); }); @@ -494,7 +488,6 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> let mut builder = test_codex() .with_model("exp-codex-personality") .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.disable(Feature::Personality); }); let test = builder.build(&server).await?; @@ -632,7 +625,6 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.disable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some(remote_slug.to_string()); config.personality = Some(Personality::Friendly); @@ -750,7 +742,6 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some(remote_slug.to_string()); config.personality = Some(Personality::Friendly); @@ -867,7 +858,6 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.features.enable(Feature::Personality); config.model = Some("gpt-5.2-codex".to_string()); }); diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 71a565540b7..cd3b4272a07 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -194,7 +194,6 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an let TestCodex { codex, .. } = test_codex() .with_config(|config| { config.user_instructions = Some("be consistent and helpful".to_string()); - config.features.enable(Feature::RemoteModels); config.features.disable(Feature::ApplyPatchFreeform); config.features.enable(Feature::CollaborationModes); config.model = Some("gpt-5".to_string()); diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 29ba047d019..90b565cd153 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -8,7 +8,6 @@ use codex_core::CodexAuth; use codex_core::ModelProviderInfo; use codex_core::built_in_model_providers; use codex_core::config::Config; -use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_core::models_manager::manager::RefreshStrategy; use codex_core::protocol::AskForApproval; @@ -92,8 +91,7 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<( .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -163,7 +161,6 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some(requested_model.to_string()); }) .build(&server) @@ -253,7 +250,6 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some("gpt-5.1".to_string()); }); let TestCodex { @@ -375,7 +371,6 @@ async fn remote_models_truncation_policy_without_override_preserves_remote() -> let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some("gpt-5.1".to_string()); }); let test = builder.build(&server).await?; @@ -420,7 +415,6 @@ async fn remote_models_truncation_policy_with_tool_output_override() -> Result<( let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some("gpt-5.1".to_string()); config.tool_output_token_limit = Some(50); }); @@ -502,7 +496,6 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { let mut builder = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some("gpt-5.1".to_string()); }); let TestCodex { @@ -574,8 +567,7 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -639,8 +631,7 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -690,8 +681,7 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -738,8 +728,7 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res let _models_mock = mount_models_once(&server, ModelsResponse { models: Vec::new() }).await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -783,8 +772,7 @@ async fn remote_models_request_times_out_after_5s() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -850,8 +838,7 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home).await; - config.features.enable(Feature::RemoteModels); + let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 467f4e354d6..ec6603ffa39 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -11,7 +11,6 @@ use std::time::UNIX_EPOCH; use codex_core::CodexAuth; use codex_core::config::types::McpServerConfig; use codex_core::config::types::McpServerTransportConfig; -use codex_core::features::Feature; use codex_core::models_manager::manager::RefreshStrategy; use codex_core::protocol::AskForApproval; @@ -441,8 +440,6 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re let fixture = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(move |config| { - config.features.enable(Feature::RemoteModels); - let mut servers = config.mcp_servers.get().clone(); servers.insert( server_name.to_string(), diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index bd5bd6a0371..bad9efa2d00 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -698,7 +698,6 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an let TestCodex { codex, cwd, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { - config.features.enable(Feature::RemoteModels); config.model = Some(model_slug.to_string()); }) .build(&server) diff --git a/codex-rs/mcp-server/tests/suite/codex_tool.rs b/codex-rs/mcp-server/tests/suite/codex_tool.rs index c898b8f0d72..de8a9306d4b 100644 --- a/codex-rs/mcp-server/tests/suite/codex_tool.rs +++ b/codex-rs/mcp-server/tests/suite/codex_tool.rs @@ -507,7 +507,6 @@ request_max_retries = 0 stream_max_retries = 0 [features] -remote_models = false "# ), ) From 5e758e7aa204a18bdee5a9a8d05576ab66bed7c9 Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Thu, 12 Feb 2026 23:59:23 -0800 Subject: [PATCH 4/8] fix --- codex-rs/core/src/features.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 8b767f72fba..08093b895fd 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -530,7 +530,7 @@ pub const FEATURES: &[FeatureSpec] = &[ id: Feature::RemoteModels, key: "remote_models", stage: Stage::Removed, - default_enabled: true, + default_enabled: false, }, FeatureSpec { id: Feature::PowershellUtf8, From 75c26171235e30dd656f7f44a5c6599bcd0b7e45 Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Fri, 13 Feb 2026 00:23:39 -0800 Subject: [PATCH 5/8] more cleanup --- .../app-server/src/codex_message_processor.rs | 8 +- codex-rs/app-server/src/models.rs | 10 +- codex-rs/core/src/codex.rs | 12 +-- codex-rs/core/src/features.rs | 2 +- codex-rs/core/src/models_manager/manager.rs | 101 ++++++------------ codex-rs/core/src/thread_manager.rs | 3 +- codex-rs/core/tests/suite/model_switching.rs | 6 +- codex-rs/core/tests/suite/models_cache_ttl.rs | 10 +- codex-rs/core/tests/suite/personality.rs | 31 +----- codex-rs/core/tests/suite/remote_models.rs | 54 +++------- codex-rs/core/tests/suite/rmcp_client.rs | 2 +- codex-rs/exec/src/lib.rs | 2 +- codex-rs/tui/src/app.rs | 4 +- codex-rs/tui/src/chatwidget.rs | 8 +- codex-rs/tui/src/chatwidget/tests.rs | 2 +- 15 files changed, 74 insertions(+), 181 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index ebee7263cfd..06e52839c08 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -599,11 +599,10 @@ impl CodexMessageProcessor { ClientRequest::ModelList { request_id, params } => { let outgoing = self.outgoing.clone(); let thread_manager = self.thread_manager.clone(); - let config = self.config.clone(); let request_id = to_connection_request_id(request_id); tokio::spawn(async move { - Self::list_models(outgoing, thread_manager, config, request_id, params).await; + Self::list_models(outgoing, thread_manager, request_id, params).await; }); } ClientRequest::ExperimentalFeatureList { request_id, params } => { @@ -3599,7 +3598,6 @@ impl CodexMessageProcessor { async fn list_models( outgoing: Arc, thread_manager: Arc, - config: Arc, request_id: ConnectionRequestId, params: ModelListParams, ) { @@ -3608,9 +3606,7 @@ impl CodexMessageProcessor { cursor, include_hidden, } = params; - let config = (*config).clone(); - let models = - supported_models(thread_manager, &config, include_hidden.unwrap_or(false)).await; + let models = supported_models(thread_manager, include_hidden.unwrap_or(false)).await; let total = models.len(); if total == 0 { diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index d31e005210e..e30ca8b2d4a 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -3,18 +3,12 @@ use std::sync::Arc; use codex_app_server_protocol::Model; use codex_app_server_protocol::ReasoningEffortOption; use codex_core::ThreadManager; -use codex_core::config::Config; -use codex_core::models_manager::manager::RefreshStrategy; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ReasoningEffortPreset; -pub async fn supported_models( - thread_manager: Arc, - config: &Config, - include_hidden: bool, -) -> Vec { +pub async fn supported_models(thread_manager: Arc, include_hidden: bool) -> Vec { thread_manager - .list_models(config, RefreshStrategy::OnlineIfUncached) + .list_models(codex_core::models_manager::manager::RefreshStrategy::OnlineIfUncached) .await .into_iter() .filter(|preset| include_hidden || preset.show_in_picker) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 34f0d9f873c..80f90e26760 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -327,15 +327,11 @@ impl Codex { let config = Arc::new(config); let _ = models_manager - .list_models( - &config, - crate::models_manager::manager::RefreshStrategy::OnlineIfUncached, - ) + .list_models(crate::models_manager::manager::RefreshStrategy::OnlineIfUncached) .await; let model = models_manager .get_default_model( &config.model, - &config, crate::models_manager::manager::RefreshStrategy::OnlineIfUncached, ) .await; @@ -5545,11 +5541,7 @@ async fn try_run_sampling_request( } ResponseEvent::ModelsEtag(etag) => { // Update internal state with latest models etag - let config = sess.get_config().await; - sess.services - .models_manager - .refresh_if_new_etag(etag, &config) - .await; + sess.services.models_manager.refresh_if_new_etag(etag).await; } ResponseEvent::Completed { response_id: _, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 08093b895fd..8b8519af7c3 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -101,7 +101,7 @@ pub enum Feature { WindowsSandbox, /// Use the elevated Windows sandbox pipeline (setup + runner). WindowsSandboxElevated, - /// Legacy remote-models flag kept for backward compatibility. + /// Legacy remote models flag kept for backward compatibility. RemoteModels, /// Experimental shell snapshotting. ShellSnapshot, diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 4400b7d6315..05a02215811 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -73,15 +73,11 @@ impl ModelsManager { /// List all available models, refreshing according to the specified strategy. /// /// Returns model presets sorted by priority and filtered by auth mode and visibility. - pub async fn list_models( - &self, - config: &Config, - refresh_strategy: RefreshStrategy, - ) -> Vec { + pub async fn list_models(&self, refresh_strategy: RefreshStrategy) -> Vec { if let Err(err) = self.refresh_available_models(refresh_strategy).await { error!("failed to refresh available models: {err}"); } - let remote_models = self.get_remote_models(config).await; + let remote_models = self.get_remote_models().await; self.build_available_models(remote_models) } @@ -95,8 +91,8 @@ impl ModelsManager { /// Attempt to list models without blocking, using the current cached state. /// /// Returns an error if the internal lock cannot be acquired. - pub fn try_list_models(&self, config: &Config) -> Result, TryLockError> { - let remote_models = self.try_get_remote_models(config)?; + pub fn try_list_models(&self) -> Result, TryLockError> { + let remote_models = self.try_get_remote_models()?; Ok(self.build_available_models(remote_models)) } @@ -108,7 +104,6 @@ impl ModelsManager { pub async fn get_default_model( &self, model: &Option, - config: &Config, refresh_strategy: RefreshStrategy, ) -> String { if let Some(model) = model.as_ref() { @@ -117,7 +112,7 @@ impl ModelsManager { if let Err(err) = self.refresh_available_models(refresh_strategy).await { error!("failed to refresh available models: {err}"); } - let remote_models = self.get_remote_models(config).await; + let remote_models = self.get_remote_models().await; let available = self.build_available_models(remote_models); available .iter() @@ -130,28 +125,26 @@ impl ModelsManager { // todo(aibrahim): look if we can tighten it to pub(crate) /// Look up model metadata, applying remote overrides and config adjustments. pub async fn get_model_info(&self, model: &str, config: &Config) -> ModelInfo { - let remote = self - .find_remote_model_by_longest_prefix(model, config) - .await; - let model_info = if let Some(remote) = remote { - ModelInfo { - slug: model.to_string(), - used_fallback_model_metadata: false, - ..remote - } + let remote = self.find_remote_model_by_longest_prefix(model).await; + // Preserve how metadata was resolved so callers can warn when we use fallback. + let (mut model_info, used_fallback_model_metadata) = if let Some(remote) = remote { + ( + ModelInfo { + slug: model.to_string(), + ..remote + }, + false, + ) } else { - model_info::model_info_from_slug(model) + (model_info::model_info_from_slug(model), true) }; + model_info.used_fallback_model_metadata = used_fallback_model_metadata; model_info::with_config_overrides(model_info, config) } - async fn find_remote_model_by_longest_prefix( - &self, - model: &str, - config: &Config, - ) -> Option { + async fn find_remote_model_by_longest_prefix(&self, model: &str) -> Option { let mut best: Option = None; - for candidate in self.get_remote_models(config).await { + for candidate in self.get_remote_models().await { if !model.starts_with(&candidate.slug) { continue; } @@ -170,7 +163,7 @@ impl ModelsManager { /// Refresh models if the provided ETag differs from the cached ETag. /// /// Uses `Online` strategy to fetch latest models when ETags differ. - pub(crate) async fn refresh_if_new_etag(&self, etag: String, _config: &Config) { + pub(crate) async fn refresh_if_new_etag(&self, etag: String) { let current_etag = self.get_etag().await; if current_etag.clone().is_some() && current_etag.as_deref() == Some(etag.as_str()) { if let Err(err) = self.cache_manager.renew_cache_ttl().await { @@ -319,11 +312,11 @@ impl ModelsManager { merged_presets } - async fn get_remote_models(&self, _config: &Config) -> Vec { + async fn get_remote_models(&self) -> Vec { self.remote_models.read().await.clone() } - fn try_get_remote_models(&self, _config: &Config) -> Result, TryLockError> { + fn try_get_remote_models(&self) -> Result, TryLockError> { Ok(self.remote_models.try_read()?.clone()) } @@ -459,7 +452,7 @@ mod tests { AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let manager = ModelsManager::new(codex_home.path().to_path_buf(), auth_manager); let known_slug = manager - .get_remote_models(&config) + .get_remote_models() .await .first() .expect("bundled models should include at least one model") @@ -493,11 +486,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -511,12 +499,10 @@ mod tests { .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("refresh succeeds"); - let cached_remote = manager.get_remote_models(&config).await; + let cached_remote = manager.get_remote_models().await; assert_models_contain(&cached_remote, &remote_models); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; let high_idx = available .iter() .position(|model| model.model == "priority-high") @@ -549,11 +535,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -567,14 +548,14 @@ mod tests { .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("first refresh succeeds"); - assert_models_contain(&manager.get_remote_models(&config).await, &remote_models); + assert_models_contain(&manager.get_remote_models().await, &remote_models); // Second call should read from cache and avoid the network. manager .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("cached refresh succeeds"); - assert_models_contain(&manager.get_remote_models(&config).await, &remote_models); + assert_models_contain(&manager.get_remote_models().await, &remote_models); assert_eq!( models_mock.requests().len(), 1, @@ -595,11 +576,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -637,7 +613,7 @@ mod tests { .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("second refresh succeeds"); - assert_models_contain(&manager.get_remote_models(&config).await, &updated_models); + assert_models_contain(&manager.get_remote_models().await, &updated_models); assert_eq!( initial_mock.requests().len(), 1, @@ -663,11 +639,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -705,7 +676,7 @@ mod tests { .refresh_available_models(RefreshStrategy::OnlineIfUncached) .await .expect("second refresh succeeds"); - assert_models_contain(&manager.get_remote_models(&config).await, &updated_models); + assert_models_contain(&manager.get_remote_models().await, &updated_models); assert_eq!( initial_mock.requests().len(), 1, @@ -731,11 +702,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); let provider = provider_for(server.uri()); @@ -767,7 +733,7 @@ mod tests { .expect("second refresh succeeds"); let available = manager - .try_list_models(&config) + .try_list_models() .expect("models should be available"); assert!( available.iter().any(|preset| preset.model == "remote-new"), @@ -802,11 +768,6 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .build() - .await - .expect("load default test config"); let auth_manager = Arc::new(AuthManager::new( codex_home.path().to_path_buf(), false, @@ -823,7 +784,7 @@ mod tests { .refresh_available_models(RefreshStrategy::Online) .await .expect("refresh should no-op without chatgpt auth"); - let cached_remote = manager.get_remote_models(&config).await; + let cached_remote = manager.get_remote_models().await; assert!( !cached_remote .iter() diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index b13ef8c61a8..c16a8838525 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -230,12 +230,11 @@ impl ThreadManager { pub async fn list_models( &self, - config: &Config, refresh_strategy: crate::models_manager::manager::RefreshStrategy, ) -> Vec { self.state .models_manager - .list_models(config, refresh_strategy) + .list_models(refresh_strategy) .await } diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index aa5a91cd937..4d4abafeb6e 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -271,7 +271,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); let _ = models_manager - .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await; let image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=" .to_string(); @@ -438,9 +438,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); - let available_models = models_manager - .list_models(&test.config, RefreshStrategy::Online) - .await; + let available_models = models_manager.list_models(RefreshStrategy::Online).await; assert!( available_models .iter() diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 72d88d545cc..e9f0a73f98c 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -68,7 +68,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { // Populate cache via initial refresh. let models_manager = test.thread_manager.get_models_manager(); let _ = models_manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await; let cache_path = config.codex_home.join(CACHE_FILE); @@ -121,7 +121,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { // Cached models remain usable offline. let offline_models = test .thread_manager - .list_models(&config, RefreshStrategy::Offline) + .list_models(RefreshStrategy::Offline) .await; assert!( offline_models @@ -164,7 +164,7 @@ async fn uses_cache_when_version_matches() -> Result<()> { let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); let models = models_manager - .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await; assert!( @@ -211,7 +211,7 @@ async fn refreshes_when_cache_version_missing() -> Result<()> { let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); let models = models_manager - .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await; assert!( @@ -259,7 +259,7 @@ async fn refreshes_when_cache_version_differs() -> Result<()> { let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); let models = models_manager - .list_models(&test.config, RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await; assert!( diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 4ecee0c95f4..b5b11af800e 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -631,12 +631,7 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul }); let test = builder.build(&server).await?; - wait_for_model_available( - &test.thread_manager.get_models_manager(), - remote_slug, - &test.config, - ) - .await; + wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await; test.codex .submit(Op::UserTurn { @@ -748,12 +743,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow }); let test = builder.build(&server).await?; - wait_for_model_available( - &test.thread_manager.get_models_manager(), - remote_slug, - &test.config, - ) - .await; + wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await; test.codex .submit(Op::UserTurn { @@ -863,12 +853,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - }); let test = builder.build(&server).await?; - wait_for_model_available( - &test.thread_manager.get_models_manager(), - remote_slug, - &test.config, - ) - .await; + wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await; test.codex .submit(Op::UserTurn { @@ -947,16 +932,10 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - Ok(()) } -async fn wait_for_model_available( - manager: &Arc, - slug: &str, - config: &codex_core::config::Config, -) { +async fn wait_for_model_available(manager: &Arc, slug: &str) { let deadline = Instant::now() + Duration::from_secs(2); loop { - let models = manager - .list_models(config, RefreshStrategy::OnlineIfUncached) - .await; + let models = manager.list_models(RefreshStrategy::OnlineIfUncached).await; if models.iter().any(|model| model.model == slug) { return; } diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 90b565cd153..110150d4475 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -7,7 +7,6 @@ use anyhow::Result; use codex_core::CodexAuth; use codex_core::ModelProviderInfo; use codex_core::built_in_model_providers; -use codex_core::config::Config; use codex_core::models_manager::manager::ModelsManager; use codex_core::models_manager::manager::RefreshStrategy; use codex_core::protocol::AskForApproval; @@ -104,9 +103,7 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<( provider, ); - manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + manager.list_models(RefreshStrategy::OnlineIfUncached).await; let model_info = manager.get_model_info("gpt-5.3-codex-test", &config).await; @@ -261,8 +258,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { } = builder.build(&server).await?; let models_manager = thread_manager.get_models_manager(); - let available_model = - wait_for_model_available(&models_manager, REMOTE_MODEL_SLUG, &config).await; + let available_model = wait_for_model_available(&models_manager, REMOTE_MODEL_SLUG).await; assert_eq!(available_model.model, REMOTE_MODEL_SLUG); @@ -376,7 +372,7 @@ async fn remote_models_truncation_policy_without_override_preserves_remote() -> let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); - wait_for_model_available(&models_manager, slug, &test.config).await; + wait_for_model_available(&models_manager, slug).await; let model_info = models_manager.get_model_info(slug, &test.config).await; assert_eq!( @@ -421,7 +417,7 @@ async fn remote_models_truncation_policy_with_tool_output_override() -> Result<( let test = builder.build(&server).await?; let models_manager = test.thread_manager.get_models_manager(); - wait_for_model_available(&models_manager, slug, &test.config).await; + wait_for_model_available(&models_manager, slug).await; let model_info = models_manager.get_model_info(slug, &test.config).await; assert_eq!( @@ -507,7 +503,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { } = builder.build(&server).await?; let models_manager = thread_manager.get_models_manager(); - wait_for_model_available(&models_manager, model, &config).await; + wait_for_model_available(&models_manager, model).await; codex .submit(Op::OverrideTurnContext { @@ -567,7 +563,6 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> { .await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -580,9 +575,7 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> { provider, ); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; let remote = available .iter() .find(|model| model.model == "remote-alpha") @@ -631,7 +624,6 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> { .await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -644,9 +636,7 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> { provider, ); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; assert_eq!( available.first().map(|model| model.model.as_str()), Some("remote-top") @@ -681,7 +671,6 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> { .await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -694,9 +683,7 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> { provider, ); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; let overridden = available .iter() .find(|model| model.model == slug) @@ -728,7 +715,6 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res let _models_mock = mount_models_once(&server, ModelsResponse { models: Vec::new() }).await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -741,9 +727,7 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res provider, ); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; let bundled_slug = bundled_model_slug(); assert!( available.iter().any(|model| model.model == bundled_slug), @@ -772,7 +756,6 @@ async fn remote_models_request_times_out_after_5s() -> Result<()> { .await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -788,7 +771,7 @@ async fn remote_models_request_times_out_after_5s() -> Result<()> { let start = Instant::now(); let model = timeout( Duration::from_secs(7), - manager.get_default_model(&None, &config, RefreshStrategy::OnlineIfUncached), + manager.get_default_model(&None, RefreshStrategy::OnlineIfUncached), ) .await; let elapsed = start.elapsed(); @@ -838,7 +821,6 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { .await; let codex_home = TempDir::new()?; - let config = load_default_config_for_test(&codex_home).await; let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); let provider = ModelProviderInfo { @@ -852,13 +834,11 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { ); let selected = manager - .get_default_model(&None, &config, RefreshStrategy::OnlineIfUncached) + .get_default_model(&None, RefreshStrategy::OnlineIfUncached) .await; assert_eq!(selected, "gpt-5.2-codex"); - let available = manager - .list_models(&config, RefreshStrategy::OnlineIfUncached) - .await; + let available = manager.list_models(RefreshStrategy::OnlineIfUncached).await; let hidden = available .iter() .find(|model| model.model == "codex-auto-balanced") @@ -875,17 +855,11 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { Ok(()) } -async fn wait_for_model_available( - manager: &Arc, - slug: &str, - config: &Config, -) -> ModelPreset { +async fn wait_for_model_available(manager: &Arc, slug: &str) -> ModelPreset { let deadline = Instant::now() + Duration::from_secs(2); loop { if let Some(model) = { - let guard = manager - .list_models(config, RefreshStrategy::OnlineIfUncached) - .await; + let guard = manager.list_models(RefreshStrategy::OnlineIfUncached).await; guard.iter().find(|model| model.model == slug).cloned() } { return model; diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index ec6603ffa39..d1394f7a74d 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -475,7 +475,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re fixture .thread_manager .get_models_manager() - .list_models(&fixture.config, RefreshStrategy::Online) + .list_models(RefreshStrategy::Online) .await; assert_eq!(models_mock.requests().len(), 1); diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 68c88f00001..49163c900b8 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -374,7 +374,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any )); let default_model = thread_manager .get_models_manager() - .get_default_model(&config.model, &config, RefreshStrategy::OnlineIfUncached) + .get_default_model(&config.model, RefreshStrategy::OnlineIfUncached) .await; // Handle resume subcommand by resolving a rollout path and using explicit resume API. diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index b23b5f1a13b..108f85ff8a4 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1029,11 +1029,11 @@ impl App { )); let mut model = thread_manager .get_models_manager() - .get_default_model(&config.model, &config, RefreshStrategy::Offline) + .get_default_model(&config.model, RefreshStrategy::Offline) .await; let available_models = thread_manager .get_models_manager() - .list_models(&config, RefreshStrategy::Offline) + .list_models(RefreshStrategy::Offline) .await; let exit_info = handle_model_migration_prompt_if_needed( tui, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index dcbee999bc8..c4a20b567ff 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -4698,7 +4698,7 @@ impl ChatWidget { } fn lower_cost_preset(&self) -> Option { - let models = self.models_manager.try_list_models(&self.config).ok()?; + let models = self.models_manager.try_list_models().ok()?; models .iter() .find(|preset| preset.show_in_picker && preset.model == NUDGE_MODEL_SLUG) @@ -4815,7 +4815,7 @@ impl ChatWidget { return; } - let presets: Vec = match self.models_manager.try_list_models(&self.config) { + let presets: Vec = match self.models_manager.try_list_models() { Ok(models) => models, Err(_) => { self.add_info_message( @@ -6137,7 +6137,7 @@ impl ChatWidget { fn current_model_supports_personality(&self) -> bool { let model = self.current_model(); self.models_manager - .try_list_models(&self.config) + .try_list_models() .ok() .and_then(|models| { models @@ -6155,7 +6155,7 @@ impl ChatWidget { fn current_model_supports_images(&self) -> bool { let model = self.current_model(); self.models_manager - .try_list_models(&self.config) + .try_list_models() .ok() .and_then(|models| { models diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index f069db91608..f49d3182744 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -2749,7 +2749,7 @@ fn active_blob(chat: &ChatWidget) -> String { fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset { let models = chat .models_manager - .try_list_models(&chat.config) + .try_list_models() .expect("models lock available"); models .iter() From 30cc58319c5845ac743c19eedd2d9eeaea4a2021 Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Tue, 17 Feb 2026 10:54:12 -0800 Subject: [PATCH 6/8] cleanup after rebase --- codex-rs/core/config.schema.json | 2 +- codex-rs/core/src/models_manager/manager.rs | 18 ++-- codex-rs/core/tests/suite/personality.rs | 111 -------------------- 3 files changed, 8 insertions(+), 123 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index f313084ad47..c9da5c688bb 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1762,4 +1762,4 @@ }, "title": "ConfigToml", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 05a02215811..457351995f8 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -126,19 +126,15 @@ impl ModelsManager { /// Look up model metadata, applying remote overrides and config adjustments. pub async fn get_model_info(&self, model: &str, config: &Config) -> ModelInfo { let remote = self.find_remote_model_by_longest_prefix(model).await; - // Preserve how metadata was resolved so callers can warn when we use fallback. - let (mut model_info, used_fallback_model_metadata) = if let Some(remote) = remote { - ( - ModelInfo { - slug: model.to_string(), - ..remote - }, - false, - ) + let model_info = if let Some(remote) = remote { + ModelInfo { + slug: model.to_string(), + used_fallback_model_metadata: false, + ..remote + } } else { - (model_info::model_info_from_slug(model), true) + model_info::model_info_from_slug(model) }; - model_info.used_fallback_model_metadata = used_fallback_model_metadata; model_info::with_config_overrides(model_info, config) } diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index b5b11af800e..d15147ed367 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -563,117 +563,6 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Result<()> { - skip_if_no_network!(Ok(())); - - let server = MockServer::builder() - .body_print_limit(BodyPrintLimit::Limited(80_000)) - .start() - .await; - - let remote_slug = "gpt-5.2-codex"; - let remote_personality_message = "Friendly from remote template"; - let remote_model = ModelInfo { - slug: remote_slug.to_string(), - display_name: "Remote personality test".to_string(), - description: Some("Remote model with personality template".to_string()), - default_reasoning_level: Some(ReasoningEffort::Medium), - supported_reasoning_levels: vec![ReasoningEffortPreset { - effort: ReasoningEffort::Medium, - description: ReasoningEffort::Medium.to_string(), - }], - shell_type: ConfigShellToolType::UnifiedExec, - visibility: ModelVisibility::List, - supported_in_api: true, - priority: 1, - upgrade: None, - base_instructions: "base instructions".to_string(), - model_messages: Some(ModelMessages { - instructions_template: Some("Base instructions\n{{ personality }}\n".to_string()), - instructions_variables: Some(ModelInstructionsVariables { - personality_default: None, - personality_friendly: Some(remote_personality_message.to_string()), - personality_pragmatic: None, - }), - }), - supports_reasoning_summaries: false, - support_verbosity: false, - default_verbosity: None, - apply_patch_tool_type: None, - truncation_policy: TruncationPolicyConfig::bytes(10_000), - supports_parallel_tool_calls: false, - context_window: Some(128_000), - auto_compact_token_limit: None, - effective_context_window_percent: 95, - experimental_supported_tools: Vec::new(), - input_modalities: default_input_modalities(), - prefer_websockets: false, - used_fallback_model_metadata: false, - }; - - let _models_mock = mount_models_once( - &server, - ModelsResponse { - models: vec![remote_model], - }, - ) - .await; - - let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; - - let mut builder = test_codex() - .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) - .with_config(|config| { - config.features.enable(Feature::Personality); - config.model = Some(remote_slug.to_string()); - config.personality = Some(Personality::Friendly); - }); - let test = builder.build(&server).await?; - - wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await; - - test.codex - .submit(Op::UserTurn { - items: vec![UserInput::Text { - text: "hello".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - model: remote_slug.to_string(), - effort: test.config.model_reasoning_effort, - summary: ReasoningSummary::Auto, - collaboration_mode: None, - personality: None, - }) - .await?; - - wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - - let request = resp_mock.single_request(); - let instructions_text = request.instructions_text(); - - assert!( - instructions_text.contains("You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals."), - "expected instructions to use the template instructions, got: {instructions_text:?}" - ); - assert!( - instructions_text.contains( - "You optimize for team morale and being a supportive teammate as much as code quality." - ), - "expected instructions to include the local friendly personality template, got: {instructions_text:?}" - ); - assert!( - !instructions_text.contains("{{ personality }}"), - "expected legacy personality placeholder to be replaced, got: {instructions_text:?}" - ); - - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); From de5cb1646a6bac3d2a689ddbddc2b03be18ed02f Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Tue, 17 Feb 2026 11:14:41 -0800 Subject: [PATCH 7/8] fmt --- codex-rs/app-server/src/models.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index e30ca8b2d4a..79a2a8ee7fe 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -6,7 +6,10 @@ use codex_core::ThreadManager; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ReasoningEffortPreset; -pub async fn supported_models(thread_manager: Arc, include_hidden: bool) -> Vec { +pub async fn supported_models( + thread_manager: Arc, + include_hidden: bool, +) -> Vec { thread_manager .list_models(codex_core::models_manager::manager::RefreshStrategy::OnlineIfUncached) .await From 41f979c129f54c304317a6962b4baafc41bc100c Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Tue, 17 Feb 2026 11:22:47 -0800 Subject: [PATCH 8/8] fix --- codex-rs/app-server/src/models.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index 79a2a8ee7fe..1594c66229f 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use codex_app_server_protocol::Model; use codex_app_server_protocol::ReasoningEffortOption; use codex_core::ThreadManager; +use codex_core::models_manager::manager::RefreshStrategy; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ReasoningEffortPreset; @@ -11,7 +12,7 @@ pub async fn supported_models( include_hidden: bool, ) -> Vec { thread_manager - .list_models(codex_core::models_manager::manager::RefreshStrategy::OnlineIfUncached) + .list_models(RefreshStrategy::OnlineIfUncached) .await .into_iter() .filter(|preset| include_hidden || preset.show_in_picker)