diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 27c4aa8f74..7494c4459d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -182,70 +182,26 @@ impl App { let app_event_tx = AppEventSender::new(app_event_tx); let mut config = config; - let pending_model_migration_notice = - crate::model_migration::maybe_show_pending_model_migration_notice(&config); let conversation_manager = Arc::new(ConversationManager::new( auth_manager.clone(), SessionSource::Cli, )); - if let Some(notice) = &pending_model_migration_notice { - let outcome = crate::model_migration::run_startup_model_migration_prompt( + if matches!( + crate::model_migration::maybe_run_startup_model_migration_prompt( tui, - &config, + &mut config, conversation_manager.get_models_manager().as_ref(), - notice, ) - .await?; - - match outcome { - crate::model_migration::ModelMigrationOutcome::Accepted => { - config.model = Some(notice.to_model.clone()); - config - .notices - .model_migrations - .insert(notice.from_model.clone(), notice.to_model.clone()); - - let edits = ConfigEditsBuilder::new(&config.codex_home) - .record_model_migration_seen( - notice.from_model.as_str(), - notice.to_model.as_str(), - ) - .set_model(config.model.as_deref(), config.model_reasoning_effort); - if let Err(err) = edits.apply().await { - tracing::error!( - error = %err, - "failed to persist model migration prompt outcome" - ); - } - } - crate::model_migration::ModelMigrationOutcome::Rejected => { - config - .notices - .model_migrations - .insert(notice.from_model.clone(), notice.to_model.clone()); - - let edits = ConfigEditsBuilder::new(&config.codex_home) - .record_model_migration_seen( - notice.from_model.as_str(), - notice.to_model.as_str(), - ); - if let Err(err) = edits.apply().await { - tracing::error!( - error = %err, - "failed to persist model migration prompt outcome" - ); - } - } - crate::model_migration::ModelMigrationOutcome::Exit => { - return Ok(AppExitInfo { - token_usage: TokenUsage::default(), - conversation_id: None, - update_action: None, - }); - } - } + .await?, + crate::model_migration::StartupModelMigrationAction::Exit + ) { + return Ok(AppExitInfo { + token_usage: TokenUsage::default(), + conversation_id: None, + update_action: None, + }); } let enhanced_keys_supported = tui.enhanced_keys_supported(); diff --git a/codex-rs/tui/src/model_migration.rs b/codex-rs/tui/src/model_migration.rs index f89fbd1adb..1c9f480530 100644 --- a/codex-rs/tui/src/model_migration.rs +++ b/codex-rs/tui/src/model_migration.rs @@ -1,4 +1,6 @@ use codex_core::config::Config; +use codex_core::config::edit::ConfigEditsBuilder; +use codex_core::models_manager::manager::ModelsManager; use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; use codex_protocol::openai_models::ModelPreset; @@ -26,6 +28,11 @@ pub(crate) use prompt_ui::ModelMigrationOutcome; pub(crate) use prompt_ui::ModelMigrationScreen; pub(crate) use prompt_ui::migration_copy_for_models; +pub(crate) enum StartupModelMigrationAction { + Continue, + Exit, +} + /// Read the pending migration notice file, returning the notice if it should be shown. pub(crate) fn maybe_show_pending_model_migration_notice( config: &Config, @@ -79,6 +86,60 @@ pub(crate) fn maybe_show_pending_model_migration_notice( Some(notice) } +pub(crate) async fn maybe_run_startup_model_migration_prompt( + tui: &mut crate::tui::Tui, + config: &mut Config, + models_manager: &ModelsManager, +) -> Result { + let pending_model_migration_notice = maybe_show_pending_model_migration_notice(config); + + let Some(notice) = &pending_model_migration_notice else { + return Ok(StartupModelMigrationAction::Continue); + }; + + let outcome = run_startup_model_migration_prompt(tui, config, models_manager, notice).await?; + + match outcome { + ModelMigrationOutcome::Accepted => { + config.model = Some(notice.to_model.clone()); + config + .notices + .model_migrations + .insert(notice.from_model.clone(), notice.to_model.clone()); + + let edits = ConfigEditsBuilder::new(&config.codex_home) + .record_model_migration_seen(notice.from_model.as_str(), notice.to_model.as_str()) + .set_model(config.model.as_deref(), config.model_reasoning_effort); + if let Err(err) = edits.apply().await { + tracing::error!( + error = %err, + "failed to persist model migration prompt outcome" + ); + } + + Ok(StartupModelMigrationAction::Continue) + } + ModelMigrationOutcome::Rejected => { + config + .notices + .model_migrations + .insert(notice.from_model.clone(), notice.to_model.clone()); + + let edits = ConfigEditsBuilder::new(&config.codex_home) + .record_model_migration_seen(notice.from_model.as_str(), notice.to_model.as_str()); + if let Err(err) = edits.apply().await { + tracing::error!( + error = %err, + "failed to persist model migration prompt outcome" + ); + } + + Ok(StartupModelMigrationAction::Continue) + } + ModelMigrationOutcome::Exit => Ok(StartupModelMigrationAction::Exit), + } +} + /// Persist the migration notice for the next startup, replacing any existing scheduled notice. /// /// Scheduling is intentionally independent of session configuration: it uses the user's config diff --git a/codex-rs/tui2/src/app.rs b/codex-rs/tui2/src/app.rs index c24deb0d6a..6dbdd567d9 100644 --- a/codex-rs/tui2/src/app.rs +++ b/codex-rs/tui2/src/app.rs @@ -223,71 +223,27 @@ impl App { let app_event_tx = AppEventSender::new(app_event_tx); let mut config = config; - let pending_model_migration_notice = - crate::model_migration::maybe_show_pending_model_migration_notice(&config); let conversation_manager = Arc::new(ConversationManager::new( auth_manager.clone(), SessionSource::Cli, )); - if let Some(notice) = &pending_model_migration_notice { - let outcome = crate::model_migration::run_startup_model_migration_prompt( + if matches!( + crate::model_migration::maybe_run_startup_model_migration_prompt( tui, - &config, + &mut config, conversation_manager.get_models_manager().as_ref(), - notice, ) - .await?; - - match outcome { - crate::model_migration::ModelMigrationOutcome::Accepted => { - config.model = Some(notice.to_model.clone()); - config - .notices - .model_migrations - .insert(notice.from_model.clone(), notice.to_model.clone()); - - let edits = ConfigEditsBuilder::new(&config.codex_home) - .record_model_migration_seen( - notice.from_model.as_str(), - notice.to_model.as_str(), - ) - .set_model(config.model.as_deref(), config.model_reasoning_effort); - if let Err(err) = edits.apply().await { - tracing::error!( - error = %err, - "failed to persist model migration prompt outcome" - ); - } - } - crate::model_migration::ModelMigrationOutcome::Rejected => { - config - .notices - .model_migrations - .insert(notice.from_model.clone(), notice.to_model.clone()); - - let edits = ConfigEditsBuilder::new(&config.codex_home) - .record_model_migration_seen( - notice.from_model.as_str(), - notice.to_model.as_str(), - ); - if let Err(err) = edits.apply().await { - tracing::error!( - error = %err, - "failed to persist model migration prompt outcome" - ); - } - } - crate::model_migration::ModelMigrationOutcome::Exit => { - return Ok(AppExitInfo { - token_usage: TokenUsage::default(), - conversation_id: None, - update_action: None, - session_lines: Vec::new(), - }); - } - } + .await?, + crate::model_migration::StartupModelMigrationAction::Exit + ) { + return Ok(AppExitInfo { + token_usage: TokenUsage::default(), + conversation_id: None, + update_action: None, + session_lines: Vec::new(), + }); } let enhanced_keys_supported = tui.enhanced_keys_supported(); diff --git a/codex-rs/tui2/src/model_migration.rs b/codex-rs/tui2/src/model_migration.rs index 2812e8534d..ef6089cd14 100644 --- a/codex-rs/tui2/src/model_migration.rs +++ b/codex-rs/tui2/src/model_migration.rs @@ -1,4 +1,6 @@ use codex_core::config::Config; +use codex_core::config::edit::ConfigEditsBuilder; +use codex_core::models_manager::manager::ModelsManager; use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; use codex_protocol::openai_models::ModelPreset; @@ -26,6 +28,11 @@ pub(crate) use prompt_ui::ModelMigrationOutcome; pub(crate) use prompt_ui::ModelMigrationScreen; pub(crate) use prompt_ui::migration_copy_for_models; +pub(crate) enum StartupModelMigrationAction { + Continue, + Exit, +} + /// Read the pending migration notice file, returning the notice if it should be shown. pub(crate) fn maybe_show_pending_model_migration_notice( config: &Config, @@ -79,6 +86,60 @@ pub(crate) fn maybe_show_pending_model_migration_notice( Some(notice) } +pub(crate) async fn maybe_run_startup_model_migration_prompt( + tui: &mut crate::tui::Tui, + config: &mut Config, + models_manager: &ModelsManager, +) -> Result { + let pending_model_migration_notice = maybe_show_pending_model_migration_notice(config); + + let Some(notice) = &pending_model_migration_notice else { + return Ok(StartupModelMigrationAction::Continue); + }; + + let outcome = run_startup_model_migration_prompt(tui, config, models_manager, notice).await?; + + match outcome { + ModelMigrationOutcome::Accepted => { + config.model = Some(notice.to_model.clone()); + config + .notices + .model_migrations + .insert(notice.from_model.clone(), notice.to_model.clone()); + + let edits = ConfigEditsBuilder::new(&config.codex_home) + .record_model_migration_seen(notice.from_model.as_str(), notice.to_model.as_str()) + .set_model(config.model.as_deref(), config.model_reasoning_effort); + if let Err(err) = edits.apply().await { + tracing::error!( + error = %err, + "failed to persist model migration prompt outcome" + ); + } + + Ok(StartupModelMigrationAction::Continue) + } + ModelMigrationOutcome::Rejected => { + config + .notices + .model_migrations + .insert(notice.from_model.clone(), notice.to_model.clone()); + + let edits = ConfigEditsBuilder::new(&config.codex_home) + .record_model_migration_seen(notice.from_model.as_str(), notice.to_model.as_str()); + if let Err(err) = edits.apply().await { + tracing::error!( + error = %err, + "failed to persist model migration prompt outcome" + ); + } + + Ok(StartupModelMigrationAction::Continue) + } + ModelMigrationOutcome::Exit => Ok(StartupModelMigrationAction::Exit), + } +} + /// Persist the migration notice for the next startup, replacing any existing scheduled notice. /// /// Scheduling is intentionally independent of session configuration: it uses the user's config