diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index a790398580617d41596861febf0425cb6f6e9a30..3364f8d2606658cdd59b44b98fcbf7796f6e3aba 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -3,6 +3,7 @@ mod delete_path_tool; mod edit_files_tool; mod list_directory_tool; mod now_tool; +mod path_search_tool; mod read_file_tool; mod regex_search; @@ -14,6 +15,7 @@ use crate::delete_path_tool::DeletePathTool; use crate::edit_files_tool::EditFilesTool; use crate::list_directory_tool::ListDirectoryTool; use crate::now_tool::NowTool; +use crate::path_search_tool::PathSearchTool; use crate::read_file_tool::ReadFileTool; use crate::regex_search::RegexSearchTool; @@ -25,6 +27,7 @@ pub fn init(cx: &mut App) { registry.register_tool(ReadFileTool); registry.register_tool(ListDirectoryTool); registry.register_tool(EditFilesTool); + registry.register_tool(PathSearchTool); registry.register_tool(RegexSearchTool); registry.register_tool(DeletePathTool); registry.register_tool(BashTool); diff --git a/crates/assistant_tools/src/path_search_tool.rs b/crates/assistant_tools/src/path_search_tool.rs new file mode 100644 index 0000000000000000000000000000000000000000..42ca8fa12c5438b8c2b637685e3dbfdb6d7cfd85 --- /dev/null +++ b/crates/assistant_tools/src/path_search_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::{path::PathBuf, sync::Arc}; +use util::paths::PathMatcher; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct PathSearchToolInput { + /// The glob to search all project paths for. + /// + /// + /// If the project has the following top-level directories: + /// + /// - directory1/a/something.txt + /// - directory2/a/things.txt + /// - directory3/a/other.txt + /// + /// You can get back the first two paths by providing a glob of "*thing*.txt" + /// + pub glob: String, +} + +pub struct PathSearchTool; + +impl Tool for PathSearchTool { + fn name(&self) -> String { + "path-search".into() + } + + fn description(&self) -> String { + include_str!("./path_search_tool/description.md").into() + } + + fn input_schema(&self) -> serde_json::Value { + let schema = schemars::schema_for!(PathSearchToolInput); + serde_json::to_value(&schema).unwrap() + } + + fn run( + self: Arc, + input: serde_json::Value, + _messages: &[LanguageModelRequestMessage], + project: Entity, + cx: &mut App, + ) -> Task> { + let glob = match serde_json::from_value::(input) { + Ok(input) => input.glob, + Err(err) => return Task::ready(Err(anyhow!(err))), + }; + let path_matcher = match PathMatcher::new(&[glob.clone()]) { + Ok(matcher) => matcher, + Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {}", err))), + }; + + let mut matches = Vec::new(); + + for worktree_handle in project.read(cx).worktrees(cx) { + let worktree = worktree_handle.read(cx); + let root_name = worktree.root_name(); + + // Don't consider ignored entries. + for entry in worktree.entries(false, 0) { + if path_matcher.is_match(&entry.path) { + matches.push( + PathBuf::from(root_name) + .join(&entry.path) + .to_string_lossy() + .to_string(), + ); + } + } + } + + if matches.is_empty() { + Task::ready(Ok(format!( + "No paths in the project matched the glob {glob:?}" + ))) + } else { + // Sort to group entries in the same directory together. + matches.sort(); + Task::ready(Ok(matches.join("\n"))) + } + } +} diff --git a/crates/assistant_tools/src/path_search_tool/description.md b/crates/assistant_tools/src/path_search_tool/description.md new file mode 100644 index 0000000000000000000000000000000000000000..654102471450b628fd437258b7422c7140bba86c --- /dev/null +++ b/crates/assistant_tools/src/path_search_tool/description.md @@ -0,0 +1 @@ +Returns all the paths in the project which match the given glob.