From 349f57381f7b3acd6c960f46585d4b1d77159327 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Mar 2025 16:17:12 +0100 Subject: [PATCH] Add ListDirectoryTool (#26549) Release Notes: - N/A --- crates/assistant_tools/src/assistant_tools.rs | 3 + .../src/list_directory_tool.rs | 88 +++++++++++++++++++ .../src/list_directory_tool/description.md | 1 + crates/assistant_tools/src/read_file_tool.rs | 4 +- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 crates/assistant_tools/src/list_directory_tool.rs create mode 100644 crates/assistant_tools/src/list_directory_tool/description.md diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index 0d3453cd2a3f379c3229ead1d5a1ab48284526ba..7cb18cb789f1305f6e9e3a77eebf00ae32ef49c5 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -1,4 +1,5 @@ mod edit_files_tool; +mod list_directory_tool; mod now_tool; mod read_file_tool; @@ -6,6 +7,7 @@ use assistant_tool::ToolRegistry; use gpui::App; use crate::edit_files_tool::EditFilesTool; +use crate::list_directory_tool::ListDirectoryTool; use crate::now_tool::NowTool; use crate::read_file_tool::ReadFileTool; @@ -15,5 +17,6 @@ pub fn init(cx: &mut App) { let registry = ToolRegistry::global(cx); registry.register_tool(NowTool); registry.register_tool(ReadFileTool); + registry.register_tool(ListDirectoryTool); registry.register_tool(EditFilesTool); } diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs new file mode 100644 index 0000000000000000000000000000000000000000..d572ecb60ac3ebdcb05af4519a35395555ef965c --- /dev/null +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -0,0 +1,88 @@ +use anyhow::{anyhow, Result}; +use assistant_tool::Tool; +use gpui::{App, Entity, Task}; +use language_model::LanguageModelRequestMessage; +use project::Project; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::{fmt::Write, path::Path, sync::Arc}; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct ListDirectoryToolInput { + /// The relative path of the directory to list. + /// + /// This path should never be absolute, and the first component + /// of the path should always be a top-level directory in a project. + /// + /// + /// If the project has the following top-level directories: + /// + /// - directory1 + /// - directory2 + /// + /// You can list the contents of `directory1` by using the path `directory1`. + /// + /// + /// + /// If the project has the following top-level directories: + /// + /// - foo + /// - bar + /// + /// If you wanna list contents in the directory `foo/baz`, you should use the path `foo/baz`. + /// + pub path: Arc, +} + +pub struct ListDirectoryTool; + +impl Tool for ListDirectoryTool { + fn name(&self) -> String { + "list-directory".into() + } + + fn description(&self) -> String { + include_str!("./list_directory_tool/description.md").into() + } + + fn input_schema(&self) -> serde_json::Value { + let schema = schemars::schema_for!(ListDirectoryToolInput); + serde_json::to_value(&schema).unwrap() + } + + fn run( + self: Arc, + input: serde_json::Value, + _messages: &[LanguageModelRequestMessage], + project: Entity, + cx: &mut App, + ) -> Task> { + let input = match serde_json::from_value::(input) { + Ok(input) => input, + Err(err) => return Task::ready(Err(anyhow!(err))), + }; + + let Some(worktree_root_name) = input.path.components().next() else { + return Task::ready(Err(anyhow!("Invalid path"))); + }; + let Some(worktree) = project + .read(cx) + .worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx) + else { + return Task::ready(Err(anyhow!("Directory not found in the project"))); + }; + let path = input.path.strip_prefix(worktree_root_name).unwrap(); + let mut output = String::new(); + for entry in worktree.read(cx).child_entries(path) { + writeln!( + output, + "{}", + Path::new(worktree_root_name.as_os_str()) + .join(&entry.path) + .display(), + ) + .unwrap(); + } + Task::ready(Ok(output)) + } +} diff --git a/crates/assistant_tools/src/list_directory_tool/description.md b/crates/assistant_tools/src/list_directory_tool/description.md new file mode 100644 index 0000000000000000000000000000000000000000..a7d364ae6348479db645cd44098a298a14e12c2a --- /dev/null +++ b/crates/assistant_tools/src/list_directory_tool/description.md @@ -0,0 +1 @@ +Lists files and directories in a given path. diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 4c08bbe4e745e65276793b8c9ff884ad12185a98..c23f52b4230a884b6106824f385daf04e1d34a02 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -16,13 +16,15 @@ pub struct ReadFileToolInput { /// This path should never be absolute, and the first component /// of the path should always be a top-level directory in a project. /// - /// For example, if the project has the following top-level directories: + /// + /// If the project has the following top-level directories: /// /// - directory1 /// - directory2 /// /// If you wanna access `file.txt` in `directory1`, you should use the path `directory1/file.txt`. /// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`. + /// pub path: Arc, }