diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 7eb2b1159f..14748fa4b9 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -208,6 +208,7 @@ use codex_core::find_thread_path_by_id_str; use codex_core::git_info::git_diff_to_remote; use codex_core::mcp::collect_mcp_snapshot; use codex_core::mcp::group_tools_by_server; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::parse_cursor; use codex_core::read_head_for_summary; use codex_core::read_session_meta_line; @@ -449,15 +450,13 @@ impl CodexMessageProcessor { fn normalize_turn_start_collaboration_mode( &self, mut collaboration_mode: CollaborationMode, - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, ) -> CollaborationMode { if collaboration_mode.settings.developer_instructions.is_none() && let Some(instructions) = self .thread_manager .get_models_manager() - .list_collaboration_modes_for_default_mode_request_user_input( - default_mode_request_user_input, - ) + .list_collaboration_modes_for_config(collaboration_modes_config) .into_iter() .find(|preset| preset.mode == Some(collaboration_mode.mode)) .and_then(|preset| preset.developer_instructions.flatten()) @@ -5354,9 +5353,10 @@ impl CodexMessageProcessor { } }; - let default_mode_request_user_input = thread.enabled(Feature::DefaultModeRequestUserInput); + let collaboration_modes_config = + CollaborationModesConfig::new(thread.enabled(Feature::DefaultModeRequestUserInput)); let collaboration_mode = params.collaboration_mode.map(|mode| { - self.normalize_turn_start_collaboration_mode(mode, default_mode_request_user_input) + self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config) }); // Map v2 input items to core input items. diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 39a8719dc0..59c9690f50 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -46,6 +46,7 @@ use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; use codex_core::default_client::set_default_client_residency_requirement; use codex_core::default_client::set_default_originator; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_feedback::CodexFeedback; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; @@ -177,9 +178,11 @@ impl MessageProcessor { auth_manager.clone(), SessionSource::VSCode, config.model_catalog.clone(), - config - .features - .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + CollaborationModesConfig::new( + config + .features + .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + ), )); let cloud_requirements = Arc::new(RwLock::new(cloud_requirements)); let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs { diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 2a8a956ade..e229d37b5b 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -29,6 +29,8 @@ use crate::features::FEATURES; use crate::features::Feature; use crate::features::Features; use crate::features::maybe_push_unstable_features_warning; +#[cfg(test)] +use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig; use crate::models_manager::manager::ModelsManager; use crate::parse_command::parse_command; use crate::parse_turn_item; @@ -8059,7 +8061,7 @@ mod tests { config.codex_home.clone(), auth_manager.clone(), None, - false, + CollaborationModesConfig::default(), )); let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref()); let model_info = @@ -8135,7 +8137,7 @@ mod tests { config.codex_home.clone(), auth_manager.clone(), None, - false, + CollaborationModesConfig::default(), )); let agent_control = AgentControl::default(); let exec_policy = ExecPolicyManager::default(); @@ -8291,7 +8293,7 @@ mod tests { config.codex_home.clone(), auth_manager.clone(), None, - false, + CollaborationModesConfig::default(), )); let agent_control = AgentControl::default(); let exec_policy = ExecPolicyManager::default(); diff --git a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs index 3c8999d595..7b0a9b22d4 100644 --- a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs +++ b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs @@ -10,13 +10,23 @@ const KNOWN_MODE_NAMES_PLACEHOLDER: &str = "{{KNOWN_MODE_NAMES}}"; const REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER: &str = "{{REQUEST_USER_INPUT_AVAILABILITY}}"; const QUESTION_STRATEGY_PLACEHOLDER: &str = "{{QUESTION_STRATEGY}}"; +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct CollaborationModesConfig { + pub default_mode_request_user_input: bool, +} + +impl CollaborationModesConfig { + pub const fn new(default_mode_request_user_input: bool) -> Self { + Self { + default_mode_request_user_input, + } + } +} + pub(crate) fn builtin_collaboration_mode_presets( - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, ) -> Vec { - vec![ - plan_preset(), - default_preset(default_mode_request_user_input), - ] + vec![plan_preset(), default_preset(collaboration_modes_config)] } fn plan_preset() -> CollaborationModeMask { @@ -29,23 +39,24 @@ fn plan_preset() -> CollaborationModeMask { } } -fn default_preset(default_mode_request_user_input: bool) -> CollaborationModeMask { +fn default_preset(collaboration_modes_config: CollaborationModesConfig) -> CollaborationModeMask { CollaborationModeMask { name: ModeKind::Default.display_name().to_string(), mode: Some(ModeKind::Default), model: None, reasoning_effort: None, - developer_instructions: Some(Some(default_mode_instructions( - default_mode_request_user_input, - ))), + developer_instructions: Some(Some(default_mode_instructions(collaboration_modes_config))), } } -fn default_mode_instructions(default_mode_request_user_input: bool) -> String { +fn default_mode_instructions(collaboration_modes_config: CollaborationModesConfig) -> String { let known_mode_names = format_mode_names(&TUI_VISIBLE_COLLABORATION_MODES); - let request_user_input_availability = - request_user_input_availability_message(ModeKind::Default, default_mode_request_user_input); - let question_strategy = question_strategy_message(default_mode_request_user_input); + let request_user_input_availability = request_user_input_availability_message( + ModeKind::Default, + collaboration_modes_config.default_mode_request_user_input, + ); + let question_strategy = + question_strategy_message(collaboration_modes_config.default_mode_request_user_input); COLLABORATION_MODE_DEFAULT .replace(KNOWN_MODE_NAMES_PLACEHOLDER, &known_mode_names) .replace( @@ -97,7 +108,10 @@ mod tests { #[test] fn preset_names_use_mode_display_names() { assert_eq!(plan_preset().name, ModeKind::Plan.display_name()); - assert_eq!(default_preset(false).name, ModeKind::Default.display_name()); + assert_eq!( + default_preset(CollaborationModesConfig::default()).name, + ModeKind::Default.display_name() + ); assert_eq!( plan_preset().reasoning_effort, Some(Some(ReasoningEffort::Medium)) @@ -106,7 +120,7 @@ mod tests { #[test] fn default_mode_instructions_replace_mode_names_placeholder() { - let default_instructions = default_preset(true) + let default_instructions = default_preset(CollaborationModesConfig::new(true)) .developer_instructions .expect("default preset should include instructions") .expect("default instructions should be set"); @@ -127,7 +141,7 @@ mod tests { #[test] fn default_mode_instructions_use_plain_text_questions_when_feature_disabled() { - let default_instructions = default_preset(false) + let default_instructions = default_preset(CollaborationModesConfig::default()) .developer_instructions .expect("default preset should include instructions") .expect("default instructions should be set"); diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index 3a297b32d3..bb3157619f 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -8,6 +8,7 @@ use crate::default_client::build_reqwest_client; use crate::error::CodexErr; use crate::error::Result as CoreResult; use crate::model_provider_info::ModelProviderInfo; +use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig; use crate::models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets; use crate::models_manager::model_info; use codex_api::ModelsClient; @@ -46,7 +47,7 @@ pub enum RefreshStrategy { pub struct ModelsManager { remote_models: RwLock>, has_custom_model_catalog: bool, - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, auth_manager: Arc, etag: RwLock>, cache_manager: ModelsCacheManager, @@ -63,7 +64,7 @@ impl ModelsManager { codex_home: PathBuf, auth_manager: Arc, model_catalog: Option, - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, ) -> Self { let cache_path = codex_home.join(MODEL_CACHE_FILE); let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL); @@ -77,7 +78,7 @@ impl ModelsManager { Self { remote_models: RwLock::new(remote_models), has_custom_model_catalog, - default_mode_request_user_input, + collaboration_modes_config, auth_manager, etag: RwLock::new(None), cache_manager, @@ -100,16 +101,14 @@ impl ModelsManager { /// /// Returns a static set of presets seeded with the configured model. pub fn list_collaboration_modes(&self) -> Vec { - self.list_collaboration_modes_for_default_mode_request_user_input( - self.default_mode_request_user_input, - ) + self.list_collaboration_modes_for_config(self.collaboration_modes_config) } - pub fn list_collaboration_modes_for_default_mode_request_user_input( + pub fn list_collaboration_modes_for_config( &self, - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, ) -> Vec { - builtin_collaboration_mode_presets(default_mode_request_user_input) + builtin_collaboration_mode_presets(collaboration_modes_config) } /// Attempt to list models without blocking, using the current cached state. @@ -377,7 +376,7 @@ impl ModelsManager { .unwrap_or_else(|err| panic!("failed to load bundled models.json: {err}")), ), has_custom_model_catalog: false, - default_mode_request_user_input: false, + collaboration_modes_config: CollaborationModesConfig::default(), auth_manager, etag: RwLock::new(None), cache_manager, @@ -504,8 +503,12 @@ mod tests { .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let manager = - ModelsManager::new(codex_home.path().to_path_buf(), auth_manager, None, false); + let manager = ModelsManager::new( + codex_home.path().to_path_buf(), + auth_manager, + None, + CollaborationModesConfig::default(), + ); let known_slug = manager .get_remote_models() .await @@ -542,7 +545,7 @@ mod tests { Some(ModelsResponse { models: vec![remote_model("gpt-overlay", "Overlay", 0)], }), - false, + CollaborationModesConfig::default(), ); let model_info = manager @@ -566,8 +569,12 @@ mod tests { .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let manager = - ModelsManager::new(codex_home.path().to_path_buf(), auth_manager, None, false); + let manager = ModelsManager::new( + codex_home.path().to_path_buf(), + auth_manager, + None, + CollaborationModesConfig::default(), + ); let known_slug = manager .get_remote_models() .await @@ -593,8 +600,12 @@ mod tests { .expect("load default test config"); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let manager = - ModelsManager::new(codex_home.path().to_path_buf(), auth_manager, None, false); + let manager = ModelsManager::new( + codex_home.path().to_path_buf(), + auth_manager, + None, + CollaborationModesConfig::default(), + ); let known_slug = manager .get_remote_models() .await diff --git a/codex-rs/core/src/test_support.rs b/codex-rs/core/src/test_support.rs index 3084d92fa8..12ba7cda82 100644 --- a/codex-rs/core/src/test_support.rs +++ b/codex-rs/core/src/test_support.rs @@ -85,5 +85,7 @@ pub fn all_model_presets() -> &'static Vec { } pub fn builtin_collaboration_mode_presets() -> Vec { - collaboration_mode_presets::builtin_collaboration_mode_presets(false) + collaboration_mode_presets::builtin_collaboration_mode_presets( + collaboration_mode_presets::CollaborationModesConfig::default(), + ) } diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 8db3bd371f..dbddc622a0 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -11,6 +11,7 @@ use crate::error::CodexErr; use crate::error::Result as CodexResult; use crate::file_watcher::FileWatcher; use crate::file_watcher::FileWatcherEvent; +use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig; use crate::models_manager::manager::ModelsManager; use crate::protocol::Event; use crate::protocol::EventMsg; @@ -143,7 +144,7 @@ impl ThreadManager { auth_manager: Arc, session_source: SessionSource, model_catalog: Option, - default_mode_request_user_input: bool, + collaboration_modes_config: CollaborationModesConfig, ) -> Self { let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY); let skills_manager = Arc::new(SkillsManager::new(codex_home.clone())); @@ -156,7 +157,7 @@ impl ThreadManager { codex_home, auth_manager.clone(), model_catalog, - default_mode_request_user_input, + collaboration_modes_config, )), skills_manager, file_watcher, diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 8dcabc428e..f61a3a33f2 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -12,6 +12,7 @@ use codex_core::built_in_model_providers; use codex_core::default_client::originator; use codex_core::error::CodexErr; use codex_core::features::Feature; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_otel::OtelManager; use codex_otel::TelemetryAuthMode; use codex_protocol::ThreadId; @@ -584,9 +585,11 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { auth_manager, SessionSource::Exec, config.model_catalog.clone(), - config - .features - .enabled(Feature::DefaultModeRequestUserInput), + CollaborationModesConfig::new( + config + .features + .enabled(Feature::DefaultModeRequestUserInput), + ), ); let NewThread { thread: codex, .. } = thread_manager .start_thread(config) diff --git a/codex-rs/core/tests/suite/model_info_overrides.rs b/codex-rs/core/tests/suite/model_info_overrides.rs index 4fb12f18be..ee4afc36bf 100644 --- a/codex-rs/core/tests/suite/model_info_overrides.rs +++ b/codex-rs/core/tests/suite/model_info_overrides.rs @@ -1,4 +1,5 @@ use codex_core::CodexAuth; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::models_manager::manager::ModelsManager; use codex_protocol::openai_models::TruncationPolicyConfig; use core_test_support::load_default_config_for_test; @@ -12,7 +13,12 @@ async fn offline_model_info_without_tool_output_override() { let auth_manager = codex_core::test_support::auth_manager_from_auth( CodexAuth::create_dummy_chatgpt_auth_for_testing(), ); - let manager = ModelsManager::new(config.codex_home.clone(), auth_manager, None, false); + let manager = ModelsManager::new( + config.codex_home.clone(), + auth_manager, + None, + CollaborationModesConfig::default(), + ); let model_info = manager.get_model_info("gpt-5.1", &config).await; @@ -30,7 +36,12 @@ async fn offline_model_info_with_tool_output_override() { let auth_manager = codex_core::test_support::auth_manager_from_auth( CodexAuth::create_dummy_chatgpt_auth_for_testing(), ); - let manager = ModelsManager::new(config.codex_home.clone(), auth_manager, None, false); + let manager = ModelsManager::new( + config.codex_home.clone(), + auth_manager, + None, + CollaborationModesConfig::default(), + ); let model_info = manager.get_model_info("gpt-5.1-codex", &config).await; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index be76728348..f99e8d2d14 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -31,6 +31,7 @@ use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::format_config_error_with_source; use codex_core::format_exec_policy_error_with_source; use codex_core::git_info::get_git_repo_root; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::models_manager::manager::RefreshStrategy; use codex_protocol::approvals::ElicitationAction; use codex_protocol::config_types::SandboxMode; @@ -396,9 +397,11 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any auth_manager.clone(), SessionSource::Exec, config.model_catalog.clone(), - config - .features - .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + CollaborationModesConfig::new( + config + .features + .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + ), )); let default_model = thread_manager .get_models_manager() diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 40794d2ffc..624deb3ff6 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -6,6 +6,7 @@ use codex_core::ThreadManager; use codex_core::config::Config; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::Submission; @@ -62,9 +63,11 @@ impl MessageProcessor { auth_manager, SessionSource::Mcp, config.model_catalog.clone(), - config - .features - .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + CollaborationModesConfig::new( + config + .features + .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + ), )); Self { outgoing, diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index bd136d4183..32ddce6b12 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -47,6 +47,7 @@ use codex_core::config::edit::ConfigEdit; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::features::Feature; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::models_manager::manager::RefreshStrategy; use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG; use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG; @@ -1247,9 +1248,11 @@ impl App { auth_manager.clone(), SessionSource::Cli, config.model_catalog.clone(), - config - .features - .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + CollaborationModesConfig::new( + config + .features + .enabled(codex_core::features::Feature::DefaultModeRequestUserInput), + ), )); let mut model = thread_manager .get_models_manager() diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 7e98836b39..8f86662dd6 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -24,6 +24,7 @@ use codex_core::config::ConstraintError; use codex_core::config::types::WindowsSandboxModeToml; use codex_core::config_loader::RequirementSource; use codex_core::features::Feature; +use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig; use codex_core::models_manager::manager::ModelsManager; use codex_core::skills::model::SkillMetadata; use codex_core::terminal::TerminalName; @@ -1637,7 +1638,7 @@ async fn make_chatwidget_manual( codex_home, auth_manager.clone(), None, - false, + CollaborationModesConfig::default(), )); let reasoning_effort = None; let base_mode = CollaborationMode { @@ -1764,7 +1765,7 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) { chat.config.codex_home.clone(), chat.auth_manager.clone(), None, - false, + CollaborationModesConfig::default(), )); }