codex_tools/
tool_config.rs1use codex_features::Feature;
2use codex_features::Features;
3use codex_protocol::config_types::ModeKind;
4use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
5use codex_protocol::openai_models::ConfigShellToolType;
6use codex_protocol::openai_models::ModelInfo;
7use codex_utils_absolute_path::AbsolutePathBuf;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Copy, Eq, PartialEq)]
11pub enum ShellCommandBackendConfig {
12 Classic,
13 ZshFork,
14}
15
16#[derive(Debug, Clone, Copy, Eq, PartialEq)]
17pub enum UnifiedExecFeatureMode {
18 Disabled,
25 Direct,
26 ZshFork,
27}
28
29#[derive(Debug, Clone, Copy, Eq, PartialEq)]
30pub enum ToolUserShellType {
31 Zsh,
32 Bash,
33 PowerShell,
34 Sh,
35 Cmd,
36}
37
38pub fn request_user_input_available_modes(features: &Features) -> Vec<ModeKind> {
39 TUI_VISIBLE_COLLABORATION_MODES
40 .into_iter()
41 .filter(|mode| {
42 mode.allows_request_user_input()
43 || (features.enabled(Feature::DefaultModeRequestUserInput)
44 && *mode == ModeKind::Default)
45 })
46 .collect()
47}
48
49pub fn shell_command_backend_for_features(features: &Features) -> ShellCommandBackendConfig {
50 if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
51 ShellCommandBackendConfig::ZshFork
52 } else {
53 ShellCommandBackendConfig::Classic
54 }
55}
56
57pub fn unified_exec_feature_mode_for_features(features: &Features) -> UnifiedExecFeatureMode {
68 if !features.enabled(Feature::ShellTool) || !features.enabled(Feature::UnifiedExec) {
69 UnifiedExecFeatureMode::Disabled
70 } else if features.enabled(Feature::ShellZshFork) {
71 if features.enabled(Feature::UnifiedExecZshFork) {
72 UnifiedExecFeatureMode::ZshFork
73 } else {
74 UnifiedExecFeatureMode::Disabled
75 }
76 } else {
77 UnifiedExecFeatureMode::Direct
78 }
79}
80
81pub fn shell_type_for_model_and_features(
82 model_info: &ModelInfo,
83 features: &Features,
84) -> ConfigShellToolType {
85 let unified_exec_feature_mode = unified_exec_feature_mode_for_features(features);
86 let unified_exec_disabled =
87 matches!(unified_exec_feature_mode, UnifiedExecFeatureMode::Disabled);
88 let model_shell_type = match model_info.shell_type {
89 ConfigShellToolType::UnifiedExec if unified_exec_disabled => {
90 ConfigShellToolType::ShellCommand
91 }
92 ConfigShellToolType::Default | ConfigShellToolType::Local => {
93 ConfigShellToolType::ShellCommand
94 }
95 other => other,
96 };
97 let shell_command_type = match shell_command_backend_for_features(features) {
98 ShellCommandBackendConfig::Classic => model_shell_type,
99 ShellCommandBackendConfig::ZshFork => ConfigShellToolType::ShellCommand,
100 };
101
102 if !features.enabled(Feature::ShellTool) {
103 ConfigShellToolType::Disabled
104 } else {
105 match unified_exec_feature_mode {
106 UnifiedExecFeatureMode::Disabled => shell_command_type,
107 UnifiedExecFeatureMode::Direct | UnifiedExecFeatureMode::ZshFork => {
108 if codex_utils_pty::conpty_supported() {
109 ConfigShellToolType::UnifiedExec
110 } else {
111 ConfigShellToolType::ShellCommand
112 }
113 }
114 }
115 }
116}
117
118#[derive(Debug, Clone, Eq, PartialEq)]
119pub enum UnifiedExecShellMode {
120 Direct,
121 ZshFork(ZshForkConfig),
122}
123
124#[derive(Debug, Clone, Eq, PartialEq)]
125pub struct ZshForkConfig {
126 pub shell_zsh_path: AbsolutePathBuf,
127 pub main_execve_wrapper_exe: AbsolutePathBuf,
128}
129
130impl UnifiedExecShellMode {
131 pub fn for_session(
132 feature_mode: UnifiedExecFeatureMode,
133 user_shell_type: ToolUserShellType,
134 shell_zsh_path: Option<&PathBuf>,
135 main_execve_wrapper_exe: Option<&PathBuf>,
136 ) -> Self {
137 if cfg!(unix)
138 && matches!(feature_mode, UnifiedExecFeatureMode::ZshFork)
139 && matches!(user_shell_type, ToolUserShellType::Zsh)
140 && let (Some(shell_zsh_path), Some(main_execve_wrapper_exe)) =
141 (shell_zsh_path, main_execve_wrapper_exe)
142 && let (Ok(shell_zsh_path), Ok(main_execve_wrapper_exe)) = (
143 AbsolutePathBuf::try_from(shell_zsh_path.as_path()).inspect_err(|err| {
144 tracing::warn!(
145 "Failed to convert shell_zsh_path `{shell_zsh_path:?}`: {err:?}"
146 )
147 }),
148 AbsolutePathBuf::try_from(main_execve_wrapper_exe.as_path()).inspect_err(
149 |err| {
150 tracing::warn!(
151 "Failed to convert main_execve_wrapper_exe `{main_execve_wrapper_exe:?}`: {err:?}"
152 )
153 },
154 ),
155 )
156 {
157 Self::ZshFork(ZshForkConfig {
158 shell_zsh_path,
159 main_execve_wrapper_exe,
160 })
161 } else {
162 Self::Direct
163 }
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum ToolEnvironmentMode {
169 None,
170 Single,
171 Multiple,
172}
173
174impl ToolEnvironmentMode {
175 pub fn from_count(count: usize) -> Self {
176 match count {
177 0 => Self::None,
178 1 => Self::Single,
179 _ => Self::Multiple,
180 }
181 }
182
183 pub fn has_environment(self) -> bool {
184 !matches!(self, Self::None)
185 }
186}
187
188#[cfg(test)]
189#[path = "tool_config_tests.rs"]
190mod tests;