Reduce the surface of collaboration modes (#20149)

Collaboration modes were slightly invasive both into ThreadManager
construction and ModelProvider
This commit is contained in:
pakrym-oai
2026-04-29 17:22:41 -07:00
committed by GitHub
Unverified
parent c8abcbf925
commit fedcefe9da
41 changed files with 121 additions and 346 deletions
-2
View File
@@ -2977,9 +2977,7 @@ dependencies = [
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
"codex-login",
"codex-models-manager",
"codex-protocol",
"codex-shell-command",
"codex-utils-absolute-path",
+1 -1
View File
@@ -194,7 +194,7 @@ Example with notification opt-out:
- `modelProvider/capabilities/read` — read provider-level capabilities for the currently configured model provider.
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `experimentalFeature/enablement/set` — patch the in-memory process-wide runtime feature enablement for the currently supported feature keys (`apps`, `memories`, `plugins`, `remote_control`, `tool_search`, `tool_suggest`, `tool_call_mcp_elicitation`). For each feature, precedence is: cloud requirements > --enable <feature_name> > config.toml > experimentalFeature/enablement/set (new) > code default.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). Built-in presets do not select a model; the Plan preset selects medium reasoning effort. This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
- `hooks/list` — list discovered hooks for one or more `cwd` values.
- `marketplace/add` — add a remote plugin marketplace from an HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand, then persist it into the user marketplace config. Returns the installed root path plus whether the marketplace was already present.
@@ -323,7 +323,6 @@ use codex_mcp::resolve_oauth_scopes;
use codex_memories_write::clear_memory_roots_contents;
use codex_model_provider::ProviderAccountError;
use codex_model_provider::create_model_provider;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
use codex_protocol::ThreadId;
use codex_protocol::config_types::CollaborationMode;
@@ -903,15 +902,13 @@ impl CodexMessageProcessor {
fn normalize_turn_start_collaboration_mode(
&self,
mut collaboration_mode: CollaborationMode,
collaboration_modes_config: CollaborationModesConfig,
) -> CollaborationMode {
if collaboration_mode.settings.developer_instructions.is_none()
&& let Some(instructions) =
builtin_collaboration_mode_presets(collaboration_modes_config)
.into_iter()
.find(|preset| preset.mode == Some(collaboration_mode.mode))
.and_then(|preset| preset.developer_instructions.flatten())
.filter(|instructions| !instructions.is_empty())
&& let Some(instructions) = builtin_collaboration_mode_presets()
.into_iter()
.find(|preset| preset.mode == Some(collaboration_mode.mode))
.and_then(|preset| preset.developer_instructions.flatten())
.filter(|instructions| !instructions.is_empty())
{
collaboration_mode.settings.developer_instructions = Some(instructions);
}
@@ -6566,13 +6563,9 @@ impl CodexMessageProcessor {
self.track_error_response(&request_id, error, /*error_type*/ None);
})?;
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: thread
.enabled(Feature::DefaultModeRequestUserInput),
};
let collaboration_mode = params.collaboration_mode.map(|mode| {
self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config)
});
let collaboration_mode = params
.collaboration_mode
.map(|mode| self.normalize_turn_start_collaboration_mode(mode));
let environments: Option<Vec<TurnEnvironmentSelection>> =
params.environments.map(|environments| {
environments
@@ -79,7 +79,6 @@ use codex_login::default_client::get_codex_user_agent;
use codex_login::default_client::set_default_client_residency_requirement;
use codex_login::default_client::set_default_originator;
use codex_model_provider::create_model_provider;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::W3cTraceContext;
@@ -290,11 +289,6 @@ impl MessageProcessor {
config.as_ref(),
auth_manager.clone(),
session_source,
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
environment_manager,
Some(analytics_events_client.clone()),
));
@@ -1,8 +1,7 @@
//! Validates that the collaboration mode list endpoint returns the expected default presets.
//!
//! The test drives the app server through the MCP harness and asserts that the list response
//! includes the plan and default modes with their default model and reasoning effort
//! settings, which keeps the API contract visible in one place.
//! includes the plan and default modes, which keeps the API contract visible in one place.
#![allow(clippy::unwrap_used)]
@@ -969,7 +969,7 @@ async fn turn_start_accepts_collaboration_mode_override_v2() -> Result<()> {
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::from([(Feature::DefaultModeRequestUserInput, true)]),
&BTreeMap::default(),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
@@ -1029,13 +1029,15 @@ async fn turn_start_accepts_collaboration_mode_override_v2() -> Result<()> {
let payload = request.body_json();
assert_eq!(payload["model"].as_str(), Some("mock-model-collab"));
let payload_text = payload.to_string();
assert!(payload_text.contains("The `request_user_input` tool is available in Default mode."));
assert!(payload_text.contains(
"Use the `request_user_input` tool only when it is listed in the available tools"
));
Ok(())
}
#[tokio::test]
async fn turn_start_uses_thread_feature_overrides_for_collaboration_mode_instructions_v2()
async fn turn_start_uses_thread_feature_overrides_for_request_user_input_tool_description_v2()
-> Result<()> {
skip_if_no_network!(Ok(()));
@@ -1114,7 +1116,7 @@ async fn turn_start_uses_thread_feature_overrides_for_collaboration_mode_instruc
let request = response_mock.single_request();
let payload_text = request.body_json().to_string();
assert!(payload_text.contains("The `request_user_input` tool is available in Default mode."));
assert!(payload_text.contains("This tool is only available in Default or Plan mode."));
Ok(())
}
+1 -3
View File
@@ -62,7 +62,6 @@ use codex_features::is_known_feature_key;
use codex_login::AuthManager;
use codex_memories_write::clear_memory_roots_contents;
use codex_models_manager::bundled_models_response;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::user_input::UserInput;
@@ -1411,8 +1410,7 @@ async fn run_debug_models_command(
let config = Config::load_with_cli_overrides(cli_overrides).await?;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
let models_manager =
build_models_manager(&config, auth_manager, CollaborationModesConfig::default());
let models_manager = build_models_manager(&config, auth_manager);
models_manager
.raw_model_catalog(RefreshStrategy::OnlineIfUncached)
.await
@@ -6,6 +6,6 @@ Your active mode changes only when new developer instructions with a different `
## request_user_input availability
{{REQUEST_USER_INPUT_AVAILABILITY}}
Use the `request_user_input` tool only when it is listed in the available tools for this turn.
{{ASKING_QUESTIONS_GUIDANCE}}
In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.
-1
View File
@@ -46,7 +46,6 @@ pub use codex_login::AuthManager;
pub use codex_login::default_client::set_default_originator;
pub use codex_model_provider_info::OPENAI_PROVIDER_ID;
pub use codex_model_provider_info::built_in_model_providers;
pub use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
pub use codex_models_manager::manager::RefreshStrategy;
pub use codex_models_manager::manager::SharedModelsManager;
pub use codex_protocol::ThreadId;
-7
View File
@@ -4,9 +4,7 @@ use std::sync::Arc;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentManagerArgs;
use codex_exec_server::ExecServerRuntimePaths;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
@@ -41,11 +39,6 @@ pub async fn build_prompt_input(
&config,
Arc::clone(&auth_manager),
SessionSource::Exec,
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::new(local_runtime_paths)).await),
/*analytics_events_client*/ None,
);
+2 -8
View File
@@ -110,11 +110,7 @@ pub fn models_manager_with_provider(
provider: ModelProviderInfo,
) -> SharedModelsManager {
let provider = create_model_provider(provider, Some(auth_manager));
provider.models_manager(
codex_home,
/*config_model_catalog*/ None,
Default::default(),
)
provider.models_manager(codex_home, /*config_model_catalog*/ None)
}
pub fn get_model_offline(model: Option<&str>) -> String {
@@ -130,7 +126,5 @@ pub fn all_model_presets() -> &'static Vec<ModelPreset> {
}
pub fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
collaboration_mode_presets::builtin_collaboration_mode_presets(
collaboration_mode_presets::CollaborationModesConfig::default(),
)
collaboration_mode_presets::builtin_collaboration_mode_presets()
}
+2 -14
View File
@@ -28,7 +28,6 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider::create_model_provider;
use codex_model_provider_info::ModelProviderInfo;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::RefreshStrategy;
use codex_models_manager::manager::SharedModelsManager;
use codex_protocol::ThreadId;
@@ -255,13 +254,11 @@ pub(crate) struct ThreadManagerState {
pub fn build_models_manager(
config: &Config,
auth_manager: Arc<AuthManager>,
collaboration_modes_config: CollaborationModesConfig,
) -> SharedModelsManager {
let provider = create_model_provider(config.model_provider.clone(), Some(auth_manager));
provider.models_manager(
config.codex_home.to_path_buf(),
config.model_catalog.clone(),
collaboration_modes_config,
)
}
@@ -281,7 +278,6 @@ impl ThreadManager {
config: &Config,
auth_manager: Arc<AuthManager>,
session_source: SessionSource,
collaboration_modes_config: CollaborationModesConfig,
environment_manager: Arc<EnvironmentManager>,
analytics_events_client: Option<AnalyticsEventsClient>,
) -> Self {
@@ -303,11 +299,7 @@ impl ThreadManager {
state: Arc::new(ThreadManagerState {
threads: Arc::new(RwLock::new(HashMap::new())),
thread_created_tx,
models_manager: build_models_manager(
config,
auth_manager.clone(),
collaboration_modes_config,
),
models_manager: build_models_manager(config, auth_manager.clone()),
environment_manager,
skills_manager,
plugins_manager,
@@ -378,11 +370,7 @@ impl ThreadManager {
threads: Arc::new(RwLock::new(HashMap::new())),
thread_created_tx,
models_manager: create_model_provider(provider, Some(auth_manager.clone()))
.models_manager(
codex_home,
/*config_model_catalog*/ None,
CollaborationModesConfig::default(),
),
.models_manager(codex_home, /*config_model_catalog*/ None),
environment_manager,
skills_manager,
plugins_manager,
@@ -6,7 +6,6 @@ use crate::session::tests::make_session_and_context;
use crate::tasks::InterruptedTurnHistoryMarker;
use crate::tasks::interrupted_turn_history_marker;
use codex_features::Feature;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ReasoningItemReasoningSummary;
@@ -392,7 +391,6 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
&config,
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -499,7 +497,6 @@ async fn new_uses_active_provider_for_model_refresh() {
&config,
auth_manager,
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -710,7 +707,6 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
&config,
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -814,7 +810,6 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
&config,
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -907,7 +902,6 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
&config,
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -1046,7 +1040,6 @@ async fn resumed_thread_activates_paused_goal_and_continues_on_request() -> anyh
&config,
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -5,13 +5,14 @@ use crate::tools::context::ToolPayload;
use crate::tools::handlers::parse_arguments;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
use codex_protocol::config_types::ModeKind;
use codex_protocol::request_user_input::RequestUserInputArgs;
use codex_tools::REQUEST_USER_INPUT_TOOL_NAME;
use codex_tools::normalize_request_user_input_args;
use codex_tools::request_user_input_unavailable_message;
pub struct RequestUserInputHandler {
pub default_mode_request_user_input: bool,
pub available_modes: Vec<ModeKind>,
}
impl ToolHandler for RequestUserInputHandler {
@@ -46,9 +47,7 @@ impl ToolHandler for RequestUserInputHandler {
}
let mode = session.collaboration_mode().await.mode;
if let Some(message) =
request_user_input_unavailable_message(mode, self.default_mode_request_user_input)
{
if let Some(message) = request_user_input_unavailable_message(mode, &self.available_modes) {
return Err(FunctionCallError::RespondToModel(message));
}
@@ -23,7 +23,7 @@ async fn multi_agent_v2_request_user_input_rejects_subagent_threads() {
});
let result = RequestUserInputHandler {
default_mode_request_user_input: true,
available_modes: Vec::new(),
}
.handle(ToolInvocation {
session: Arc::new(session),
+1 -1
View File
@@ -166,7 +166,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
let shell_command_handler = Arc::new(ShellCommandHandler::from(config.shell_command_backend));
let request_permissions_handler = Arc::new(RequestPermissionsHandler);
let request_user_input_handler = Arc::new(RequestUserInputHandler {
default_mode_request_user_input: config.default_mode_request_user_input,
available_modes: config.request_user_input_available_modes.clone(),
});
let deferred_dynamic_tools = dynamic_tools
.iter()
-2
View File
@@ -27,7 +27,6 @@ use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::built_in_model_providers;
use codex_models_manager::bundled_models_response;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ModelsResponse;
@@ -429,7 +428,6 @@ impl TestCodexBuilder {
&config,
codex_core::test_support::auth_manager_from_auth(auth.clone()),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::clone(&environment_manager),
/*analytics_events_client*/ None,
)
-6
View File
@@ -14,7 +14,6 @@ use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_model_provider_info::built_in_model_providers;
use codex_models_manager::bundled_models_response;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_otel::SessionTelemetry;
use codex_otel::TelemetryAuthMode;
use codex_protocol::ThreadId;
@@ -1106,11 +1105,6 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
&config,
auth_manager,
SessionSource::Exec,
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
-2
View File
@@ -9,7 +9,6 @@ use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecutorFileSystem;
use codex_login::CodexAuth;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Op;
@@ -240,7 +239,6 @@ async fn list_skills_skips_cwd_roots_when_environment_disabled() -> Result<()> {
&config,
codex_core::test_support::auth_manager_from_auth(CodexAuth::from_api_key("dummy")),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(EnvironmentManager::disabled_for_tests(
ExecServerRuntimePaths::new(
std::env::current_exe()?,
+1
View File
@@ -88,6 +88,7 @@ Fetch the built-in collaboration mode presets with `collaborationMode/list`. Thi
- `data` - ordered list of collaboration mode masks (partial settings to apply on top of the base mode)
- For tri-state fields like `reasoning_effort` and `developer_instructions`, omit the field to keep the current value, set it to `null` to clear it, or set a concrete value to update it.
- Built-in presets do not set `model`. The Plan preset sets `reasoning_effort` to medium; clients keep or override model separately.
When sending `turn/start` with `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
-2
View File
@@ -21,9 +21,7 @@ codex-arg0 = { workspace = true }
codex-config = { workspace = true }
codex-core = { workspace = true }
codex-exec-server = { workspace = true }
codex-features = { workspace = true }
codex-login = { workspace = true }
codex-models-manager = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-cli = { workspace = true }
codex-utils-json-to-toml = { workspace = true }
@@ -5,11 +5,9 @@ use codex_arg0::Arg0DispatchPaths;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_exec_server::EnvironmentManager;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::default_client::USER_AGENT_SUFFIX;
use codex_login::default_client::get_codex_user_agent;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::Submission;
@@ -65,11 +63,6 @@ impl MessageProcessor {
config.as_ref(),
auth_manager,
SessionSource::Mcp,
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
environment_manager,
/*analytics_events_client*/ None,
));
@@ -11,7 +11,6 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderAwsAuthInfo;
use codex_model_provider_info::ModelProviderInfo;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::SharedModelsManager;
use codex_models_manager::manager::StaticModelsManager;
use codex_protocol::account::ProviderAccount;
@@ -94,12 +93,10 @@ impl ModelProvider for AmazonBedrockModelProvider {
&self,
_codex_home: PathBuf,
config_model_catalog: Option<ModelsResponse>,
collaboration_modes_config: CollaborationModesConfig,
) -> SharedModelsManager {
Arc::new(StaticModelsManager::new(
/*auth_manager*/ None,
config_model_catalog.unwrap_or_else(static_model_catalog),
collaboration_modes_config,
))
}
}
+4 -16
View File
@@ -7,7 +7,6 @@ use codex_api::SharedAuthProvider;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderInfo;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::OpenAiModelsManager;
use codex_models_manager::manager::SharedModelsManager;
use codex_models_manager::manager::StaticModelsManager;
@@ -118,7 +117,6 @@ pub trait ModelProvider: fmt::Debug + Send + Sync {
&self,
codex_home: PathBuf,
config_model_catalog: Option<ModelsResponse>,
collaboration_modes_config: CollaborationModesConfig,
) -> SharedModelsManager;
}
@@ -213,13 +211,11 @@ impl ModelProvider for ConfiguredModelProvider {
&self,
codex_home: PathBuf,
config_model_catalog: Option<ModelsResponse>,
collaboration_modes_config: CollaborationModesConfig,
) -> SharedModelsManager {
match config_model_catalog {
Some(model_catalog) => Arc::new(StaticModelsManager::new(
self.auth_manager.clone(),
model_catalog,
collaboration_modes_config,
)),
None => {
let endpoint = Arc::new(OpenAiModelsEndpoint::new(
@@ -230,7 +226,6 @@ impl ModelProvider for ConfiguredModelProvider {
codex_home,
endpoint,
self.auth_manager.clone(),
collaboration_modes_config,
))
}
}
@@ -445,11 +440,8 @@ mod tests {
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
/*auth_manager*/ None,
);
let manager = provider.models_manager(
test_codex_home(),
/*config_model_catalog*/ None,
Default::default(),
);
let manager =
provider.models_manager(test_codex_home(), /*config_model_catalog*/ None);
let catalog = manager.raw_model_catalog(RefreshStrategy::Online).await;
let model_ids = catalog
@@ -491,7 +483,6 @@ mod tests {
Some(ModelsResponse {
models: vec![custom_model],
}),
Default::default(),
);
let catalog = manager.raw_model_catalog(RefreshStrategy::Online).await;
@@ -528,11 +519,8 @@ mod tests {
)),
);
let manager = provider.models_manager(
test_codex_home(),
/*config_model_catalog*/ None,
Default::default(),
);
let manager =
provider.models_manager(test_codex_home(), /*config_model_catalog*/ None);
let catalog = manager.raw_model_catalog(RefreshStrategy::Online).await;
assert!(
@@ -8,28 +8,13 @@ use codex_utils_template::Template;
use std::sync::LazyLock;
const KNOWN_MODE_NAMES_TEMPLATE_KEY: &str = "KNOWN_MODE_NAMES";
const REQUEST_USER_INPUT_AVAILABILITY_TEMPLATE_KEY: &str = "REQUEST_USER_INPUT_AVAILABILITY";
const ASKING_QUESTIONS_GUIDANCE_TEMPLATE_KEY: &str = "ASKING_QUESTIONS_GUIDANCE";
static COLLABORATION_MODE_DEFAULT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
Template::parse(COLLABORATION_MODE_DEFAULT)
.unwrap_or_else(|err| panic!("collaboration mode default template must parse: {err}"))
});
/// Stores feature flags that control collaboration-mode behavior.
///
/// Keep mode-related flags here so new collaboration-mode capabilities can be
/// added without large cross-cutting diffs to constructor and call-site
/// signatures.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CollaborationModesConfig {
/// Enables `request_user_input` availability in Default mode.
pub default_mode_request_user_input: bool,
}
pub fn builtin_collaboration_mode_presets(
collaboration_modes_config: CollaborationModesConfig,
) -> Vec<CollaborationModeMask> {
vec![plan_preset(), default_preset(collaboration_modes_config)]
pub fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
vec![plan_preset(), default_preset()]
}
fn plan_preset() -> CollaborationModeMask {
@@ -42,37 +27,20 @@ fn plan_preset() -> CollaborationModeMask {
}
}
fn default_preset(collaboration_modes_config: CollaborationModesConfig) -> CollaborationModeMask {
fn default_preset() -> 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(collaboration_modes_config))),
developer_instructions: Some(Some(default_mode_instructions())),
}
}
fn default_mode_instructions(collaboration_modes_config: CollaborationModesConfig) -> String {
fn default_mode_instructions() -> String {
let known_mode_names = format_mode_names(&TUI_VISIBLE_COLLABORATION_MODES);
let request_user_input_availability = request_user_input_availability_message(
ModeKind::Default,
collaboration_modes_config.default_mode_request_user_input,
);
let asking_questions_guidance = asking_questions_guidance_message(
collaboration_modes_config.default_mode_request_user_input,
);
COLLABORATION_MODE_DEFAULT_TEMPLATE
.render([
(KNOWN_MODE_NAMES_TEMPLATE_KEY, known_mode_names.as_str()),
(
REQUEST_USER_INPUT_AVAILABILITY_TEMPLATE_KEY,
request_user_input_availability.as_str(),
),
(
ASKING_QUESTIONS_GUIDANCE_TEMPLATE_KEY,
asking_questions_guidance.as_str(),
),
])
.render([(KNOWN_MODE_NAMES_TEMPLATE_KEY, known_mode_names.as_str())])
.unwrap_or_else(|err| panic!("collaboration mode default template must render: {err}"))
}
@@ -86,30 +54,6 @@ fn format_mode_names(modes: &[ModeKind]) -> String {
}
}
fn request_user_input_availability_message(
mode: ModeKind,
default_mode_request_user_input: bool,
) -> String {
let mode_name = mode.display_name();
if mode.allows_request_user_input()
|| (default_mode_request_user_input && mode == ModeKind::Default)
{
format!("The `request_user_input` tool is available in {mode_name} mode.")
} else {
format!(
"The `request_user_input` tool is unavailable in {mode_name} mode. If you call it while in {mode_name} mode, it will return an error."
)
}
}
fn asking_questions_guidance_message(default_mode_request_user_input: bool) -> String {
if default_mode_request_user_input {
"In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, prefer using the `request_user_input` tool rather than writing a multiple choice question as a textual assistant message. Never write a multiple choice question as a textual assistant message.".to_string()
} else {
"In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.".to_string()
}
}
#[cfg(test)]
#[path = "collaboration_mode_presets_tests.rs"]
mod tests;
@@ -4,49 +4,32 @@ use pretty_assertions::assert_eq;
#[test]
fn preset_names_use_mode_display_names() {
assert_eq!(plan_preset().name, ModeKind::Plan.display_name());
assert_eq!(
default_preset(CollaborationModesConfig::default()).name,
ModeKind::Default.display_name()
);
assert_eq!(default_preset().name, ModeKind::Default.display_name());
assert_eq!(plan_preset().model, None);
assert_eq!(
plan_preset().reasoning_effort,
Some(Some(ReasoningEffort::Medium))
);
assert_eq!(default_preset().model, None);
assert_eq!(default_preset().reasoning_effort, None);
}
#[test]
fn default_mode_instructions_replace_mode_names_placeholder() {
let default_instructions = default_preset(CollaborationModesConfig {
default_mode_request_user_input: true,
})
.developer_instructions
.expect("default preset should include instructions")
.expect("default instructions should be set");
let default_instructions = default_preset()
.developer_instructions
.expect("default preset should include instructions")
.expect("default instructions should be set");
assert!(!default_instructions.contains("{{KNOWN_MODE_NAMES}}"));
assert!(!default_instructions.contains("{{REQUEST_USER_INPUT_AVAILABILITY}}"));
assert!(!default_instructions.contains("{{ASKING_QUESTIONS_GUIDANCE}}"));
let known_mode_names = format_mode_names(&TUI_VISIBLE_COLLABORATION_MODES);
let expected_snippet = format!("Known mode names are {known_mode_names}.");
assert!(default_instructions.contains(&expected_snippet));
let expected_availability_message = request_user_input_availability_message(
ModeKind::Default,
/*default_mode_request_user_input*/ true,
);
assert!(default_instructions.contains(&expected_availability_message));
assert!(default_instructions.contains("prefer using the `request_user_input` tool"));
}
#[test]
fn default_mode_instructions_use_plain_text_questions_when_feature_disabled() {
let default_instructions = default_preset(CollaborationModesConfig::default())
.developer_instructions
.expect("default preset should include instructions")
.expect("default instructions should be set");
assert!(!default_instructions.contains("prefer using the `request_user_input` tool"));
assert!(default_instructions.contains(
"Use the `request_user_input` tool only when it is listed in the available tools"
));
assert!(
default_instructions.contains("ask the user directly with a concise plain-text question")
);
+3 -13
View File
@@ -1,5 +1,4 @@
use super::cache::ModelsCacheManager;
use crate::collaboration_mode_presets::CollaborationModesConfig;
use crate::collaboration_mode_presets::builtin_collaboration_mode_presets;
use crate::config::ModelsManagerConfig;
use crate::model_info;
@@ -180,7 +179,6 @@ pub type SharedModelsManager = Arc<dyn ModelsManager>;
#[derive(Debug)]
pub struct OpenAiModelsManager {
remote_models: RwLock<Vec<ModelInfo>>,
collaboration_modes_config: CollaborationModesConfig,
etag: RwLock<Option<String>>,
cache_manager: ModelsCacheManager,
endpoint_client: SharedModelsEndpointClient,
@@ -191,7 +189,6 @@ pub struct OpenAiModelsManager {
#[derive(Debug)]
pub struct StaticModelsManager {
remote_models: Vec<ModelInfo>,
collaboration_modes_config: CollaborationModesConfig,
auth_manager: Option<Arc<AuthManager>>,
}
@@ -201,14 +198,12 @@ impl OpenAiModelsManager {
codex_home: PathBuf,
endpoint_client: Arc<dyn ModelsEndpointClient>,
auth_manager: Option<Arc<AuthManager>>,
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);
let remote_models = load_remote_models_from_file().unwrap_or_default();
Self {
remote_models: RwLock::new(remote_models),
collaboration_modes_config,
etag: RwLock::new(None),
cache_manager,
endpoint_client,
@@ -219,14 +214,9 @@ impl OpenAiModelsManager {
impl StaticModelsManager {
/// Construct a static model manager from an authoritative catalog.
pub fn new(
auth_manager: Option<Arc<AuthManager>>,
model_catalog: ModelsResponse,
collaboration_modes_config: CollaborationModesConfig,
) -> Self {
pub fn new(auth_manager: Option<Arc<AuthManager>>, model_catalog: ModelsResponse) -> Self {
Self {
remote_models: model_catalog.models,
collaboration_modes_config,
auth_manager,
}
}
@@ -256,7 +246,7 @@ impl ModelsManager for OpenAiModelsManager {
}
fn list_collaboration_modes(&self) -> Vec<CollaborationModeMask> {
builtin_collaboration_mode_presets(self.collaboration_modes_config)
builtin_collaboration_mode_presets()
}
async fn refresh_if_new_etag(&self, etag: String) {
@@ -391,7 +381,7 @@ impl ModelsManager for StaticModelsManager {
}
fn list_collaboration_modes(&self) -> Vec<CollaborationModeMask> {
builtin_collaboration_mode_presets(self.collaboration_modes_config)
builtin_collaboration_mode_presets()
}
async fn refresh_if_new_etag(&self, _etag: String) {}
+2 -12
View File
@@ -187,20 +187,11 @@ fn openai_manager_for_tests_with_auth(
endpoint_client: Arc<dyn ModelsEndpointClient>,
auth_manager: Option<Arc<AuthManager>>,
) -> OpenAiModelsManager {
OpenAiModelsManager::new(
codex_home,
endpoint_client,
auth_manager,
CollaborationModesConfig::default(),
)
OpenAiModelsManager::new(codex_home, endpoint_client, auth_manager)
}
fn static_manager_for_tests(model_catalog: ModelsResponse) -> StaticModelsManager {
StaticModelsManager::new(
/*auth_manager*/ None,
model_catalog,
CollaborationModesConfig::default(),
)
StaticModelsManager::new(/*auth_manager*/ None, model_catalog)
}
async fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth {
@@ -735,7 +726,6 @@ async fn static_manager_reads_latest_auth_mode() {
ModelsResponse {
models: vec![chatgpt_only_model, api_model],
},
CollaborationModesConfig::default(),
);
let chatgpt_models = manager.list_models(RefreshStrategy::Online).await;
@@ -16,7 +16,6 @@ use codex_core_api::AskForApproval;
use codex_core_api::AuthCredentialsStoreMode;
use codex_core_api::AuthManager;
use codex_core_api::CodexThread;
use codex_core_api::CollaborationModesConfig;
use codex_core_api::Config;
use codex_core_api::ConfigLayerStack;
use codex_core_api::Constrained;
@@ -24,7 +23,6 @@ use codex_core_api::EnvironmentManager;
use codex_core_api::EnvironmentManagerArgs;
use codex_core_api::EventMsg;
use codex_core_api::ExecServerRuntimePaths;
use codex_core_api::Feature;
use codex_core_api::GhostSnapshotConfig;
use codex_core_api::History;
use codex_core_api::MemoriesConfig;
@@ -116,11 +114,6 @@ async fn run_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
&config,
auth_manager,
SessionSource::Exec,
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
environment_manager,
/*analytics_events_client*/ None,
);
+1
View File
@@ -83,6 +83,7 @@ pub use plan_tool::create_update_plan_tool;
pub use request_user_input_tool::REQUEST_USER_INPUT_TOOL_NAME;
pub use request_user_input_tool::create_request_user_input_tool;
pub use request_user_input_tool::normalize_request_user_input_args;
pub use request_user_input_tool::request_user_input_available_modes;
pub use request_user_input_tool::request_user_input_tool_description;
pub use request_user_input_tool::request_user_input_unavailable_message;
pub use responses_api::FreeformTool;
+21 -14
View File
@@ -1,6 +1,8 @@
use crate::JsonSchema;
use crate::ResponsesApiTool;
use crate::ToolSpec;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
use codex_protocol::request_user_input::RequestUserInputArgs;
@@ -8,6 +10,17 @@ use std::collections::BTreeMap;
pub const REQUEST_USER_INPUT_TOOL_NAME: &str = "request_user_input";
pub fn request_user_input_available_modes(features: &Features) -> Vec<ModeKind> {
TUI_VISIBLE_COLLABORATION_MODES
.into_iter()
.filter(|mode| {
mode.allows_request_user_input()
|| (features.enabled(Feature::DefaultModeRequestUserInput)
&& *mode == ModeKind::Default)
})
.collect()
}
pub fn create_request_user_input_tool(description: String) -> ToolSpec {
let option_props = BTreeMap::from([
(
@@ -85,9 +98,9 @@ pub fn create_request_user_input_tool(description: String) -> ToolSpec {
pub fn request_user_input_unavailable_message(
mode: ModeKind,
default_mode_request_user_input: bool,
available_modes: &[ModeKind],
) -> Option<String> {
if request_user_input_is_available(mode, default_mode_request_user_input) {
if available_modes.contains(&mode) {
None
} else {
let mode_name = mode.display_name();
@@ -115,23 +128,17 @@ pub fn normalize_request_user_input_args(
Ok(args)
}
pub fn request_user_input_tool_description(default_mode_request_user_input: bool) -> String {
let allowed_modes = format_allowed_modes(default_mode_request_user_input);
pub fn request_user_input_tool_description(available_modes: &[ModeKind]) -> String {
let allowed_modes = format_allowed_modes(available_modes);
format!(
"Request user input for one to three short questions and wait for the response. This tool is only available in {allowed_modes}."
)
}
fn request_user_input_is_available(mode: ModeKind, default_mode_request_user_input: bool) -> bool {
mode.allows_request_user_input()
|| (default_mode_request_user_input && mode == ModeKind::Default)
}
fn format_allowed_modes(default_mode_request_user_input: bool) -> String {
let mode_names: Vec<&str> = TUI_VISIBLE_COLLABORATION_MODES
.into_iter()
.filter(|mode| request_user_input_is_available(*mode, default_mode_request_user_input))
.map(ModeKind::display_name)
fn format_allowed_modes(available_modes: &[ModeKind]) -> String {
let mode_names: Vec<&str> = available_modes
.iter()
.map(|mode| mode.display_name())
.collect();
match mode_names.as_slice() {
@@ -1,9 +1,21 @@
use super::*;
use crate::JsonSchema;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
fn default_mode_enabled_available_modes() -> Vec<ModeKind> {
let mut features = Features::with_defaults();
features.enable(Feature::DefaultModeRequestUserInput);
request_user_input_available_modes(&features)
}
fn default_available_modes() -> Vec<ModeKind> {
request_user_input_available_modes(&Features::with_defaults())
}
#[test]
fn request_user_input_tool_includes_questions_schema() {
assert_eq!(
@@ -92,37 +104,28 @@ fn request_user_input_tool_includes_questions_schema() {
#[test]
fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
assert_eq!(
request_user_input_unavailable_message(
ModeKind::Plan,
/*default_mode_request_user_input*/ false
),
request_user_input_unavailable_message(ModeKind::Plan, &default_available_modes()),
None
);
assert_eq!(
request_user_input_unavailable_message(
ModeKind::Default,
/*default_mode_request_user_input*/ false
),
request_user_input_unavailable_message(ModeKind::Default, &default_available_modes()),
Some("request_user_input is unavailable in Default mode".to_string())
);
assert_eq!(
request_user_input_unavailable_message(
ModeKind::Default,
/*default_mode_request_user_input*/ true
&default_mode_enabled_available_modes()
),
None
);
assert_eq!(
request_user_input_unavailable_message(
ModeKind::Execute,
/*default_mode_request_user_input*/ false
),
request_user_input_unavailable_message(ModeKind::Execute, &default_available_modes()),
Some("request_user_input is unavailable in Execute mode".to_string())
);
assert_eq!(
request_user_input_unavailable_message(
ModeKind::PairProgramming,
/*default_mode_request_user_input*/ false
&default_available_modes()
),
Some("request_user_input is unavailable in Pair Programming mode".to_string())
);
@@ -131,11 +134,11 @@ fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
#[test]
fn request_user_input_tool_description_mentions_available_modes() {
assert_eq!(
request_user_input_tool_description(/*default_mode_request_user_input*/ false),
request_user_input_tool_description(&default_available_modes()),
"Request user input for one to three short questions and wait for the response. This tool is only available in Plan mode.".to_string()
);
assert_eq!(
request_user_input_tool_description(/*default_mode_request_user_input*/ true),
request_user_input_tool_description(&default_mode_enabled_available_modes()),
"Request user input for one to three short questions and wait for the response. This tool is only available in Default or Plan mode.".to_string()
);
}
+4 -4
View File
@@ -1,6 +1,8 @@
use crate::can_request_original_image_detail;
use crate::request_user_input_available_modes;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::WebSearchConfig;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel;
@@ -109,7 +111,7 @@ pub struct ToolsConfig {
pub spawn_agent_usage_hint_text: Option<String>,
pub max_concurrent_threads_per_session: Option<usize>,
pub wait_agent_min_timeout_ms: Option<i64>,
pub default_mode_request_user_input: bool,
pub request_user_input_available_modes: Vec<ModeKind>,
pub experimental_supported_tools: Vec<String>,
pub agent_jobs_tools: bool,
pub agent_jobs_worker_tools: bool,
@@ -145,8 +147,6 @@ impl ToolsConfig {
let include_goal_tools = features.enabled(Feature::Goals);
let include_multi_agent_v2 = features.enabled(Feature::MultiAgentV2);
let include_agent_jobs = features.enabled(Feature::SpawnCsv);
let include_default_mode_request_user_input =
features.enabled(Feature::DefaultModeRequestUserInput);
let include_search_tool =
model_info.supports_search_tool && features.enabled(Feature::ToolSearch);
let include_tool_suggest = features.enabled(Feature::ToolSuggest)
@@ -228,7 +228,7 @@ impl ToolsConfig {
spawn_agent_usage_hint_text: None,
max_concurrent_threads_per_session: None,
wait_agent_min_timeout_ms: None,
default_mode_request_user_input: include_default_mode_request_user_input,
request_user_input_available_modes: request_user_input_available_modes(features),
experimental_supported_tools: model_info.experimental_supported_tools.clone(),
agent_jobs_tools: include_agent_jobs,
agent_jobs_worker_tools,
+5 -2
View File
@@ -155,7 +155,7 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
}
#[test]
fn subagents_keep_request_user_input_mode_config_and_agent_jobs_workers_opt_in_by_label() {
fn subagents_keep_request_user_input_config_and_agent_jobs_workers_opt_in_by_label() {
let model_info = model_info();
let mut features = Features::with_defaults();
features.enable(Feature::DefaultModeRequestUserInput);
@@ -175,7 +175,10 @@ fn subagents_keep_request_user_input_mode_config_and_agent_jobs_workers_opt_in_b
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
assert!(tools_config.default_mode_request_user_input);
assert_eq!(
tools_config.request_user_input_available_modes,
request_user_input_available_modes(&features)
);
assert!(tools_config.agent_jobs_tools);
assert!(tools_config.agent_jobs_worker_tools);
}
+1 -1
View File
@@ -241,7 +241,7 @@ pub fn build_tool_registry_plan(
plan.push_spec(
create_request_user_input_tool(request_user_input_tool_description(
config.default_mode_request_user_input,
&config.request_user_input_available_modes,
)),
/*supports_parallel_tool_calls*/ false,
config.code_mode_enabled,
@@ -19,9 +19,11 @@ use crate::ToolRegistryPlanMcpTool;
use crate::ToolsConfigParams;
use crate::WaitAgentTimeoutOptions;
use crate::mcp_call_tool_result_output_schema;
use crate::request_user_input_available_modes;
use codex_app_server_protocol::AppInfo;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::WebSearchConfig;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel;
@@ -88,7 +90,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
}),
create_write_stdin_tool(),
create_update_plan_tool(),
request_user_input_tool_spec(/*default_mode_request_user_input*/ false),
request_user_input_tool_spec(&request_user_input_available_modes(&features)),
create_apply_patch_freeform_tool(),
ToolSpec::WebSearch {
external_web_access: Some(true),
@@ -586,7 +588,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
assert_eq!(
request_user_input_tool.spec,
request_user_input_tool_spec(/*default_mode_request_user_input*/ false)
request_user_input_tool_spec(&request_user_input_available_modes(&features))
);
features.enable(Feature::DefaultModeRequestUserInput);
@@ -609,7 +611,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
assert_eq!(
request_user_input_tool.spec,
request_user_input_tool_spec(/*default_mode_request_user_input*/ true)
request_user_input_tool_spec(&request_user_input_available_modes(&features))
);
}
@@ -2357,10 +2359,8 @@ fn assert_lacks_tool_name(tools: &[ConfiguredToolSpec], expected_absent: &str) {
);
}
fn request_user_input_tool_spec(default_mode_request_user_input: bool) -> ToolSpec {
create_request_user_input_tool(request_user_input_tool_description(
default_mode_request_user_input,
))
fn request_user_input_tool_spec(available_modes: &[ModeKind]) -> ToolSpec {
create_request_user_input_tool(request_user_input_tool_description(available_modes))
}
fn spawn_agent_tool_options(config: &ToolsConfig) -> SpawnAgentToolOptions<'_> {
+1 -9
View File
@@ -122,7 +122,6 @@ use codex_config::types::ApprovalsReviewer;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_exec_server::EnvironmentManager;
use codex_features::Feature;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
use codex_models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;
use codex_otel::SessionTelemetry;
@@ -743,14 +742,7 @@ impl App {
if let Some(updated_model) = config.model.clone() {
model = updated_model;
}
let model_catalog = Arc::new(ModelCatalog::new(
available_models.clone(),
CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
));
let model_catalog = Arc::new(ModelCatalog::new(available_models.clone()));
let feedback_audience = bootstrap.feedback_audience;
let auth_mode = bootstrap.auth_mode;
let has_chatgpt_account = bootstrap.has_chatgpt_account;
-1
View File
@@ -112,7 +112,6 @@ pub(super) use codex_core_skills::model::SkillMetadata;
pub(super) use codex_features::FEATURES;
pub(super) use codex_features::Feature;
pub(super) use codex_git_utils::CommitLogEntry;
pub(super) use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
pub(super) use codex_otel::RuntimeMetricsSummary;
pub(super) use codex_otel::SessionTelemetry;
pub(super) use codex_protocol::ThreadId;
+2 -16
View File
@@ -126,15 +126,9 @@ pub(super) fn test_session_telemetry(config: &Config, model: &str) -> SessionTel
)
}
pub(super) fn test_model_catalog(config: &Config) -> Arc<ModelCatalog> {
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: config
.features
.enabled(Feature::DefaultModeRequestUserInput),
};
pub(super) fn test_model_catalog(_config: &Config) -> Arc<ModelCatalog> {
Arc::new(ModelCatalog::new(
crate::legacy_core::test_support::all_model_presets().clone(),
collaboration_modes_config,
))
}
@@ -429,15 +423,7 @@ pub(crate) fn set_fast_mode_test_catalog(chat: &mut ChatWidget) {
.map(Into::into)
.collect();
chat.model_catalog = Arc::new(ModelCatalog::new(
models,
CollaborationModesConfig {
default_mode_request_user_input: chat
.config
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
));
chat.model_catalog = Arc::new(ModelCatalog::new(models));
}
pub(crate) async fn make_chatwidget_manual_with_sender() -> (
+3 -3
View File
@@ -1,11 +1,11 @@
use codex_models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use crate::model_catalog::ModelCatalog;
fn filtered_presets(model_catalog: &ModelCatalog) -> Vec<CollaborationModeMask> {
model_catalog
.list_collaboration_modes()
fn filtered_presets(_model_catalog: &ModelCatalog) -> Vec<CollaborationModeMask> {
builtin_collaboration_mode_presets()
.into_iter()
.filter(|mask| mask.mode.is_some_and(ModeKind::is_tui_visible))
.collect()
+2 -35
View File
@@ -1,50 +1,17 @@
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::openai_models::ModelPreset;
use std::convert::Infallible;
#[derive(Debug, Clone)]
pub(crate) struct ModelCatalog {
models: Vec<ModelPreset>,
collaboration_modes_config: CollaborationModesConfig,
}
impl ModelCatalog {
pub(crate) fn new(
models: Vec<ModelPreset>,
collaboration_modes_config: CollaborationModesConfig,
) -> Self {
Self {
models,
collaboration_modes_config,
}
pub(crate) fn new(models: Vec<ModelPreset>) -> Self {
Self { models }
}
pub(crate) fn try_list_models(&self) -> Result<Vec<ModelPreset>, Infallible> {
Ok(self.models.clone())
}
pub(crate) fn list_collaboration_modes(&self) -> Vec<CollaborationModeMask> {
builtin_collaboration_mode_presets(self.collaboration_modes_config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn list_collaboration_modes_matches_core_presets() {
let collaboration_modes_config = CollaborationModesConfig {
default_mode_request_user_input: true,
};
let catalog = ModelCatalog::new(Vec::new(), collaboration_modes_config);
assert_eq!(
catalog.list_collaboration_modes(),
builtin_collaboration_mode_presets(collaboration_modes_config)
);
}
}