use crate::{AgentTool, ToolCallEventStream}; use agent_client_protocol::ToolKind; use anyhow::{Context as _, Result, anyhow}; use gpui::{App, AppContext, Entity, SharedString, Task}; use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::sync::Arc; use util::markdown::MarkdownInlineCode; /// 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. #[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 { project: Entity, } impl CopyPathTool { pub fn new(project: Entity) -> Self { Self { project } } } impl AgentTool for CopyPathTool { type Input = CopyPathToolInput; type Output = String; fn name(&self) -> SharedString { "copy_path".into() } fn kind(&self) -> ToolKind { ToolKind::Move } fn initial_title(&self, input: Result) -> ui::SharedString { if let Ok(input) = input { let src = MarkdownInlineCode(&input.source_path); let dest = MarkdownInlineCode(&input.destination_path); format!("Copy {src} to {dest}").into() } else { "Copy path".into() } } fn run( self: Arc, input: Self::Input, _event_stream: ToolCallEventStream, cx: &mut App, ) -> Task> { let copy_task = self.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 { let _ = copy_task.await.with_context(|| { format!( "Copying {} to {}", input.source_path, input.destination_path ) })?; Ok(format!( "Copied {} to {}", input.source_path, input.destination_path )) }) } }