Codex Handbook
tui/src/app/thread_settings.rs 209 lines
//! Thread settings sync between TUI-local state and app-server thread state.use super::App;use crate::app_command::AppCommand;use crate::app_server_session::AppServerSession;use crate::session_state::ThreadSessionState;use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;use codex_app_server_protocol::ThreadSettings;use codex_app_server_protocol::ThreadSettingsUpdateParams;use codex_protocol::ThreadId;use codex_protocol::config_types::ModeKind;use codex_protocol::models::PermissionProfile;impl App {    pub(super) async fn sync_active_thread_model_setting(        &mut self,        app_server: &mut AppServerSession,        model: String,    ) {        let Some(params) = self.active_thread_model_setting_update_params(model) else {            return;        };        self.send_thread_settings_update(app_server, params).await;    }    pub(super) fn active_thread_model_setting_update_params(        &self,        model: String,    ) -> Option<ThreadSettingsUpdateParams> {        let thread_id = self.active_thread_id?;        Some(ThreadSettingsUpdateParams {            thread_id: thread_id.to_string(),            model: Some(model),            collaboration_mode: Some(self.chat_widget.effective_collaboration_mode()),            ..ThreadSettingsUpdateParams::default()        })    }    pub(super) async fn sync_active_thread_reasoning_setting(        &mut self,        app_server: &mut AppServerSession,        effort: Option<codex_protocol::openai_models::ReasoningEffort>,    ) {        let Some(params) = self.active_thread_reasoning_setting_update_params(effort) else {            return;        };        self.send_thread_settings_update(app_server, params).await;    }    pub(super) fn active_thread_reasoning_setting_update_params(        &self,        effort: Option<codex_protocol::openai_models::ReasoningEffort>,    ) -> Option<ThreadSettingsUpdateParams> {        let thread_id = self.active_thread_id?;        Some(ThreadSettingsUpdateParams {            thread_id: thread_id.to_string(),            effort,            collaboration_mode: Some(self.chat_widget.current_collaboration_mode().clone()),            ..ThreadSettingsUpdateParams::default()        })    }    pub(super) async fn sync_active_thread_plan_mode_reasoning_setting(        &mut self,        app_server: &mut AppServerSession,    ) {        let Some(thread_id) = self.active_thread_id else {            return;        };        let params = ThreadSettingsUpdateParams {            thread_id: thread_id.to_string(),            collaboration_mode: Some(self.chat_widget.effective_collaboration_mode()),            ..ThreadSettingsUpdateParams::default()        };        self.send_thread_settings_update(app_server, params).await;    }    pub(super) async fn sync_active_thread_personality_setting(        &mut self,        app_server: &mut AppServerSession,        personality: codex_protocol::config_types::Personality,    ) {        let Some(thread_id) = self.active_thread_id else {            return;        };        let params = ThreadSettingsUpdateParams {            thread_id: thread_id.to_string(),            personality: Some(personality),            ..ThreadSettingsUpdateParams::default()        };        self.send_thread_settings_update(app_server, params).await;    }    pub(super) async fn sync_override_turn_context_settings(        &mut self,        app_server: &mut AppServerSession,        thread_id: ThreadId,        op: &AppCommand,    ) {        let AppCommand::OverrideTurnContext {            cwd,            approval_policy,            approvals_reviewer,            permission_profile: _,            active_permission_profile,            windows_sandbox_level: _,            model,            effort,            summary,            service_tier,            collaboration_mode,            personality,        } = op        else {            return;        };        let params = ThreadSettingsUpdateParams {            thread_id: thread_id.to_string(),            cwd: cwd.clone(),            approval_policy: *approval_policy,            approvals_reviewer: approvals_reviewer.map(AppServerApprovalsReviewer::from),            permissions: active_permission_profile                .as_ref()                .map(|profile| profile.id.clone()),            model: model.clone(),            effort: effort.clone().unwrap_or_default(),            summary: *summary,            service_tier: service_tier.clone(),            collaboration_mode: collaboration_mode.clone(),            personality: *personality,            ..ThreadSettingsUpdateParams::default()        };        self.send_thread_settings_update(app_server, params).await;    }    pub(super) async fn apply_thread_settings_to_cached_session(        &mut self,        thread_id: ThreadId,        settings: &ThreadSettings,    ) {        if self.primary_thread_id == Some(thread_id)            && let Some(session) = self.primary_session_configured.as_mut()        {            apply_thread_settings_to_session(session, settings);        }        if let Some(channel) = self.thread_event_channels.get(&thread_id) {            let mut store = channel.store.lock().await;            if let Some(session) = store.session.as_mut() {                apply_thread_settings_to_session(session, settings);            }        }    }    async fn send_thread_settings_update(        &mut self,        app_server: &mut AppServerSession,        params: ThreadSettingsUpdateParams,    ) {        if !thread_settings_update_has_changes(&params) {            return;        }        if let Err(err) = app_server.thread_settings_update(params).await {            tracing::warn!("failed to update app-server thread settings from TUI: {err}");            self.chat_widget                .add_error_message(format!("Failed to update thread settings: {err}"));        }    }}fn apply_thread_settings_to_session(session: &mut ThreadSessionState, settings: &ThreadSettings) {    if settings.collaboration_mode.mode == ModeKind::Default {        session.model = settings.model.clone();        session.reasoning_effort = settings.effort.clone();    }    session.model_provider_id = settings.model_provider.clone();    session.service_tier = settings.service_tier.clone();    session.approval_policy = settings.approval_policy;    session.approvals_reviewer = settings.approvals_reviewer.to_core();    session.permission_profile = PermissionProfile::from_legacy_sandbox_policy_for_cwd(        &settings.sandbox_policy.to_core(),        settings.cwd.as_path(),    );    session.active_permission_profile = settings.active_permission_profile.clone().map(Into::into);    session.set_cwd_retargeting_implicit_runtime_workspace_root(settings.cwd.clone());    session.personality = settings.personality;    let mut collaboration_mode = settings.collaboration_mode.clone();    collaboration_mode        .settings        .model        .clone_from(&settings.model);    collaboration_mode.settings.reasoning_effort = settings.effort.clone();    session.collaboration_mode = Some(Box::new(collaboration_mode));}fn thread_settings_update_has_changes(params: &ThreadSettingsUpdateParams) -> bool {    params.cwd.is_some()        || params.approval_policy.is_some()        || params.approvals_reviewer.is_some()        || params.sandbox_policy.is_some()        || params.permissions.is_some()        || params.model.is_some()        || params.service_tier.is_some()        || params.effort.is_some()        || params.summary.is_some()        || params.collaboration_mode.is_some()        || params.personality.is_some()}