From e9e6529df4c158f299e7077daf33136b345a6074 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 24 Mar 2025 21:21:55 -0400 Subject: [PATCH] Add copy-path tool (#27371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2025-03-24 at 11 01 10 AM Release Notes: - N/A --- crates/assistant_tools/src/assistant_tools.rs | 3 + crates/assistant_tools/src/copy_path_tool.rs | 114 ++++++++++++++++++ .../src/copy_path_tool/description.md | 6 + 3 files changed, 123 insertions(+) create mode 100644 crates/assistant_tools/src/copy_path_tool.rs create mode 100644 crates/assistant_tools/src/copy_path_tool/description.md diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index 53611c8fbfbbe5200c691ff1c4151af7c02fef99..fd2c19ba914cc98055333dfee0736ccb763db94c 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -1,4 +1,5 @@ mod bash_tool; +mod copy_path_tool; mod delete_path_tool; mod diagnostics_tool; mod edit_files_tool; @@ -14,6 +15,7 @@ mod thinking_tool; use std::sync::Arc; use assistant_tool::ToolRegistry; +use copy_path_tool::CopyPathTool; use gpui::App; use http_client::HttpClientWithUrl; use move_path_tool::MovePathTool; @@ -36,6 +38,7 @@ pub fn init(http_client: Arc, cx: &mut App) { let registry = ToolRegistry::global(cx); registry.register_tool(BashTool); + registry.register_tool(CopyPathTool); registry.register_tool(DeletePathTool); registry.register_tool(MovePathTool); registry.register_tool(DiagnosticsTool); diff --git a/crates/assistant_tools/src/copy_path_tool.rs b/crates/assistant_tools/src/copy_path_tool.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0f325102eaef090aa3f667287a5901d8dba767d --- /dev/null +++ b/crates/assistant_tools/src/copy_path_tool.rs @@ -0,0 +1,114 @@ +use anyhow::{anyhow, Result}; +use assistant_tool::{ActionLog, Tool}; +use gpui::{App, AppContext, Entity, Task}; +use language_model::LanguageModelRequestMessage; +use project::Project; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct CopyPathToolInput { + /// The source path of the file or directory to copy. + /// If a directory is specified, its contents will be copied recursively (like `cp -r`). + /// + /// + /// If the project has the following files: + /// + /// - directory1/a/something.txt + /// - directory2/a/things.txt + /// - directory3/a/other.txt + /// + /// You can copy the first file by providing a source_path of "directory1/a/something.txt" + /// + pub source_path: String, + + /// The destination path where the file or directory should be copied to. + /// + /// + /// To copy "directory1/a/something.txt" to "directory2/b/copy.txt", + /// provide a destination_path of "directory2/b/copy.txt" + /// + pub destination_path: String, +} + +pub struct CopyPathTool; + +impl Tool for CopyPathTool { + fn name(&self) -> String { + "copy-path".into() + } + + fn needs_confirmation(&self) -> bool { + true + } + + fn description(&self) -> String { + include_str!("./copy_path_tool/description.md").into() + } + + fn input_schema(&self) -> serde_json::Value { + let schema = schemars::schema_for!(CopyPathToolInput); + serde_json::to_value(&schema).unwrap() + } + + fn ui_text(&self, input: &serde_json::Value) -> String { + match serde_json::from_value::(input.clone()) { + Ok(input) => { + let src = input.source_path.as_str(); + let dest = input.destination_path.as_str(); + format!("Copy `{src}` to `{dest}`") + } + Err(_) => "Copy path".to_string(), + } + } + + fn run( + self: Arc, + input: serde_json::Value, + _messages: &[LanguageModelRequestMessage], + project: Entity, + _action_log: 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 copy_task = project.update(cx, |project, cx| { + match project + .find_project_path(&input.source_path, cx) + .and_then(|project_path| project.entry_for_path(&project_path, cx)) + { + Some(entity) => match project.find_project_path(&input.destination_path, cx) { + Some(project_path) => { + project.copy_entry(entity.id, None, project_path.path, cx) + } + None => Task::ready(Err(anyhow!( + "Destination path {} was outside the project.", + input.destination_path + ))), + }, + None => Task::ready(Err(anyhow!( + "Source path {} was not found in the project.", + input.source_path + ))), + } + }); + + cx.background_spawn(async move { + match copy_task.await { + Ok(_) => Ok(format!( + "Copied {} to {}", + input.source_path, input.destination_path + )), + Err(err) => Err(anyhow!( + "Failed to copy {} to {}: {}", + input.source_path, + input.destination_path, + err + )), + } + }) + } +} diff --git a/crates/assistant_tools/src/copy_path_tool/description.md b/crates/assistant_tools/src/copy_path_tool/description.md new file mode 100644 index 0000000000000000000000000000000000000000..a5105e6f18c705e93aa9c30b9588f84dd8db542a --- /dev/null +++ b/crates/assistant_tools/src/copy_path_tool/description.md @@ -0,0 +1,6 @@ +Copies a file or directory in the project, and returns confirmation that the copy succeeded. +Directory contents will be copied recursively (like `cp -r`). + +This tool should be used when it's desirable to create a copy of a file or directory without modifying the original. +It's much more efficient than doing this by separately reading and then writing the file or directory's contents, +so this tool should be preferred over that approach whenever copying is the goal.