mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Enable request_user_input in Default mode (#12735)
## Summary - allow `request_user_input` in Default collaboration mode as well as Plan - update the Default-mode instructions to prefer assumptions first and use `request_user_input` only when a question is unavoidable - update request_user_input and app-server tests to match the new Default-mode behavior - refactor collaboration-mode availability plumbing into `CollaborationModesConfig` for future mode-related flags ## Codex author `codex resume 019c9124-ed28-7c13-96c6-b916b1c97d49`
This commit is contained in:
committed by
GitHub
Unverified
parent
2bd87d1a75
commit
2f4d6ded1d
@@ -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;
|
||||
@@ -8293,6 +8295,7 @@ mod tests {
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info =
|
||||
@@ -8369,6 +8372,7 @@ mod tests {
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let agent_control = AgentControl::default();
|
||||
let exec_policy = ExecPolicyManager::default();
|
||||
@@ -8527,6 +8531,7 @@ mod tests {
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let agent_control = AgentControl::default();
|
||||
let exec_policy = ExecPolicyManager::default();
|
||||
|
||||
@@ -137,6 +137,8 @@ pub enum Feature {
|
||||
SkillApproval,
|
||||
/// Steer feature flag - when enabled, Enter submits immediately instead of queuing.
|
||||
Steer,
|
||||
/// Allow request_user_input in Default collaboration mode.
|
||||
DefaultModeRequestUserInput,
|
||||
/// Enable collaboration modes (Plan, Default).
|
||||
/// Kept for config backward compatibility; behavior is always collaboration-modes-enabled.
|
||||
CollaborationModes,
|
||||
@@ -639,6 +641,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::DefaultModeRequestUserInput,
|
||||
key: "default_mode_request_user_input",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CollaborationModes,
|
||||
key: "collaboration_modes",
|
||||
|
||||
@@ -8,9 +8,23 @@ const COLLABORATION_MODE_DEFAULT: &str =
|
||||
include_str!("../../templates/collaboration_mode/default.md");
|
||||
const KNOWN_MODE_NAMES_PLACEHOLDER: &str = "{{KNOWN_MODE_NAMES}}";
|
||||
const REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER: &str = "{{REQUEST_USER_INPUT_AVAILABILITY}}";
|
||||
const ASKING_QUESTIONS_GUIDANCE_PLACEHOLDER: &str = "{{ASKING_QUESTIONS_GUIDANCE}}";
|
||||
|
||||
pub(crate) fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
|
||||
vec![plan_preset(), default_preset()]
|
||||
/// 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(crate) fn builtin_collaboration_mode_presets(
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
) -> Vec<CollaborationModeMask> {
|
||||
vec![plan_preset(), default_preset(collaboration_modes_config)]
|
||||
}
|
||||
|
||||
fn plan_preset() -> CollaborationModeMask {
|
||||
@@ -23,26 +37,35 @@ fn plan_preset() -> CollaborationModeMask {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_preset() -> 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())),
|
||||
developer_instructions: Some(Some(default_mode_instructions(collaboration_modes_config))),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_mode_instructions() -> 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);
|
||||
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
|
||||
.replace(KNOWN_MODE_NAMES_PLACEHOLDER, &known_mode_names)
|
||||
.replace(
|
||||
REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER,
|
||||
&request_user_input_availability,
|
||||
)
|
||||
.replace(
|
||||
ASKING_QUESTIONS_GUIDANCE_PLACEHOLDER,
|
||||
&asking_questions_guidance,
|
||||
)
|
||||
}
|
||||
|
||||
fn format_mode_names(modes: &[ModeKind]) -> String {
|
||||
@@ -55,9 +78,14 @@ fn format_mode_names(modes: &[ModeKind]) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn request_user_input_availability_message(mode: 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() {
|
||||
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!(
|
||||
@@ -66,6 +94,14 @@ fn request_user_input_availability_message(mode: ModeKind) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -74,7 +110,10 @@ mod tests {
|
||||
#[test]
|
||||
fn preset_names_use_mode_display_names() {
|
||||
assert_eq!(plan_preset().name, ModeKind::Plan.display_name());
|
||||
assert_eq!(default_preset().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))
|
||||
@@ -83,20 +122,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_mode_instructions_replace_mode_names_placeholder() {
|
||||
let default_instructions = default_preset()
|
||||
.developer_instructions
|
||||
.expect("default preset should include instructions")
|
||||
.expect("default instructions should be set");
|
||||
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");
|
||||
|
||||
assert!(!default_instructions.contains(KNOWN_MODE_NAMES_PLACEHOLDER));
|
||||
assert!(!default_instructions.contains(REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER));
|
||||
assert!(!default_instructions.contains(ASKING_QUESTIONS_GUIDANCE_PLACEHOLDER));
|
||||
|
||||
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);
|
||||
request_user_input_availability_message(ModeKind::Default, 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("ask the user directly with a concise plain-text question")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -55,6 +56,7 @@ enum CatalogMode {
|
||||
pub struct ModelsManager {
|
||||
remote_models: RwLock<Vec<ModelInfo>>,
|
||||
catalog_mode: CatalogMode,
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
etag: RwLock<Option<String>>,
|
||||
cache_manager: ModelsCacheManager,
|
||||
@@ -71,6 +73,7 @@ impl ModelsManager {
|
||||
codex_home: PathBuf,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
model_catalog: Option<ModelsResponse>,
|
||||
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);
|
||||
@@ -88,6 +91,7 @@ impl ModelsManager {
|
||||
Self {
|
||||
remote_models: RwLock::new(remote_models),
|
||||
catalog_mode,
|
||||
collaboration_modes_config,
|
||||
auth_manager,
|
||||
etag: RwLock::new(None),
|
||||
cache_manager,
|
||||
@@ -110,7 +114,14 @@ impl ModelsManager {
|
||||
///
|
||||
/// Returns a static set of presets seeded with the configured model.
|
||||
pub fn list_collaboration_modes(&self) -> Vec<CollaborationModeMask> {
|
||||
builtin_collaboration_mode_presets()
|
||||
self.list_collaboration_modes_for_config(self.collaboration_modes_config)
|
||||
}
|
||||
|
||||
pub fn list_collaboration_modes_for_config(
|
||||
&self,
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
) -> Vec<CollaborationModeMask> {
|
||||
builtin_collaboration_mode_presets(collaboration_modes_config)
|
||||
}
|
||||
|
||||
/// Attempt to list models without blocking, using the current cached state.
|
||||
@@ -378,6 +389,7 @@ impl ModelsManager {
|
||||
.unwrap_or_else(|err| panic!("failed to load bundled models.json: {err}")),
|
||||
),
|
||||
catalog_mode: CatalogMode::Default,
|
||||
collaboration_modes_config: CollaborationModesConfig::default(),
|
||||
auth_manager,
|
||||
etag: RwLock::new(None),
|
||||
cache_manager,
|
||||
@@ -504,7 +516,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);
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
auth_manager,
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
let known_slug = manager
|
||||
.get_remote_models()
|
||||
.await
|
||||
@@ -541,6 +558,7 @@ mod tests {
|
||||
Some(ModelsResponse {
|
||||
models: vec![remote_model("gpt-overlay", "Overlay", 0)],
|
||||
}),
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
|
||||
let model_info = manager
|
||||
@@ -564,7 +582,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);
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
auth_manager,
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
let known_slug = manager
|
||||
.get_remote_models()
|
||||
.await
|
||||
@@ -590,7 +613,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);
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
auth_manager,
|
||||
None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
let known_slug = manager
|
||||
.get_remote_models()
|
||||
.await
|
||||
|
||||
@@ -85,5 +85,7 @@ 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::builtin_collaboration_mode_presets(
|
||||
collaboration_mode_presets::CollaborationModesConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,6 +144,7 @@ impl ThreadManager {
|
||||
auth_manager: Arc<AuthManager>,
|
||||
session_source: SessionSource,
|
||||
model_catalog: Option<ModelsResponse>,
|
||||
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()));
|
||||
@@ -155,6 +157,7 @@ impl ThreadManager {
|
||||
codex_home,
|
||||
auth_manager.clone(),
|
||||
model_catalog,
|
||||
collaboration_modes_config,
|
||||
)),
|
||||
skills_manager,
|
||||
file_watcher,
|
||||
|
||||
@@ -12,10 +12,15 @@ use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
|
||||
use codex_protocol::request_user_input::RequestUserInputArgs;
|
||||
|
||||
fn format_allowed_modes() -> String {
|
||||
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| mode.allows_request_user_input())
|
||||
.filter(|mode| request_user_input_is_available(*mode, default_mode_request_user_input))
|
||||
.map(ModeKind::display_name)
|
||||
.collect();
|
||||
|
||||
@@ -27,8 +32,11 @@ fn format_allowed_modes() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_user_input_unavailable_message(mode: ModeKind) -> Option<String> {
|
||||
if mode.allows_request_user_input() {
|
||||
pub(crate) fn request_user_input_unavailable_message(
|
||||
mode: ModeKind,
|
||||
default_mode_request_user_input: bool,
|
||||
) -> Option<String> {
|
||||
if request_user_input_is_available(mode, default_mode_request_user_input) {
|
||||
None
|
||||
} else {
|
||||
let mode_name = mode.display_name();
|
||||
@@ -38,14 +46,16 @@ pub(crate) fn request_user_input_unavailable_message(mode: ModeKind) -> Option<S
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_user_input_tool_description() -> String {
|
||||
let allowed_modes = format_allowed_modes();
|
||||
pub(crate) fn request_user_input_tool_description(default_mode_request_user_input: bool) -> String {
|
||||
let allowed_modes = format_allowed_modes(default_mode_request_user_input);
|
||||
format!(
|
||||
"Request user input for one to three short questions and wait for the response. This tool is only available in {allowed_modes}."
|
||||
)
|
||||
}
|
||||
|
||||
pub struct RequestUserInputHandler;
|
||||
pub struct RequestUserInputHandler {
|
||||
pub default_mode_request_user_input: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolHandler for RequestUserInputHandler {
|
||||
@@ -72,7 +82,9 @@ impl ToolHandler for RequestUserInputHandler {
|
||||
};
|
||||
|
||||
let mode = session.collaboration_mode().await.mode;
|
||||
if let Some(message) = request_user_input_unavailable_message(mode) {
|
||||
if let Some(message) =
|
||||
request_user_input_unavailable_message(mode, self.default_mode_request_user_input)
|
||||
{
|
||||
return Err(FunctionCallError::RespondToModel(message));
|
||||
}
|
||||
|
||||
@@ -117,7 +129,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn request_user_input_mode_availability_is_plan_only() {
|
||||
fn request_user_input_mode_availability_defaults_to_plan_only() {
|
||||
assert!(ModeKind::Plan.allows_request_user_input());
|
||||
assert!(!ModeKind::Default.allows_request_user_input());
|
||||
assert!(!ModeKind::Execute.allows_request_user_input());
|
||||
@@ -125,27 +137,38 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_unavailable_messages_use_default_name_for_default_modes() {
|
||||
assert_eq!(request_user_input_unavailable_message(ModeKind::Plan), None);
|
||||
fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Default),
|
||||
request_user_input_unavailable_message(ModeKind::Plan, false),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Default, false),
|
||||
Some("request_user_input is unavailable in Default mode".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Execute),
|
||||
request_user_input_unavailable_message(ModeKind::Default, true),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Execute, false),
|
||||
Some("request_user_input is unavailable in Execute mode".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::PairProgramming),
|
||||
request_user_input_unavailable_message(ModeKind::PairProgramming, false),
|
||||
Some("request_user_input is unavailable in Pair Programming mode".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_tool_description_mentions_plan_only() {
|
||||
fn request_user_input_tool_description_mentions_available_modes() {
|
||||
assert_eq!(
|
||||
request_user_input_tool_description(),
|
||||
request_user_input_tool_description(false),
|
||||
"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(true),
|
||||
"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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::config::AgentRoleConfig;
|
||||
use crate::features::Feature;
|
||||
use crate::features::Features;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::tools::handlers::PLAN_TOOL;
|
||||
use crate::tools::handlers::SEARCH_TOOL_BM25_DEFAULT_LIMIT;
|
||||
use crate::tools::handlers::SEARCH_TOOL_BM25_TOOL_NAME;
|
||||
@@ -54,7 +55,7 @@ pub(crate) struct ToolsConfig {
|
||||
pub js_repl_enabled: bool,
|
||||
pub js_repl_tools_only: bool,
|
||||
pub collab_tools: bool,
|
||||
pub collaboration_modes_tools: bool,
|
||||
pub default_mode_request_user_input: bool,
|
||||
pub experimental_supported_tools: Vec<String>,
|
||||
pub agent_jobs_tools: bool,
|
||||
pub agent_jobs_worker_tools: bool,
|
||||
@@ -80,7 +81,8 @@ impl ToolsConfig {
|
||||
let include_js_repl_tools_only =
|
||||
include_js_repl && features.enabled(Feature::JsReplToolsOnly);
|
||||
let include_collab_tools = features.enabled(Feature::Collab);
|
||||
let include_collaboration_modes_tools = features.enabled(Feature::CollaborationModes);
|
||||
let include_default_mode_request_user_input =
|
||||
features.enabled(Feature::DefaultModeRequestUserInput);
|
||||
let include_search_tool = features.enabled(Feature::Apps);
|
||||
let include_agent_jobs = include_collab_tools && features.enabled(Feature::Sqlite);
|
||||
let request_permission_enabled = features.enabled(Feature::RequestPermissions);
|
||||
@@ -137,7 +139,7 @@ impl ToolsConfig {
|
||||
js_repl_enabled: include_js_repl,
|
||||
js_repl_tools_only: include_js_repl_tools_only,
|
||||
collab_tools: include_collab_tools,
|
||||
collaboration_modes_tools: include_collaboration_modes_tools,
|
||||
default_mode_request_user_input: include_default_mode_request_user_input,
|
||||
experimental_supported_tools: model_info.experimental_supported_tools.clone(),
|
||||
agent_jobs_tools: include_agent_jobs,
|
||||
agent_jobs_worker_tools,
|
||||
@@ -872,7 +874,9 @@ fn create_wait_tool() -> ToolSpec {
|
||||
})
|
||||
}
|
||||
|
||||
fn create_request_user_input_tool() -> ToolSpec {
|
||||
fn create_request_user_input_tool(
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
) -> ToolSpec {
|
||||
let mut option_props = BTreeMap::new();
|
||||
option_props.insert(
|
||||
"label".to_string(),
|
||||
@@ -943,7 +947,9 @@ fn create_request_user_input_tool() -> ToolSpec {
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "request_user_input".to_string(),
|
||||
description: request_user_input_tool_description(),
|
||||
description: request_user_input_tool_description(
|
||||
collaboration_modes_config.default_mode_request_user_input,
|
||||
),
|
||||
strict: false,
|
||||
parameters: JsonSchema::Object {
|
||||
properties,
|
||||
@@ -1664,7 +1670,9 @@ pub(crate) fn build_specs(
|
||||
let mcp_handler = Arc::new(McpHandler);
|
||||
let mcp_resource_handler = Arc::new(McpResourceHandler);
|
||||
let shell_command_handler = Arc::new(ShellCommandHandler::from(config.shell_command_backend));
|
||||
let request_user_input_handler = Arc::new(RequestUserInputHandler);
|
||||
let request_user_input_handler = Arc::new(RequestUserInputHandler {
|
||||
default_mode_request_user_input: config.default_mode_request_user_input,
|
||||
});
|
||||
let search_tool_handler = Arc::new(SearchToolBm25Handler);
|
||||
let js_repl_handler = Arc::new(JsReplHandler);
|
||||
let js_repl_reset_handler = Arc::new(JsReplResetHandler);
|
||||
@@ -1727,10 +1735,10 @@ pub(crate) fn build_specs(
|
||||
builder.register_handler("js_repl_reset", js_repl_reset_handler);
|
||||
}
|
||||
|
||||
if config.collaboration_modes_tools {
|
||||
builder.push_spec(create_request_user_input_tool());
|
||||
builder.register_handler("request_user_input", request_user_input_handler);
|
||||
}
|
||||
builder.push_spec(create_request_user_input_tool(CollaborationModesConfig {
|
||||
default_mode_request_user_input: config.default_mode_request_user_input,
|
||||
}));
|
||||
builder.register_handler("request_user_input", request_user_input_handler);
|
||||
|
||||
if config.search_tool
|
||||
&& let Some(app_tools) = app_tools
|
||||
@@ -2024,7 +2032,6 @@ mod tests {
|
||||
let model_info = model_info_from_models_json("gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2055,7 +2062,7 @@ mod tests {
|
||||
create_exec_command_tool(true, false),
|
||||
create_write_stdin_tool(),
|
||||
PLAN_TOOL.clone(),
|
||||
create_request_user_input_tool(),
|
||||
create_request_user_input_tool(CollaborationModesConfig::default()),
|
||||
create_apply_patch_freeform_tool(),
|
||||
ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
@@ -2087,8 +2094,6 @@ mod tests {
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
features.enable(Feature::Sqlite);
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2115,7 +2120,6 @@ mod tests {
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
features.enable(Feature::Sqlite);
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
@@ -2141,12 +2145,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_requires_collaboration_modes_feature() {
|
||||
fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let config = test_config();
|
||||
let model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.disable(Feature::CollaborationModes);
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2154,12 +2157,13 @@ mod tests {
|
||||
session_source: SessionSource::Cli,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
assert!(
|
||||
!tools.iter().any(|t| t.spec.name() == "request_user_input"),
|
||||
"request_user_input should be disabled when collaboration_modes feature is off"
|
||||
let request_user_input_tool = find_tool(&tools, "request_user_input");
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
create_request_user_input_tool(CollaborationModesConfig::default())
|
||||
);
|
||||
|
||||
features.enable(Feature::CollaborationModes);
|
||||
features.enable(Feature::DefaultModeRequestUserInput);
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2167,7 +2171,13 @@ mod tests {
|
||||
session_source: SessionSource::Cli,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
assert_contains_tool_names(&tools, &["request_user_input"]);
|
||||
let request_user_input_tool = find_tool(&tools, "request_user_input");
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
create_request_user_input_tool(CollaborationModesConfig {
|
||||
default_mode_request_user_input: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2335,8 +2345,7 @@ mod tests {
|
||||
let config = test_config();
|
||||
let model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2359,8 +2368,7 @@ mod tests {
|
||||
let config = test_config();
|
||||
let model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
features: &features,
|
||||
@@ -2381,8 +2389,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt5_codex_default() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5-codex",
|
||||
&features,
|
||||
@@ -2400,8 +2407,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_gpt51_codex_default() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex",
|
||||
&features,
|
||||
@@ -2421,7 +2427,6 @@ mod tests {
|
||||
fn test_build_specs_gpt5_codex_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
assert_model_tools(
|
||||
"gpt-5-codex",
|
||||
&features,
|
||||
@@ -2442,7 +2447,6 @@ mod tests {
|
||||
fn test_build_specs_gpt51_codex_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
assert_model_tools(
|
||||
"gpt-5.1-codex",
|
||||
&features,
|
||||
@@ -2461,8 +2465,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_1_codex_max_defaults() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex-max",
|
||||
&features,
|
||||
@@ -2480,8 +2483,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_codex_5_1_mini_defaults() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1-codex-mini",
|
||||
&features,
|
||||
@@ -2499,8 +2501,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_defaults() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5",
|
||||
&features,
|
||||
@@ -2517,8 +2518,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_gpt_5_1_defaults() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CollaborationModes);
|
||||
let features = Features::with_defaults();
|
||||
assert_default_model_tools(
|
||||
"gpt-5.1",
|
||||
&features,
|
||||
@@ -2538,7 +2538,6 @@ mod tests {
|
||||
fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::CollaborationModes);
|
||||
assert_model_tools(
|
||||
"gpt-5.1-codex-max",
|
||||
&features,
|
||||
|
||||
Reference in New Issue
Block a user