diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index 3940adf40b..a24c09e36b 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -631,36 +631,6 @@ impl ConfigEditsBuilder { self } - pub fn set_pending_model_migration_notice(mut self, from_model: &str, to_model: &str) -> Self { - self.edits.push(ConfigEdit::SetPath { - segments: vec![ - Notice::TABLE_KEY.to_string(), - "pending_model_migration".to_string(), - "from_model".to_string(), - ], - value: value(from_model.to_string()), - }); - self.edits.push(ConfigEdit::SetPath { - segments: vec![ - Notice::TABLE_KEY.to_string(), - "pending_model_migration".to_string(), - "to_model".to_string(), - ], - value: value(to_model.to_string()), - }); - self - } - - pub fn clear_pending_model_migration_notice(mut self) -> Self { - self.edits.push(ConfigEdit::ClearPath { - segments: vec![ - Notice::TABLE_KEY.to_string(), - "pending_model_migration".to_string(), - ], - }); - self - } - pub fn set_windows_wsl_setup_acknowledged(mut self, acknowledged: bool) -> Self { self.edits .push(ConfigEdit::SetWindowsWslSetupAcknowledged(acknowledged)); diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 4dcf96c7a2..3aa72d5ce5 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -514,12 +514,6 @@ const fn default_true() -> bool { /// Settings for notices we display to users via the tui and app-server clients /// (primarily the Codex IDE extension). NOTE: these are different from /// notifications - notices are warnings, NUX screens, acknowledgements, etc. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PendingModelMigrationNotice { - pub from_model: String, - pub to_model: String, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] pub struct Notice { /// Tracks whether the user has acknowledged the full access warning prompt. @@ -536,8 +530,6 @@ pub struct Notice { /// Tracks acknowledged model migrations as old->new model slug mappings. #[serde(default)] pub model_migrations: BTreeMap, - /// Persisted "show on next run" model migration notice. - pub pending_model_migration: Option, } impl Notice { diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 4270fd30e2..d7be885ebd 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -217,9 +217,20 @@ impl App { } async fn maybe_emit_pending_model_migration_notice(&mut self, used_model: &str) { - let Some(pending) = self.config.notices.pending_model_migration.take() else { - return; - }; + let pending = + match crate::model_migration_notice::read_pending_model_migration_notice(&self.config) + .await + { + Ok(Some(pending)) => pending, + Ok(None) => return, + Err(err) => { + tracing::error!( + error = %err, + "failed to read pending model migration notice" + ); + return; + } + }; let should_show = pending.from_model == used_model; if should_show { @@ -237,20 +248,31 @@ impl App { .insert(pending.from_model.clone(), pending.to_model.clone()); } - let mut edits = ConfigEditsBuilder::new(&self.config.codex_home); if should_show { - edits = edits.record_model_migration_seen( - pending.from_model.as_str(), - pending.to_model.as_str(), - ); + if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home) + .record_model_migration_seen(pending.from_model.as_str(), pending.to_model.as_str()) + .apply() + .await + { + tracing::error!( + error = %err, + "failed to persist model migration notice acknowledgement" + ); + self.chat_widget.add_error_message(format!( + "Failed to persist model migration notice acknowledgement: {err}" + )); + } } - if let Err(err) = edits.clear_pending_model_migration_notice().apply().await { + + if let Err(err) = + crate::model_migration_notice::clear_pending_model_migration_notice(&self.config).await + { tracing::error!( error = %err, - "failed to clear pending model migration notice" + "failed to clear pending model migration notice file" ); self.chat_widget.add_error_message(format!( - "Failed to persist model migration notice state: {err}" + "Failed to clear pending model migration notice: {err}" )); } } @@ -285,10 +307,12 @@ impl App { return; } - if let Err(err) = ConfigEditsBuilder::new(&config.codex_home) - .set_pending_model_migration_notice(used_model.as_str(), target_model.as_str()) - .apply() - .await + if let Err(err) = crate::model_migration_notice::write_pending_model_migration_notice( + &config, + used_model.as_str(), + target_model.as_str(), + ) + .await { tracing::error!( error = %err, diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index ba10c147e4..42f3f4a07b 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -57,6 +57,7 @@ pub mod live_wrap; mod markdown; mod markdown_render; mod markdown_stream; +pub mod model_migration_notice; // Model migration prompt UI is no longer used in production; keep it for snapshot tests. #[cfg(test)] mod model_migration; diff --git a/codex-rs/tui2/src/app.rs b/codex-rs/tui2/src/app.rs index 69aef667f1..7f4fe4ea89 100644 --- a/codex-rs/tui2/src/app.rs +++ b/codex-rs/tui2/src/app.rs @@ -283,8 +283,20 @@ impl App { } async fn maybe_emit_pending_model_migration_notice(&mut self, used_model: &str) { - let Some(pending) = self.config.notices.pending_model_migration.take() else { - return; + let pending = match codex_tui::model_migration_notice::read_pending_model_migration_notice( + &self.config, + ) + .await + { + Ok(Some(pending)) => pending, + Ok(None) => return, + Err(err) => { + tracing::error!( + error = %err, + "failed to read pending model migration notice" + ); + return; + } }; let should_show = pending.from_model == used_model; @@ -303,20 +315,32 @@ impl App { .insert(pending.from_model.clone(), pending.to_model.clone()); } - let mut edits = ConfigEditsBuilder::new(&self.config.codex_home); if should_show { - edits = edits.record_model_migration_seen( - pending.from_model.as_str(), - pending.to_model.as_str(), - ); + if let Err(err) = ConfigEditsBuilder::new(&self.config.codex_home) + .record_model_migration_seen(pending.from_model.as_str(), pending.to_model.as_str()) + .apply() + .await + { + tracing::error!( + error = %err, + "failed to persist model migration notice acknowledgement" + ); + self.chat_widget.add_error_message(format!( + "Failed to persist model migration notice acknowledgement: {err}" + )); + } } - if let Err(err) = edits.clear_pending_model_migration_notice().apply().await { + + if let Err(err) = + codex_tui::model_migration_notice::clear_pending_model_migration_notice(&self.config) + .await + { tracing::error!( error = %err, - "failed to clear pending model migration notice" + "failed to clear pending model migration notice file" ); self.chat_widget.add_error_message(format!( - "Failed to persist model migration notice state: {err}" + "Failed to clear pending model migration notice: {err}" )); } } @@ -351,9 +375,12 @@ impl App { return; } - if let Err(err) = ConfigEditsBuilder::new(&config.codex_home) - .set_pending_model_migration_notice(used_model.as_str(), target_model.as_str()) - .apply() + if let Err(err) = + codex_tui::model_migration_notice::write_pending_model_migration_notice( + &config, + used_model.as_str(), + target_model.as_str(), + ) .await { tracing::error!(