change collaboration mode to struct (#9793)

Shouldn't cause behavioral change
This commit is contained in:
Ahmed Ibrahim
2026-01-23 17:00:23 -08:00
committed by GitHub
parent 1167465bf6
commit 69cfc73dc6
16 changed files with 291 additions and 210 deletions

View File

@@ -910,7 +910,7 @@ impl ChatWidget {
if !self.queued_user_messages.is_empty() {
return;
}
if !matches!(self.stored_collaboration_mode, CollaborationMode::Plan(_)) {
if self.stored_collaboration_mode.mode != ModeKind::Plan {
return;
}
let has_message = last_agent_message.is_some_and(|message| !message.trim().is_empty());
@@ -1943,7 +1943,10 @@ impl ChatWidget {
config.experimental_mode,
)
} else {
CollaborationMode::Custom(fallback_custom)
CollaborationMode {
mode: ModeKind::Custom,
settings: fallback_custom,
}
};
let active_cell = Some(Self::placeholder_session_header_cell(&config));
@@ -2061,7 +2064,10 @@ impl ChatWidget {
config.experimental_mode,
)
} else {
CollaborationMode::Custom(fallback_custom)
CollaborationMode {
mode: ModeKind::Custom,
settings: fallback_custom,
}
};
let active_cell = Some(Self::placeholder_session_header_cell(&config));
@@ -2182,7 +2188,10 @@ impl ChatWidget {
config.experimental_mode,
)
} else {
CollaborationMode::Custom(fallback_custom)
CollaborationMode {
mode: ModeKind::Custom,
settings: fallback_custom,
}
};
let mut widget = Self {
@@ -3552,11 +3561,11 @@ impl ChatWidget {
let items: Vec<SelectionItem> = presets
.into_iter()
.map(|preset| {
let name = match preset {
CollaborationMode::Plan(_) => "Plan",
CollaborationMode::PairProgramming(_) => "Pair Programming",
CollaborationMode::Execute(_) => "Execute",
CollaborationMode::Custom(_) => "Custom",
let name = match preset.mode {
ModeKind::Plan => "Plan",
ModeKind::PairProgramming => "Pair Programming",
ModeKind::Execute => "Execute",
ModeKind::Custom => "Custom",
};
let is_current =
collaboration_modes::same_variant(&self.stored_collaboration_mode, &preset);
@@ -4538,12 +4547,7 @@ impl ChatWidget {
}
if feature == Feature::CollaborationModes {
self.bottom_pane.set_collaboration_modes_enabled(enabled);
let settings = match &self.stored_collaboration_mode {
CollaborationMode::Plan(settings)
| CollaborationMode::PairProgramming(settings)
| CollaborationMode::Execute(settings)
| CollaborationMode::Custom(settings) => settings.clone(),
};
let settings = self.stored_collaboration_mode.settings.clone();
let fallback_custom = settings.clone();
self.stored_collaboration_mode = if enabled {
initial_collaboration_mode(
@@ -4552,7 +4556,10 @@ impl ChatWidget {
self.config.experimental_mode,
)
} else {
CollaborationMode::Custom(settings)
CollaborationMode {
mode: ModeKind::Custom,
settings,
}
};
self.update_collaboration_mode_indicator();
}
@@ -4632,11 +4639,11 @@ impl ChatWidget {
if !self.collaboration_modes_enabled() {
return None;
}
match &self.stored_collaboration_mode {
CollaborationMode::Plan(_) => Some("Plan"),
CollaborationMode::PairProgramming(_) => Some("Pair Programming"),
CollaborationMode::Execute(_) => Some("Execute"),
CollaborationMode::Custom(_) => None,
match self.stored_collaboration_mode.mode {
ModeKind::Plan => Some("Plan"),
ModeKind::PairProgramming => Some("Pair Programming"),
ModeKind::Execute => Some("Execute"),
ModeKind::Custom => None,
}
}
@@ -4644,13 +4651,11 @@ impl ChatWidget {
if !self.collaboration_modes_enabled() {
return None;
}
match &self.stored_collaboration_mode {
CollaborationMode::Plan(_) => Some(CollaborationModeIndicator::Plan),
CollaborationMode::PairProgramming(_) => {
Some(CollaborationModeIndicator::PairProgramming)
}
CollaborationMode::Execute(_) => Some(CollaborationModeIndicator::Execute),
CollaborationMode::Custom(_) => None,
match self.stored_collaboration_mode.mode {
ModeKind::Plan => Some(CollaborationModeIndicator::Plan),
ModeKind::PairProgramming => Some(CollaborationModeIndicator::PairProgramming),
ModeKind::Execute => Some(CollaborationModeIndicator::Execute),
ModeKind::Custom => None,
}
}
@@ -4681,7 +4686,8 @@ impl ChatWidget {
if !self.collaboration_modes_enabled() {
return;
}
let old_model = self.stored_collaboration_mode.model().to_string();
let mode = mode.with_updates(Some(old_model), None, None);
self.stored_collaboration_mode = mode;
self.update_collaboration_mode_indicator();
self.request_redraw();
@@ -5304,15 +5310,20 @@ fn initial_collaboration_mode(
) -> CollaborationMode {
if let Some(kind) = desired_mode {
if kind == ModeKind::Custom {
return CollaborationMode::Custom(fallback_custom);
return CollaborationMode {
mode: ModeKind::Custom,
settings: fallback_custom,
};
}
if let Some(mode) = collaboration_modes::mode_for_kind(models_manager, kind) {
return mode;
}
}
collaboration_modes::default_mode(models_manager)
.unwrap_or(CollaborationMode::Custom(fallback_custom))
collaboration_modes::default_mode(models_manager).unwrap_or(CollaborationMode {
mode: ModeKind::Custom,
settings: fallback_custom,
})
}
async fn fetch_rate_limits(base_url: String, auth: CodexAuth) -> Option<RateLimitSnapshot> {

View File

@@ -64,6 +64,8 @@ use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffortPreset;
use codex_protocol::parse_command::ParsedCommand;
@@ -779,18 +781,24 @@ async fn make_chatwidget_manual(
let reasoning_effort = None;
let stored_collaboration_mode = if collaboration_modes_enabled {
collaboration_modes::default_mode(models_manager.as_ref()).unwrap_or_else(|| {
CollaborationMode::Custom(Settings {
CollaborationMode {
mode: ModeKind::Custom,
settings: Settings {
model: resolved_model.clone(),
reasoning_effort,
developer_instructions: None,
},
}
})
} else {
CollaborationMode {
mode: ModeKind::Custom,
settings: Settings {
model: resolved_model.clone(),
reasoning_effort,
developer_instructions: None,
})
})
} else {
CollaborationMode::Custom(Settings {
model: resolved_model.clone(),
reasoning_effort,
developer_instructions: None,
})
},
}
};
let widget = ChatWidget {
app_event_tx,
@@ -1206,7 +1214,7 @@ async fn plan_implementation_popup_yes_emits_submit_message_event() {
panic!("expected SubmitUserMessageWithMode, got {event:?}");
};
assert_eq!(text, PLAN_IMPLEMENTATION_EXECUTE_MESSAGE);
assert!(matches!(collaboration_mode, CollaborationMode::Execute(_)));
assert_eq!(collaboration_mode.mode, ModeKind::Execute);
}
#[tokio::test]
@@ -1221,7 +1229,11 @@ async fn submit_user_message_with_mode_sets_execute_collaboration_mode() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::Execute(_)),
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::Execute,
..
}),
personality: None,
..
} => {}
@@ -1235,11 +1247,14 @@ async fn submit_user_message_with_mode_sets_execute_collaboration_mode() {
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);
chat.stored_collaboration_mode = CollaborationMode::Plan(Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
});
chat.stored_collaboration_mode = CollaborationMode {
mode: ModeKind::Plan,
settings: Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
},
};
chat.replay_initial_messages(vec![EventMsg::TurnComplete(TurnCompleteEvent {
last_agent_message: Some("Plan details".to_string()),
@@ -1256,11 +1271,14 @@ async fn plan_implementation_popup_skips_replayed_turn_complete() {
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);
chat.stored_collaboration_mode = CollaborationMode::Plan(Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
});
chat.stored_collaboration_mode = CollaborationMode {
mode: ModeKind::Plan,
settings: Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
},
};
chat.bottom_pane.set_task_running(true);
chat.queue_user_message("Queued message".into());
@@ -1277,11 +1295,14 @@ async fn plan_implementation_popup_skips_when_messages_queued() {
async fn plan_implementation_popup_shows_on_plan_update_without_message() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.stored_collaboration_mode = CollaborationMode::Plan(Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
});
chat.stored_collaboration_mode = CollaborationMode {
mode: ModeKind::Plan,
settings: Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
},
};
chat.on_task_started();
chat.on_plan_update(UpdatePlanArgs {
@@ -1306,11 +1327,14 @@ async fn plan_implementation_popup_skips_when_rate_limit_prompt_pending() {
chat.auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.stored_collaboration_mode = CollaborationMode::Plan(Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
});
chat.stored_collaboration_mode = CollaborationMode {
mode: ModeKind::Plan,
settings: Settings {
model: chat.current_model().to_string(),
reasoning_effort: None,
developer_instructions: None,
},
};
chat.on_task_started();
chat.on_plan_update(UpdatePlanArgs {
@@ -2205,16 +2229,10 @@ async fn collab_mode_shift_tab_cycles_only_when_enabled_and_idle() {
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::Execute(_)
));
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Execute);
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::Plan(_)
));
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Plan);
chat.on_task_started();
let before = chat.stored_collaboration_mode.clone();
@@ -2247,7 +2265,11 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
..
}),
personality: None,
..
} => {}
@@ -2261,7 +2283,11 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
..
}),
personality: None,
..
} => {}
@@ -2282,7 +2308,11 @@ async fn collab_mode_defaults_to_pair_programming_when_enabled() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
..
}),
personality: None,
..
} => {}
@@ -2296,10 +2326,10 @@ async fn collab_mode_defaults_to_pair_programming_when_enabled() {
async fn collab_mode_enabling_sets_pair_programming_default() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::PairProgramming(_)
));
assert_eq!(
chat.stored_collaboration_mode.mode,
ModeKind::PairProgramming
);
}
#[tokio::test]

View File

@@ -3,19 +3,14 @@ use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
fn mode_kind(mode: &CollaborationMode) -> ModeKind {
match mode {
CollaborationMode::Plan(_) => ModeKind::Plan,
CollaborationMode::PairProgramming(_) => ModeKind::PairProgramming,
CollaborationMode::Execute(_) => ModeKind::Execute,
CollaborationMode::Custom(_) => ModeKind::Custom,
}
mode.mode
}
pub(crate) fn default_mode(models_manager: &ModelsManager) -> Option<CollaborationMode> {
let presets = models_manager.list_collaboration_modes();
presets
.iter()
.find(|preset| matches!(preset, CollaborationMode::PairProgramming(_)))
.find(|preset| preset.mode == ModeKind::PairProgramming)
.cloned()
.or_else(|| presets.into_iter().next())
}