From 782a99cde519ef2c693c9b28a1a9eca08d6bda40 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 25 Mar 2026 15:01:30 +0000 Subject: [PATCH] chore: remove list_dir handler Co-authored-by: Codex --- codex-rs/core/src/tools/handlers/list_dir.rs | 271 ------------------- codex-rs/core/src/tools/handlers/mod.rs | 2 - codex-rs/core/src/tools/spec.rs | 64 ----- 3 files changed, 337 deletions(-) delete mode 100644 codex-rs/core/src/tools/handlers/list_dir.rs diff --git a/codex-rs/core/src/tools/handlers/list_dir.rs b/codex-rs/core/src/tools/handlers/list_dir.rs deleted file mode 100644 index fd461e82e5..0000000000 --- a/codex-rs/core/src/tools/handlers/list_dir.rs +++ /dev/null @@ -1,271 +0,0 @@ -use std::collections::VecDeque; -use std::ffi::OsStr; -use std::fs::FileType; -use std::path::Path; -use std::path::PathBuf; - -use async_trait::async_trait; -use codex_utils_string::take_bytes_at_char_boundary; -use serde::Deserialize; -use tokio::fs; - -use crate::function_tool::FunctionCallError; -use crate::tools::context::FunctionToolOutput; -use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolPayload; -use crate::tools::handlers::parse_arguments; -use crate::tools::registry::ToolHandler; -use crate::tools::registry::ToolKind; - -pub struct ListDirHandler; - -const MAX_ENTRY_LENGTH: usize = 500; -const INDENTATION_SPACES: usize = 2; - -fn default_offset() -> usize { - 1 -} - -fn default_limit() -> usize { - 25 -} - -fn default_depth() -> usize { - 2 -} - -#[derive(Deserialize)] -struct ListDirArgs { - dir_path: String, - #[serde(default = "default_offset")] - offset: usize, - #[serde(default = "default_limit")] - limit: usize, - #[serde(default = "default_depth")] - depth: usize, -} - -#[async_trait] -impl ToolHandler for ListDirHandler { - type Output = FunctionToolOutput; - - fn kind(&self) -> ToolKind { - ToolKind::Function - } - - async fn handle(&self, invocation: ToolInvocation) -> Result { - let ToolInvocation { payload, .. } = invocation; - - let arguments = match payload { - ToolPayload::Function { arguments } => arguments, - _ => { - return Err(FunctionCallError::RespondToModel( - "list_dir handler received unsupported payload".to_string(), - )); - } - }; - - let args: ListDirArgs = parse_arguments(&arguments)?; - - let ListDirArgs { - dir_path, - offset, - limit, - depth, - } = args; - - if offset == 0 { - return Err(FunctionCallError::RespondToModel( - "offset must be a 1-indexed entry number".to_string(), - )); - } - - if limit == 0 { - return Err(FunctionCallError::RespondToModel( - "limit must be greater than zero".to_string(), - )); - } - - if depth == 0 { - return Err(FunctionCallError::RespondToModel( - "depth must be greater than zero".to_string(), - )); - } - - let path = PathBuf::from(&dir_path); - if !path.is_absolute() { - return Err(FunctionCallError::RespondToModel( - "dir_path must be an absolute path".to_string(), - )); - } - - let entries = list_dir_slice(&path, offset, limit, depth).await?; - let mut output = Vec::with_capacity(entries.len() + 1); - output.push(format!("Absolute path: {}", path.display())); - output.extend(entries); - Ok(FunctionToolOutput::from_text(output.join("\n"), Some(true))) - } -} - -async fn list_dir_slice( - path: &Path, - offset: usize, - limit: usize, - depth: usize, -) -> Result, FunctionCallError> { - let mut entries = Vec::new(); - collect_entries(path, Path::new(""), depth, &mut entries).await?; - - if entries.is_empty() { - return Ok(Vec::new()); - } - - entries.sort_unstable_by(|a, b| a.name.cmp(&b.name)); - - let start_index = offset - 1; - if start_index >= entries.len() { - return Err(FunctionCallError::RespondToModel( - "offset exceeds directory entry count".to_string(), - )); - } - - let remaining_entries = entries.len() - start_index; - let capped_limit = limit.min(remaining_entries); - let end_index = start_index + capped_limit; - let selected_entries = &entries[start_index..end_index]; - let mut formatted = Vec::with_capacity(selected_entries.len()); - - for entry in selected_entries { - formatted.push(format_entry_line(entry)); - } - - if end_index < entries.len() { - formatted.push(format!("More than {capped_limit} entries found")); - } - - Ok(formatted) -} - -async fn collect_entries( - dir_path: &Path, - relative_prefix: &Path, - depth: usize, - entries: &mut Vec, -) -> Result<(), FunctionCallError> { - let mut queue = VecDeque::new(); - queue.push_back((dir_path.to_path_buf(), relative_prefix.to_path_buf(), depth)); - - while let Some((current_dir, prefix, remaining_depth)) = queue.pop_front() { - let mut read_dir = fs::read_dir(¤t_dir).await.map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to read directory: {err}")) - })?; - - let mut dir_entries = Vec::new(); - - while let Some(entry) = read_dir.next_entry().await.map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to read directory: {err}")) - })? { - let file_type = entry.file_type().await.map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to inspect entry: {err}")) - })?; - - let file_name = entry.file_name(); - let relative_path = if prefix.as_os_str().is_empty() { - PathBuf::from(&file_name) - } else { - prefix.join(&file_name) - }; - - let display_name = format_entry_component(&file_name); - let display_depth = prefix.components().count(); - let sort_key = format_entry_name(&relative_path); - let kind = DirEntryKind::from(&file_type); - dir_entries.push(( - entry.path(), - relative_path, - kind, - DirEntry { - name: sort_key, - display_name, - depth: display_depth, - kind, - }, - )); - } - - dir_entries.sort_unstable_by(|a, b| a.3.name.cmp(&b.3.name)); - - for (entry_path, relative_path, kind, dir_entry) in dir_entries { - if kind == DirEntryKind::Directory && remaining_depth > 1 { - queue.push_back((entry_path, relative_path, remaining_depth - 1)); - } - entries.push(dir_entry); - } - } - - Ok(()) -} - -fn format_entry_name(path: &Path) -> String { - let normalized = path.to_string_lossy().replace("\\", "/"); - if normalized.len() > MAX_ENTRY_LENGTH { - take_bytes_at_char_boundary(&normalized, MAX_ENTRY_LENGTH).to_string() - } else { - normalized - } -} - -fn format_entry_component(name: &OsStr) -> String { - let normalized = name.to_string_lossy(); - if normalized.len() > MAX_ENTRY_LENGTH { - take_bytes_at_char_boundary(&normalized, MAX_ENTRY_LENGTH).to_string() - } else { - normalized.to_string() - } -} - -fn format_entry_line(entry: &DirEntry) -> String { - let indent = " ".repeat(entry.depth * INDENTATION_SPACES); - let mut name = entry.display_name.clone(); - match entry.kind { - DirEntryKind::Directory => name.push('/'), - DirEntryKind::Symlink => name.push('@'), - DirEntryKind::Other => name.push('?'), - DirEntryKind::File => {} - } - format!("{indent}{name}") -} - -#[derive(Clone)] -struct DirEntry { - name: String, - display_name: String, - depth: usize, - kind: DirEntryKind, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum DirEntryKind { - Directory, - File, - Symlink, - Other, -} - -impl From<&FileType> for DirEntryKind { - fn from(file_type: &FileType) -> Self { - if file_type.is_symlink() { - DirEntryKind::Symlink - } else if file_type.is_dir() { - DirEntryKind::Directory - } else if file_type.is_file() { - DirEntryKind::File - } else { - DirEntryKind::Other - } - } -} - -#[cfg(test)] -#[path = "list_dir_tests.rs"] -mod tests; diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 9b14f7ee50..8c3959dd3c 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -4,7 +4,6 @@ mod artifacts; mod dynamic; mod grep_files; mod js_repl; -mod list_dir; mod mcp; mod mcp_resource; pub(crate) mod multi_agents; @@ -44,7 +43,6 @@ pub use dynamic::DynamicToolHandler; pub use grep_files::GrepFilesHandler; pub use js_repl::JsReplHandler; pub use js_repl::JsReplResetHandler; -pub use list_dir::ListDirHandler; pub use mcp::McpHandler; pub use mcp_resource::McpResourceHandler; pub use plan::PlanHandler; diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 5686b4bb78..3661277e68 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -2204,54 +2204,6 @@ fn create_read_file_tool() -> ToolSpec { }) } -fn create_list_dir_tool() -> ToolSpec { - let properties = BTreeMap::from([ - ( - "dir_path".to_string(), - JsonSchema::String { - description: Some("Absolute path to the directory to list.".to_string()), - }, - ), - ( - "offset".to_string(), - JsonSchema::Number { - description: Some( - "The entry number to start listing from. Must be 1 or greater.".to_string(), - ), - }, - ), - ( - "limit".to_string(), - JsonSchema::Number { - description: Some("The maximum number of entries to return.".to_string()), - }, - ), - ( - "depth".to_string(), - JsonSchema::Number { - description: Some( - "The maximum directory depth to traverse. Must be 1 or greater.".to_string(), - ), - }, - ), - ]); - - ToolSpec::Function(ResponsesApiTool { - name: "list_dir".to_string(), - description: - "Lists entries in a local directory with 1-indexed entry numbers and simple type labels." - .to_string(), - strict: false, - defer_loading: None, - parameters: JsonSchema::Object { - properties, - required: Some(vec!["dir_path".to_string()]), - additional_properties: Some(false.into()), - }, - output_schema: None, - }) -} - fn create_js_repl_tool() -> ToolSpec { // Keep JS input freeform, but block the most common malformed payload shapes // (JSON wrappers, quoted strings, and markdown fences) before they reach the @@ -2754,7 +2706,6 @@ pub(crate) fn build_specs_with_discoverable_tools( use crate::tools::handlers::GrepFilesHandler; use crate::tools::handlers::JsReplHandler; use crate::tools::handlers::JsReplResetHandler; - use crate::tools::handlers::ListDirHandler; use crate::tools::handlers::McpHandler; use crate::tools::handlers::McpResourceHandler; use crate::tools::handlers::PlanHandler; @@ -3057,21 +3008,6 @@ pub(crate) fn build_specs_with_discoverable_tools( builder.register_handler("read_file", read_file_handler); } - if config - .experimental_supported_tools - .iter() - .any(|tool| tool == "list_dir") - { - let list_dir_handler = Arc::new(ListDirHandler); - push_tool_spec( - &mut builder, - create_list_dir_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler("list_dir", list_dir_handler); - } - if config .experimental_supported_tools .contains(&"test_sync_tool".to_string())