copy_path_tool.rs

  1use crate::{AgentTool, ToolCallEventStream};
  2use agent_client_protocol::ToolKind;
  3use anyhow::{Context as _, Result, anyhow};
  4use gpui::{App, AppContext, Entity, SharedString, Task};
  5use project::Project;
  6use schemars::JsonSchema;
  7use serde::{Deserialize, Serialize};
  8use std::sync::Arc;
  9use util::markdown::MarkdownInlineCode;
 10
 11/// Copies a file or directory in the project, and returns confirmation that the
 12/// copy succeeded.
 13///
 14/// Directory contents will be copied recursively (like `cp -r`).
 15///
 16/// This tool should be used when it's desirable to create a copy of a file or
 17/// directory without modifying the original. It's much more efficient than
 18/// doing this by separately reading and then writing the file or directory's
 19/// contents, so this tool should be preferred over that approach whenever
 20/// copying is the goal.
 21#[derive(Debug, Serialize, Deserialize, JsonSchema)]
 22pub struct CopyPathToolInput {
 23    /// The source path of the file or directory to copy.
 24    /// If a directory is specified, its contents will be copied recursively (like `cp -r`).
 25    ///
 26    /// <example>
 27    /// If the project has the following files:
 28    ///
 29    /// - directory1/a/something.txt
 30    /// - directory2/a/things.txt
 31    /// - directory3/a/other.txt
 32    ///
 33    /// You can copy the first file by providing a source_path of "directory1/a/something.txt"
 34    /// </example>
 35    pub source_path: String,
 36
 37    /// The destination path where the file or directory should be copied to.
 38    ///
 39    /// <example>
 40    /// To copy "directory1/a/something.txt" to "directory2/b/copy.txt",
 41    /// provide a destination_path of "directory2/b/copy.txt"
 42    /// </example>
 43    pub destination_path: String,
 44}
 45
 46pub struct CopyPathTool {
 47    project: Entity<Project>,
 48}
 49
 50impl CopyPathTool {
 51    pub fn new(project: Entity<Project>) -> Self {
 52        Self { project }
 53    }
 54}
 55
 56impl AgentTool for CopyPathTool {
 57    type Input = CopyPathToolInput;
 58    type Output = String;
 59
 60    fn name(&self) -> SharedString {
 61        "copy_path".into()
 62    }
 63
 64    fn kind(&self) -> ToolKind {
 65        ToolKind::Move
 66    }
 67
 68    fn initial_title(&self, input: Result<Self::Input, serde_json::Value>) -> ui::SharedString {
 69        if let Ok(input) = input {
 70            let src = MarkdownInlineCode(&input.source_path);
 71            let dest = MarkdownInlineCode(&input.destination_path);
 72            format!("Copy {src} to {dest}").into()
 73        } else {
 74            "Copy path".into()
 75        }
 76    }
 77
 78    fn run(
 79        self: Arc<Self>,
 80        input: Self::Input,
 81        _event_stream: ToolCallEventStream,
 82        cx: &mut App,
 83    ) -> Task<Result<Self::Output>> {
 84        let copy_task = self.project.update(cx, |project, cx| {
 85            match project
 86                .find_project_path(&input.source_path, cx)
 87                .and_then(|project_path| project.entry_for_path(&project_path, cx))
 88            {
 89                Some(entity) => match project.find_project_path(&input.destination_path, cx) {
 90                    Some(project_path) => {
 91                        project.copy_entry(entity.id, None, project_path.path, cx)
 92                    }
 93                    None => Task::ready(Err(anyhow!(
 94                        "Destination path {} was outside the project.",
 95                        input.destination_path
 96                    ))),
 97                },
 98                None => Task::ready(Err(anyhow!(
 99                    "Source path {} was not found in the project.",
100                    input.source_path
101                ))),
102            }
103        });
104
105        cx.background_spawn(async move {
106            let _ = copy_task.await.with_context(|| {
107                format!(
108                    "Copying {} to {}",
109                    input.source_path, input.destination_path
110                )
111            })?;
112            Ok(format!(
113                "Copied {} to {}",
114                input.source_path, input.destination_path
115            ))
116        })
117    }
118}