mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Standardize shell execution on unified exec (#39772)
## What changed - Use `exec_command` and `write_stdin` as the shell tool surface. - Treat legacy `default`, `local`, and `shell_command` model metadata as `unified_exec`. - Remove obsolete shell-selection configuration and runtime paths while preserving the feature and policy gates for zsh fork execution. GitOrigin-RevId: d743cbe598630d73052f1fecad680c4cde17977d
This commit is contained in:
@@ -73,12 +73,9 @@ pub use tool_call::TurnItemEmissionFuture;
|
||||
pub use tool_call::TurnItemEmitter;
|
||||
pub use tool_config::ToolEnvironmentMode;
|
||||
pub use tool_config::ToolUserShellType;
|
||||
pub use tool_config::UnifiedExecFeatureMode;
|
||||
pub use tool_config::UnifiedExecShellMode;
|
||||
pub use tool_config::ZshForkConfig;
|
||||
pub use tool_config::request_user_input_available_modes;
|
||||
pub use tool_config::shell_type_for_model_and_features;
|
||||
pub use tool_config::unified_exec_feature_mode_for_features;
|
||||
pub use tool_definition::ToolDefinition;
|
||||
pub use tool_discovery::DiscoverablePluginInfo;
|
||||
pub use tool_discovery::DiscoverableTool;
|
||||
|
||||
@@ -2,20 +2,9 @@ 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::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum UnifiedExecFeatureMode {
|
||||
/// Unified exec should not be selected by this feature set.
|
||||
///
|
||||
Disabled,
|
||||
Direct,
|
||||
ZshFork,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ToolUserShellType {
|
||||
Zsh,
|
||||
@@ -36,40 +25,6 @@ pub fn request_user_input_available_modes(features: &Features) -> Vec<ModeKind>
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the unified-exec mode requested by feature policy, before runtime
|
||||
/// session inputs such as platform, user shell, and zsh-fork binary paths are
|
||||
/// resolved.
|
||||
///
|
||||
/// Disabling unified exec keeps command execution disabled. The legacy
|
||||
/// composition flag can still disable zsh-fork interception independently.
|
||||
pub fn unified_exec_feature_mode_for_features(features: &Features) -> UnifiedExecFeatureMode {
|
||||
if !features.enabled(Feature::ShellTool) || !features.enabled(Feature::UnifiedExec) {
|
||||
UnifiedExecFeatureMode::Disabled
|
||||
} else if features.enabled(Feature::ShellZshFork) {
|
||||
if features.enabled(Feature::UnifiedExecZshFork) {
|
||||
UnifiedExecFeatureMode::ZshFork
|
||||
} else {
|
||||
UnifiedExecFeatureMode::Direct
|
||||
}
|
||||
} else {
|
||||
UnifiedExecFeatureMode::Direct
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shell_type_for_model_and_features(
|
||||
model_info: &ModelInfo,
|
||||
features: &Features,
|
||||
) -> ConfigShellToolType {
|
||||
if !features.enabled(Feature::ShellTool)
|
||||
|| !features.enabled(Feature::UnifiedExec)
|
||||
|| matches!(model_info.shell_type, ConfigShellToolType::Disabled)
|
||||
{
|
||||
ConfigShellToolType::Disabled
|
||||
} else {
|
||||
ConfigShellToolType::UnifiedExec
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum UnifiedExecShellMode {
|
||||
Direct,
|
||||
@@ -84,13 +39,16 @@ pub struct ZshForkConfig {
|
||||
|
||||
impl UnifiedExecShellMode {
|
||||
pub fn for_session(
|
||||
feature_mode: UnifiedExecFeatureMode,
|
||||
features: &Features,
|
||||
user_shell_type: ToolUserShellType,
|
||||
shell_zsh_path: Option<&PathBuf>,
|
||||
main_execve_wrapper_exe: Option<&PathBuf>,
|
||||
) -> Self {
|
||||
if cfg!(unix)
|
||||
&& matches!(feature_mode, UnifiedExecFeatureMode::ZshFork)
|
||||
&& features.enabled(Feature::ShellTool)
|
||||
&& features.enabled(Feature::UnifiedExec)
|
||||
&& features.enabled(Feature::ShellZshFork)
|
||||
&& features.enabled(Feature::UnifiedExecZshFork)
|
||||
&& matches!(user_shell_type, ToolUserShellType::Zsh)
|
||||
&& let (Some(shell_zsh_path), Some(main_execve_wrapper_exe)) =
|
||||
(shell_zsh_path, main_execve_wrapper_exe)
|
||||
|
||||
@@ -1,142 +1,10 @@
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn model_with_shell_type(shell_type: ConfigShellToolType) -> ModelInfo {
|
||||
ModelInfo {
|
||||
slug: "test-model".to_string(),
|
||||
display_name: "Test Model".to_string(),
|
||||
description: None,
|
||||
default_reasoning_level: None,
|
||||
supported_reasoning_levels: Vec::new(),
|
||||
shell_type,
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 0,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
service_tiers: Vec::new(),
|
||||
default_service_tier: None,
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
model_messages: None,
|
||||
include_skills_usage_instructions: false,
|
||||
include_plugin_usage_instructions: false,
|
||||
include_apps_usage_instructions: false,
|
||||
supports_reasoning_summary_parameter: true,
|
||||
default_reasoning_summary: Default::default(),
|
||||
support_verbosity: false,
|
||||
default_verbosity: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::tokens(/*limit*/ 1024),
|
||||
supports_image_detail_original: false,
|
||||
context_window: None,
|
||||
max_context_window: None,
|
||||
auto_compact_token_limit: None,
|
||||
comp_hash: None,
|
||||
effective_context_window_percent: 95,
|
||||
experimental_supported_tools: Vec::new(),
|
||||
input_modalities: codex_protocol::openai_models::default_input_modalities(),
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
use_responses_lite: false,
|
||||
node_repl_auto_review_required: false,
|
||||
node_repl_disabled: false,
|
||||
auto_review_model_override: None,
|
||||
model_specialty: None,
|
||||
tool_mode: None,
|
||||
multi_agent_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn shell_features() -> Features {
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ShellTool);
|
||||
features.disable(Feature::ShellZshFork);
|
||||
features.disable(Feature::UnifiedExec);
|
||||
features.disable(Feature::UnifiedExecZshFork);
|
||||
features
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_type_is_derived_from_model_and_feature_gates() {
|
||||
let model = model_with_shell_type(ConfigShellToolType::UnifiedExec);
|
||||
let mut features = shell_features();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
assert_eq!(
|
||||
shell_type_for_model_and_features(&model, &features),
|
||||
ConfigShellToolType::UnifiedExec
|
||||
);
|
||||
features.disable(Feature::ShellTool);
|
||||
assert_eq!(
|
||||
shell_type_for_model_and_features(&model, &features),
|
||||
ConfigShellToolType::Disabled
|
||||
);
|
||||
|
||||
features.enable(Feature::ShellTool);
|
||||
features.disable(Feature::UnifiedExec);
|
||||
assert_eq!(
|
||||
shell_type_for_model_and_features(&model, &features),
|
||||
ConfigShellToolType::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_type_respects_disabled_model_capability() {
|
||||
let model = model_with_shell_type(ConfigShellToolType::Disabled);
|
||||
assert_eq!(
|
||||
shell_type_for_model_and_features(&model, &shell_features()),
|
||||
ConfigShellToolType::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_feature_mode_follows_composition_dependencies() {
|
||||
let mut features = shell_features();
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::Disabled
|
||||
);
|
||||
|
||||
features.enable(Feature::UnifiedExec);
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::UnifiedExecZshFork);
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::ShellZshFork);
|
||||
features.disable(Feature::UnifiedExecZshFork);
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::UnifiedExecZshFork);
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::ZshFork
|
||||
);
|
||||
|
||||
features.disable(Feature::ShellTool);
|
||||
assert_eq!(
|
||||
unified_exec_feature_mode_for_features(&features),
|
||||
UnifiedExecFeatureMode::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_modes_follow_default_mode_feature() {
|
||||
let mut features = Features::with_defaults();
|
||||
@@ -154,12 +22,14 @@ fn request_user_input_modes_follow_default_mode_feature() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_shell_mode_uses_zsh_fork_only_when_all_inputs_match() {
|
||||
fn unified_exec_shell_mode_respects_feature_and_policy_gates() {
|
||||
let exe = std::env::current_exe().expect("current exe path");
|
||||
let shell = exe.clone();
|
||||
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ShellTool);
|
||||
features.enable(Feature::ShellZshFork);
|
||||
let mode = UnifiedExecShellMode::for_session(
|
||||
UnifiedExecFeatureMode::ZshFork,
|
||||
&features,
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&shell),
|
||||
Some(&exe),
|
||||
@@ -170,9 +40,46 @@ fn unified_exec_shell_mode_uses_zsh_fork_only_when_all_inputs_match() {
|
||||
assert_eq!(mode, UnifiedExecShellMode::Direct);
|
||||
}
|
||||
|
||||
features.disable(Feature::ShellZshFork);
|
||||
assert_eq!(
|
||||
UnifiedExecShellMode::for_session(
|
||||
UnifiedExecFeatureMode::Direct,
|
||||
&features,
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&shell),
|
||||
Some(&exe),
|
||||
),
|
||||
UnifiedExecShellMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::ShellZshFork);
|
||||
features.disable(Feature::UnifiedExecZshFork);
|
||||
assert_eq!(
|
||||
UnifiedExecShellMode::for_session(
|
||||
&features,
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&shell),
|
||||
Some(&exe),
|
||||
),
|
||||
UnifiedExecShellMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::UnifiedExecZshFork);
|
||||
features.disable(Feature::UnifiedExec);
|
||||
assert_eq!(
|
||||
UnifiedExecShellMode::for_session(
|
||||
&features,
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&shell),
|
||||
Some(&exe),
|
||||
),
|
||||
UnifiedExecShellMode::Direct
|
||||
);
|
||||
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.disable(Feature::ShellTool);
|
||||
assert_eq!(
|
||||
UnifiedExecShellMode::for_session(
|
||||
&features,
|
||||
ToolUserShellType::Zsh,
|
||||
Some(&shell),
|
||||
Some(&exe),
|
||||
|
||||
Reference in New Issue
Block a user