diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs index 9271089c43..efb9a26c63 100644 --- a/codex-rs/tui/src/app/event_dispatch.rs +++ b/codex-rs/tui/src/app/event_dispatch.rs @@ -1303,8 +1303,10 @@ impl App { } AppEvent::PersistServiceTierSelection { service_tier } => { self.refresh_status_line(); - let profile = self.active_profile.as_deref(); self.config.service_tier = service_tier.clone(); + self.sync_active_thread_service_tier_to_cached_session() + .await; + let profile = self.active_profile.as_deref(); let edits = crate::config_update::build_service_tier_selection_edits( profile, service_tier.as_deref(), diff --git a/codex-rs/tui/src/app/thread_session_state.rs b/codex-rs/tui/src/app/thread_session_state.rs index 4037a085eb..6f2b3ec7f1 100644 --- a/codex-rs/tui/src/app/thread_session_state.rs +++ b/codex-rs/tui/src/app/thread_session_state.rs @@ -8,6 +8,30 @@ use codex_protocol::models::ActivePermissionProfile; use codex_protocol::models::PermissionProfile; impl App { + pub(super) async fn sync_active_thread_service_tier_to_cached_session(&mut self) { + let Some(active_thread_id) = self.active_thread_id else { + return; + }; + + let service_tier = self.chat_widget.current_service_tier().map(str::to_string); + let update_session = |session: &mut ThreadSessionState| { + session.service_tier = service_tier.clone(); + }; + + if self.primary_thread_id == Some(active_thread_id) + && let Some(session) = self.primary_session_configured.as_mut() + { + update_session(session); + } + + if let Some(channel) = self.thread_event_channels.get(&active_thread_id) { + let mut store = channel.store.lock().await; + if let Some(session) = store.session.as_mut() { + update_session(session); + } + } + } + pub(super) async fn sync_active_thread_permission_settings_to_cached_session(&mut self) { let Some(active_thread_id) = self.active_thread_id else { return; @@ -125,6 +149,7 @@ mod tests { use crate::test_support::test_path_buf; use codex_app_server_protocol::AskForApproval; use codex_config::types::ApprovalsReviewer; + use codex_protocol::config_types::ServiceTier; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; @@ -316,6 +341,50 @@ mod tests { assert_eq!(store_session, Some(expected_session)); } + #[tokio::test] + async fn service_tier_sync_updates_active_cached_session() { + let mut app = make_test_app().await; + let thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000406").expect("valid thread"); + let session = ThreadSessionState { + service_tier: Some(ServiceTier::Fast.request_value().to_string()), + ..test_thread_session(thread_id, test_path_buf("/tmp/main")) + }; + + app.primary_thread_id = Some(thread_id); + app.active_thread_id = Some(thread_id); + app.primary_session_configured = Some(session.clone()); + app.thread_event_channels.insert( + thread_id, + ThreadEventChannel::new_with_session(/*capacity*/ 4, session.clone(), Vec::new()), + ); + app.chat_widget.handle_thread_session(session); + app.chat_widget.set_service_tier(/*service_tier*/ None); + + app.sync_active_thread_service_tier_to_cached_session() + .await; + + let expected_session = ThreadSessionState { + service_tier: None, + ..test_thread_session(thread_id, test_path_buf("/tmp/main")) + }; + assert_eq!( + app.primary_session_configured, + Some(expected_session.clone()) + ); + + let store_session = app + .thread_event_channels + .get(&thread_id) + .expect("thread channel") + .store + .lock() + .await + .session + .clone(); + assert_eq!(store_session, Some(expected_session)); + } + #[tokio::test] async fn thread_read_fallback_uses_active_permission_settings() { let mut app = make_test_app().await;