This commit is contained in:
Eric Traut
2026-03-14 10:19:57 -06:00
parent 70eddad6b0
commit 709916ad10
6 changed files with 1817 additions and 505 deletions
+388 -346
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+76 -73
View File
@@ -67,7 +67,6 @@ use codex_core::git_info::current_branch_name;
use codex_core::git_info::get_git_repo_root;
use codex_core::git_info::local_git_branches;
use codex_core::mcp::McpManager;
use codex_core::models_manager::manager::ModelsManager;
use codex_core::plugins::PluginsManager;
use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
use codex_core::skills::model::SkillMetadata;
@@ -274,7 +273,7 @@ use self::interrupts::InterruptManager;
mod agent;
use self::agent::spawn_agent;
use self::agent::spawn_agent_from_existing;
pub(crate) use self::agent::spawn_op_forwarder;
pub(crate) use self::agent::spawn_app_event_forwarder;
mod session_header;
use self::session_header::SessionHeader;
mod skills;
@@ -476,7 +475,8 @@ pub(crate) struct ChatWidgetInit {
pub(crate) initial_user_message: Option<UserMessage>,
pub(crate) enhanced_keys_supported: bool,
pub(crate) auth_manager: Arc<AuthManager>,
pub(crate) models_manager: Arc<ModelsManager>,
pub(crate) available_models: Vec<ModelPreset>,
pub(crate) available_collaboration_modes: Vec<CollaborationModeMask>,
pub(crate) feedback: codex_feedback::CodexFeedback,
pub(crate) is_first_run: bool,
pub(crate) feedback_audience: FeedbackAudience,
@@ -654,7 +654,8 @@ pub(crate) struct ChatWidget {
/// The currently active collaboration mask, if any.
active_collaboration_mask: Option<CollaborationModeMask>,
auth_manager: Arc<AuthManager>,
models_manager: Arc<ModelsManager>,
available_models: Vec<ModelPreset>,
available_collaboration_modes: Vec<CollaborationModeMask>,
session_telemetry: SessionTelemetry,
session_header: SessionHeader,
initial_user_message: Option<UserMessage>,
@@ -1813,7 +1814,8 @@ impl ChatWidget {
}
fn open_plan_implementation_prompt(&mut self) {
let default_mask = collaboration_modes::default_mode_mask(self.models_manager.as_ref());
let default_mask =
collaboration_modes::default_mode_mask(&self.available_collaboration_modes);
let (implement_actions, implement_disabled_reason) = match default_mask {
Some(mask) => {
let user_text = PLAN_IMPLEMENTATION_CODING_MESSAGE.to_string();
@@ -3519,7 +3521,8 @@ impl ChatWidget {
initial_user_message,
enhanced_keys_supported,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
feedback,
is_first_run,
feedback_audience,
@@ -3540,8 +3543,11 @@ impl ChatWidget {
let model_for_header = model
.clone()
.unwrap_or_else(|| DEFAULT_MODEL_DISPLAY_NAME.to_string());
let active_collaboration_mask =
Self::initial_collaboration_mask(&config, models_manager.as_ref(), model_override);
let active_collaboration_mask = Self::initial_collaboration_mask(
&config,
&available_collaboration_modes,
model_override,
);
let header_model = active_collaboration_mask
.as_ref()
.and_then(|mask| mask.model.clone())
@@ -3584,7 +3590,8 @@ impl ChatWidget {
current_collaboration_mode,
active_collaboration_mask,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
session_telemetry,
session_header: SessionHeader::new(header_model),
initial_user_message,
@@ -3706,7 +3713,8 @@ impl ChatWidget {
initial_user_message,
enhanced_keys_supported,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
feedback,
is_first_run,
feedback_audience,
@@ -3726,8 +3734,11 @@ impl ChatWidget {
let model_for_header = model
.clone()
.unwrap_or_else(|| DEFAULT_MODEL_DISPLAY_NAME.to_string());
let active_collaboration_mask =
Self::initial_collaboration_mask(&config, models_manager.as_ref(), model_override);
let active_collaboration_mask = Self::initial_collaboration_mask(
&config,
&available_collaboration_modes,
model_override,
);
let header_model = active_collaboration_mask
.as_ref()
.and_then(|mask| mask.model.clone())
@@ -3770,7 +3781,8 @@ impl ChatWidget {
current_collaboration_mode,
active_collaboration_mask,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
session_telemetry,
session_header: SessionHeader::new(header_model),
initial_user_message,
@@ -3884,7 +3896,8 @@ impl ChatWidget {
initial_user_message,
enhanced_keys_supported,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
feedback,
is_first_run: _,
feedback_audience,
@@ -3902,8 +3915,11 @@ impl ChatWidget {
let header_model = model
.clone()
.unwrap_or_else(|| session_configured.model.clone());
let active_collaboration_mask =
Self::initial_collaboration_mask(&config, models_manager.as_ref(), model_override);
let active_collaboration_mask = Self::initial_collaboration_mask(
&config,
&available_collaboration_modes,
model_override,
);
let header_model = active_collaboration_mask
.as_ref()
.and_then(|mask| mask.model.clone())
@@ -3948,7 +3964,8 @@ impl ChatWidget {
current_collaboration_mode,
active_collaboration_mask,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
session_telemetry,
session_header: SessionHeader::new(header_model),
initial_user_message,
@@ -4393,7 +4410,9 @@ impl ChatWidget {
);
return;
}
if let Some(mask) = collaboration_modes::plan_mask(self.models_manager.as_ref()) {
if let Some(mask) =
collaboration_modes::plan_mask(&self.available_collaboration_modes)
{
self.set_collaboration_mask(mask);
} else {
self.add_info_message("Plan mode unavailable right now.".to_string(), None);
@@ -6106,8 +6125,7 @@ impl ChatWidget {
}
fn lower_cost_preset(&self) -> Option<ModelPreset> {
let models = self.models_manager.try_list_models().ok()?;
models
self.available_models
.iter()
.find(|preset| preset.show_in_picker && preset.model == NUDGE_MODEL_SLUG)
.cloned()
@@ -6225,16 +6243,11 @@ impl ChatWidget {
return;
}
let presets: Vec<ModelPreset> = match self.models_manager.try_list_models() {
Ok(models) => models,
Err(_) => {
self.add_info_message(
"Models are being updated; please try /model again in a moment.".to_string(),
None,
);
return;
}
};
let presets = self.available_models.clone();
if presets.is_empty() {
self.add_info_message("No models are available right now.".to_string(), None);
return;
}
self.open_model_popup_with_presets(presets);
}
@@ -6645,7 +6658,7 @@ impl ChatWidget {
}
pub(crate) fn open_collaboration_modes_popup(&mut self) {
let presets = collaboration_modes::presets_for_tui(self.models_manager.as_ref());
let presets = collaboration_modes::presets_for_tui(&self.available_collaboration_modes);
if presets.is_empty() {
self.add_info_message(
"No collaboration modes are available right now.".to_string(),
@@ -6659,7 +6672,7 @@ impl ChatWidget {
.as_ref()
.and_then(|mask| mask.mode)
.or_else(|| {
collaboration_modes::default_mask(self.models_manager.as_ref())
collaboration_modes::default_mask(&self.available_collaboration_modes)
.and_then(|mask| mask.mode)
});
let items: Vec<SelectionItem> = presets
@@ -6748,25 +6761,25 @@ impl ChatWidget {
None => "the selected reasoning".to_string(),
};
let plan_only_description = format!("Always use {reasoning_phrase} in Plan mode.");
let plan_reasoning_source = if let Some(plan_override) =
self.config.plan_mode_reasoning_effort
{
format!(
"user-chosen Plan override ({})",
Self::reasoning_effort_label(plan_override).to_lowercase()
)
} else if let Some(plan_mask) = collaboration_modes::plan_mask(self.models_manager.as_ref())
{
match plan_mask.reasoning_effort.flatten() {
Some(plan_effort) => format!(
"built-in Plan default ({})",
Self::reasoning_effort_label(plan_effort).to_lowercase()
),
None => "built-in Plan default (no reasoning)".to_string(),
}
} else {
"built-in Plan default".to_string()
};
let plan_reasoning_source =
if let Some(plan_override) = self.config.plan_mode_reasoning_effort {
format!(
"user-chosen Plan override ({})",
Self::reasoning_effort_label(plan_override).to_lowercase()
)
} else if let Some(plan_mask) =
collaboration_modes::plan_mask(&self.available_collaboration_modes)
{
match plan_mask.reasoning_effort.flatten() {
Some(plan_effort) => format!(
"built-in Plan default ({})",
Self::reasoning_effort_label(plan_effort).to_lowercase()
),
None => "built-in Plan default (no reasoning)".to_string(),
}
} else {
"built-in Plan default".to_string()
};
let all_modes_description = format!(
"Set the global default reasoning level and the Plan mode override. This replaces the current {plan_reasoning_source}."
);
@@ -7901,7 +7914,7 @@ impl ChatWidget {
if let Some(effort) = effort {
mask.reasoning_effort = Some(Some(effort));
} else if let Some(plan_mask) =
collaboration_modes::plan_mask(self.models_manager.as_ref())
collaboration_modes::plan_mask(&self.available_collaboration_modes)
{
mask.reasoning_effort = plan_mask.reasoning_effort;
}
@@ -8042,15 +8055,10 @@ impl ChatWidget {
fn current_model_supports_personality(&self) -> bool {
let model = self.current_model();
self.models_manager
.try_list_models()
.ok()
.and_then(|models| {
models
.into_iter()
.find(|preset| preset.model == model)
.map(|preset| preset.supports_personality)
})
self.available_models
.iter()
.find(|preset| preset.model == model)
.map(|preset| preset.supports_personality)
.unwrap_or(false)
}
@@ -8060,15 +8068,10 @@ impl ChatWidget {
/// failures do not hard-block user input in the UI.
fn current_model_supports_images(&self) -> bool {
let model = self.current_model();
self.models_manager
.try_list_models()
.ok()
.and_then(|models| {
models
.into_iter()
.find(|preset| preset.model == model)
.map(|preset| preset.input_modalities.contains(&InputModality::Image))
})
self.available_models
.iter()
.find(|preset| preset.model == model)
.map(|preset| preset.input_modalities.contains(&InputModality::Image))
.unwrap_or(true)
}
@@ -8108,10 +8111,10 @@ impl ChatWidget {
fn initial_collaboration_mask(
_config: &Config,
models_manager: &ModelsManager,
available_collaboration_modes: &[CollaborationModeMask],
model_override: Option<&str>,
) -> Option<CollaborationModeMask> {
let mut mask = collaboration_modes::default_mask(models_manager)?;
let mut mask = collaboration_modes::default_mask(available_collaboration_modes)?;
if let Some(model_override) = model_override {
mask.model = Some(model_override.to_string());
}
@@ -8211,7 +8214,7 @@ impl ChatWidget {
}
if let Some(next_mask) = collaboration_modes::next_mask(
self.models_manager.as_ref(),
&self.available_collaboration_modes,
self.active_collaboration_mask.as_ref(),
) {
self.set_collaboration_mask(next_mask);
+16
View File
@@ -147,3 +147,19 @@ pub(crate) fn spawn_op_forwarder(thread: std::sync::Arc<CodexThread>) -> Unbound
codex_op_tx
}
/// Spawn an op forwarder that routes all widget submissions back onto the app event bus.
///
/// The app layer owns app-server request translation during the migration, so the chat widget
/// should no longer need a direct thread handle for normal runtime operation.
pub(crate) fn spawn_app_event_forwarder(app_event_tx: AppEventSender) -> UnboundedSender<Op> {
let (codex_op_tx, mut codex_op_rx) = unbounded_channel::<Op>();
tokio::spawn(async move {
while let Some(op) = codex_op_rx.recv().await {
app_event_tx.send(AppEvent::CodexOp(op));
}
});
codex_op_tx
}
+73 -40
View File
@@ -1310,7 +1310,7 @@ async fn interrupted_turn_restore_keeps_active_mode_for_resubmission() {
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
let expected_mode = plan_mask
.mode
@@ -1761,7 +1761,13 @@ async fn helpers_are_available_and_do_not_panic() {
initial_user_message: None,
enhanced_keys_supported: false,
auth_manager,
models_manager: thread_manager.get_models_manager(),
available_models: thread_manager
.get_models_manager()
.try_list_models()
.expect("test models should be available"),
available_collaboration_modes: thread_manager
.get_models_manager()
.list_collaboration_modes(),
feedback: codex_feedback::CodexFeedback::new(),
is_first_run: true,
feedback_audience: FeedbackAudience::External,
@@ -1791,6 +1797,17 @@ fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry {
)
}
fn available_chatwidget_catalog(
models_manager: &ModelsManager,
) -> (Vec<ModelPreset>, Vec<CollaborationModeMask>) {
(
models_manager
.try_list_models()
.expect("test models should be available"),
models_manager.list_collaboration_modes(),
)
}
// --- Helpers for tests that need direct construction and event draining ---
async fn make_chatwidget_manual(
model_override: Option<&str>,
@@ -1831,6 +1848,8 @@ async fn make_chatwidget_manual(
None,
CollaborationModesConfig::default(),
));
let (available_models, available_collaboration_modes) =
available_chatwidget_catalog(models_manager.as_ref());
let reasoning_effort = None;
let base_mode = CollaborationMode {
mode: ModeKind::Default,
@@ -1841,7 +1860,8 @@ async fn make_chatwidget_manual(
},
};
let current_collaboration_mode = base_mode;
let active_collaboration_mask = collaboration_modes::default_mask(models_manager.as_ref());
let active_collaboration_mask =
collaboration_modes::default_mask(&available_collaboration_modes);
let mut widget = ChatWidget {
app_event_tx,
codex_op_tx: op_tx,
@@ -1852,7 +1872,8 @@ async fn make_chatwidget_manual(
current_collaboration_mode,
active_collaboration_mask,
auth_manager,
models_manager,
available_models,
available_collaboration_modes,
session_telemetry,
session_header: SessionHeader::new(resolved_model.clone()),
initial_user_message: None,
@@ -1970,12 +1991,16 @@ pub(crate) fn set_chatgpt_auth(chat: &mut ChatWidget) {
chat.auth_manager = codex_core::test_support::auth_manager_from_auth(
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
);
chat.models_manager = Arc::new(ModelsManager::new(
let models_manager = Arc::new(ModelsManager::new(
chat.config.codex_home.clone(),
chat.auth_manager.clone(),
None,
CollaborationModesConfig::default(),
));
let (available_models, available_collaboration_modes) =
available_chatwidget_catalog(models_manager.as_ref());
chat.available_models = available_models;
chat.available_collaboration_modes = available_collaboration_modes;
}
#[tokio::test]
@@ -2477,7 +2502,7 @@ async fn submit_user_message_with_mode_sets_coding_collaboration_mode() {
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let default_mode = collaboration_modes::default_mode_mask(chat.models_manager.as_ref())
let default_mode = collaboration_modes::default_mode_mask(&chat.available_collaboration_modes)
.expect("expected default collaboration mode");
chat.submit_user_message_with_mode("Implement the plan.".to_string(), default_mode);
@@ -2502,7 +2527,7 @@ async fn reasoning_selection_in_plan_mode_opens_scope_prompt_event() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
let _ = drain_insert_history(&mut rx);
@@ -2529,7 +2554,7 @@ async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_sc
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
let _ = drain_insert_history(&mut rx);
@@ -2564,7 +2589,7 @@ async fn reasoning_selection_in_plan_mode_matching_plan_effort_but_different_glo
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
let _ = drain_insert_history(&mut rx);
@@ -2597,7 +2622,7 @@ async fn plan_mode_reasoning_override_is_marked_current_in_reasoning_popup() {
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
chat.set_plan_mode_reasoning_effort(Some(ReasoningEffortConfig::Low));
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
@@ -2617,7 +2642,7 @@ async fn reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_promp
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
let _ = drain_insert_history(&mut rx);
@@ -2883,12 +2908,12 @@ async fn submit_user_message_with_mode_errors_when_mode_changes_during_running_t
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.on_task_started();
let default_mode = collaboration_modes::default_mask(chat.models_manager.as_ref())
let default_mode = collaboration_modes::default_mask(&chat.available_collaboration_modes)
.expect("expected default collaboration mode");
chat.submit_user_message_with_mode("Implement the plan.".to_string(), default_mode);
@@ -2912,7 +2937,7 @@ async fn submit_user_message_with_mode_allows_same_mode_during_running_turn() {
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask.clone());
chat.on_task_started();
@@ -2943,11 +2968,11 @@ async fn submit_user_message_with_mode_submits_when_plan_stream_is_not_active()
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
let default_mode = collaboration_modes::default_mask(chat.models_manager.as_ref())
let default_mode = collaboration_modes::default_mask(&chat.available_collaboration_modes)
.expect("expected default collaboration mode");
let expected_mode = default_mode
.mode
@@ -2973,7 +2998,7 @@ async fn plan_implementation_popup_skips_replayed_turn_complete() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -2994,7 +3019,7 @@ async fn plan_implementation_popup_shows_once_when_replay_precedes_live_turn_com
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -3072,7 +3097,7 @@ async fn plan_implementation_popup_skips_when_messages_queued() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.bottom_pane.set_task_running(true);
@@ -3092,7 +3117,7 @@ async fn plan_implementation_popup_skips_without_proposed_plan() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -3118,7 +3143,7 @@ async fn plan_implementation_popup_shows_after_proposed_plan_output() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -3139,7 +3164,7 @@ async fn plan_implementation_popup_skips_when_steer_follows_proposed_plan() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.thread_id = Some(ThreadId::new());
@@ -3181,7 +3206,7 @@ async fn plan_implementation_popup_shows_after_new_plan_follows_steer() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.thread_id = Some(ThreadId::new());
@@ -3230,7 +3255,7 @@ async fn plan_implementation_popup_skips_when_rate_limit_prompt_pending() {
);
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -3634,11 +3659,7 @@ fn active_blob(chat: &ChatWidget) -> String {
}
fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset {
let models = chat
.models_manager
.try_list_models()
.expect("models lock available");
models
chat.available_models
.iter()
.find(|&preset| preset.model == model)
.cloned()
@@ -3902,7 +3923,7 @@ async fn plan_completion_restores_status_indicator_after_streaming_plan_output()
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -3997,7 +4018,7 @@ async fn steer_enter_queues_while_plan_stream_is_active() {
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.on_task_started();
@@ -4712,7 +4733,7 @@ async fn enter_submits_when_plan_stream_is_not_active() {
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
chat.on_task_started();
@@ -5417,7 +5438,7 @@ async fn mode_switch_surfaces_model_change_notification_when_effective_model_cha
let default_model = chat.current_model().to_string();
let mut plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mode");
plan_mask.model = Some("gpt-5.1-codex-mini".to_string());
chat.set_collaboration_mask(plan_mask);
@@ -5432,7 +5453,7 @@ async fn mode_switch_surfaces_model_change_notification_when_effective_model_cha
"expected Plan-mode model switch notice, got: {plan_messages:?}"
);
let default_mask = collaboration_modes::default_mask(chat.models_manager.as_ref())
let default_mask = collaboration_modes::default_mask(&chat.available_collaboration_modes)
.expect("expected default collaboration mode");
chat.set_collaboration_mask(default_mask);
@@ -5455,7 +5476,7 @@ async fn mode_switch_surfaces_reasoning_change_notification_when_model_stays_sam
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
let plan_mask = collaboration_modes::plan_mask(chat.models_manager.as_ref())
let plan_mask = collaboration_modes::plan_mask(&chat.available_collaboration_modes)
.expect("expected plan collaboration mode");
chat.set_collaboration_mask(plan_mask);
@@ -5621,7 +5642,13 @@ async fn collaboration_modes_defaults_to_code_on_startup() {
initial_user_message: None,
enhanced_keys_supported: false,
auth_manager,
models_manager: thread_manager.get_models_manager(),
available_models: thread_manager
.get_models_manager()
.try_list_models()
.expect("test models should be available"),
available_collaboration_modes: thread_manager
.get_models_manager()
.list_collaboration_modes(),
feedback: codex_feedback::CodexFeedback::new(),
is_first_run: true,
feedback_audience: FeedbackAudience::External,
@@ -5671,7 +5698,13 @@ async fn experimental_mode_plan_is_ignored_on_startup() {
initial_user_message: None,
enhanced_keys_supported: false,
auth_manager,
models_manager: thread_manager.get_models_manager(),
available_models: thread_manager
.get_models_manager()
.try_list_models()
.expect("test models should be available"),
available_collaboration_modes: thread_manager
.get_models_manager()
.list_collaboration_modes(),
feedback: codex_feedback::CodexFeedback::new(),
is_first_run: true,
feedback_audience: FeedbackAudience::External,
@@ -5691,7 +5724,7 @@ async fn set_model_updates_active_collaboration_mask() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -5706,7 +5739,7 @@ async fn set_reasoning_effort_updates_active_collaboration_mask() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
@@ -5725,7 +5758,7 @@ async fn set_reasoning_effort_does_not_override_active_plan_override() {
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.set_plan_mode_reasoning_effort(Some(ReasoningEffortConfig::High));
let plan_mask =
collaboration_modes::mask_for_kind(chat.models_manager.as_ref(), ModeKind::Plan)
collaboration_modes::mask_for_kind(&chat.available_collaboration_modes, ModeKind::Plan)
.expect("expected plan collaboration mask");
chat.set_collaboration_mask(plan_mask);
+25 -18
View File
@@ -1,21 +1,24 @@
use codex_core::models_manager::manager::ModelsManager;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
fn filtered_presets(models_manager: &ModelsManager) -> Vec<CollaborationModeMask> {
models_manager
.list_collaboration_modes()
.into_iter()
.filter(|mask| mask.mode.is_some_and(ModeKind::is_tui_visible))
fn filtered_presets(collaboration_modes: &[CollaborationModeMask]) -> Vec<CollaborationModeMask> {
collaboration_modes
.iter()
.filter(|&mask| mask.mode.is_some_and(ModeKind::is_tui_visible))
.cloned()
.collect()
}
pub(crate) fn presets_for_tui(models_manager: &ModelsManager) -> Vec<CollaborationModeMask> {
filtered_presets(models_manager)
pub(crate) fn presets_for_tui(
collaboration_modes: &[CollaborationModeMask],
) -> Vec<CollaborationModeMask> {
filtered_presets(collaboration_modes)
}
pub(crate) fn default_mask(models_manager: &ModelsManager) -> Option<CollaborationModeMask> {
let presets = filtered_presets(models_manager);
pub(crate) fn default_mask(
collaboration_modes: &[CollaborationModeMask],
) -> Option<CollaborationModeMask> {
let presets = filtered_presets(collaboration_modes);
presets
.iter()
.find(|mask| mask.mode == Some(ModeKind::Default))
@@ -24,23 +27,23 @@ pub(crate) fn default_mask(models_manager: &ModelsManager) -> Option<Collaborati
}
pub(crate) fn mask_for_kind(
models_manager: &ModelsManager,
collaboration_modes: &[CollaborationModeMask],
kind: ModeKind,
) -> Option<CollaborationModeMask> {
if !kind.is_tui_visible() {
return None;
}
filtered_presets(models_manager)
filtered_presets(collaboration_modes)
.into_iter()
.find(|mask| mask.mode == Some(kind))
}
/// Cycle to the next collaboration mode preset in list order.
pub(crate) fn next_mask(
models_manager: &ModelsManager,
collaboration_modes: &[CollaborationModeMask],
current: Option<&CollaborationModeMask>,
) -> Option<CollaborationModeMask> {
let presets = filtered_presets(models_manager);
let presets = filtered_presets(collaboration_modes);
if presets.is_empty() {
return None;
}
@@ -52,10 +55,14 @@ pub(crate) fn next_mask(
presets.get(next_index).cloned()
}
pub(crate) fn default_mode_mask(models_manager: &ModelsManager) -> Option<CollaborationModeMask> {
mask_for_kind(models_manager, ModeKind::Default)
pub(crate) fn default_mode_mask(
collaboration_modes: &[CollaborationModeMask],
) -> Option<CollaborationModeMask> {
mask_for_kind(collaboration_modes, ModeKind::Default)
}
pub(crate) fn plan_mask(models_manager: &ModelsManager) -> Option<CollaborationModeMask> {
mask_for_kind(models_manager, ModeKind::Plan)
pub(crate) fn plan_mask(
collaboration_modes: &[CollaborationModeMask],
) -> Option<CollaborationModeMask> {
mask_for_kind(collaboration_modes, ModeKind::Plan)
}