copy_path_tool.rs

  1use anyhow::{anyhow, Result};
  2use assistant_tool::{ActionLog, Tool};
  3use gpui::{App, AppContext, Entity, Task};
  4use language_model::LanguageModelRequestMessage;
  5use project::Project;
  6use schemars::JsonSchema;
  7use serde::{Deserialize, Serialize};
  8use std::sync::Arc;
  9use ui::IconName;
 10use util::markdown::MarkdownString;
 11
 12#[derive(Debug, Serialize, Deserialize, JsonSchema)]
 13pub struct CopyPathToolInput {
 14    /// The source path of the file or directory to copy.
 15    /// If a directory is specified, its contents will be copied recursively (like `cp -r`).
 16    ///
 17    /// <example>
 18    /// If the project has the following files:
 19    ///
 20    /// - directory1/a/something.txt
 21    /// - directory2/a/things.txt
 22    /// - directory3/a/other.txt
 23    ///
 24    /// You can copy the first file by providing a source_path of "directory1/a/something.txt"
 25    /// </example>
 26    pub source_path: String,
 27
 28    /// The destination path where the file or directory should be copied to.
 29    ///
 30    /// <example>
 31    /// To copy "directory1/a/something.txt" to "directory2/b/copy.txt",
 32    /// provide a destination_path of "directory2/b/copy.txt"
 33    /// </example>
 34    pub destination_path: String,
 35}
 36
 37pub struct CopyPathTool;
 38
 39impl Tool for CopyPathTool {
 40    fn name(&self) -> String {
 41        "copy-path".into()
 42    }
 43
 44    fn needs_confirmation(&self) -> bool {
 45        true
 46    }
 47
 48    fn description(&self) -> String {
 49        include_str!("./copy_path_tool/description.md").into()
 50    }
 51
 52    fn icon(&self) -> IconName {
 53        IconName::Clipboard
 54    }
 55
 56    fn input_schema(&self) -> serde_json::Value {
 57        let schema = schemars::schema_for!(CopyPathToolInput);
 58        serde_json::to_value(&schema).unwrap()
 59    }
 60
 61    fn ui_text(&self, input: &serde_json::Value) -> String {
 62        match serde_json::from_value::<CopyPathToolInput>(input.clone()) {
 63            Ok(input) => {
 64                let src = MarkdownString::escape(&input.source_path);
 65                let dest = MarkdownString::escape(&input.destination_path);
 66                format!("Copy `{src}` to `{dest}`")
 67            }
 68            Err(_) => "Copy path".to_string(),
 69        }
 70    }
 71
 72    fn run(
 73        self: Arc<Self>,
 74        input: serde_json::Value,
 75        _messages: &[LanguageModelRequestMessage],
 76        project: Entity<Project>,
 77        _action_log: Entity<ActionLog>,
 78        cx: &mut App,
 79    ) -> Task<Result<String>> {
 80        let input = match serde_json::from_value::<CopyPathToolInput>(input) {
 81            Ok(input) => input,
 82            Err(err) => return Task::ready(Err(anyhow!(err))),
 83        };
 84        let copy_task = 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            match copy_task.await {
107                Ok(_) => Ok(format!(
108                    "Copied {} to {}",
109                    input.source_path, input.destination_path
110                )),
111                Err(err) => Err(anyhow!(
112                    "Failed to copy {} to {}: {}",
113                    input.source_path,
114                    input.destination_path,
115                    err
116                )),
117            }
118        })
119    }
120}