copy_path_tool.rs

  1use crate::schema::json_schema_for;
  2use anyhow::{Result, anyhow};
  3use assistant_tool::{ActionLog, Tool, ToolResult};
  4use gpui::AnyWindowHandle;
  5use gpui::{App, AppContext, Entity, Task};
  6use language_model::LanguageModelRequestMessage;
  7use language_model::LanguageModelToolSchemaFormat;
  8use project::Project;
  9use schemars::JsonSchema;
 10use serde::{Deserialize, Serialize};
 11use std::sync::Arc;
 12use ui::IconName;
 13use util::markdown::MarkdownInlineCode;
 14
 15#[derive(Debug, Serialize, Deserialize, JsonSchema)]
 16pub struct CopyPathToolInput {
 17    /// The source path of the file or directory to copy.
 18    /// If a directory is specified, its contents will be copied recursively (like `cp -r`).
 19    ///
 20    /// <example>
 21    /// If the project has the following files:
 22    ///
 23    /// - directory1/a/something.txt
 24    /// - directory2/a/things.txt
 25    /// - directory3/a/other.txt
 26    ///
 27    /// You can copy the first file by providing a source_path of "directory1/a/something.txt"
 28    /// </example>
 29    pub source_path: String,
 30
 31    /// The destination path where the file or directory should be copied to.
 32    ///
 33    /// <example>
 34    /// To copy "directory1/a/something.txt" to "directory2/b/copy.txt",
 35    /// provide a destination_path of "directory2/b/copy.txt"
 36    /// </example>
 37    pub destination_path: String,
 38}
 39
 40pub struct CopyPathTool;
 41
 42impl Tool for CopyPathTool {
 43    fn name(&self) -> String {
 44        "copy_path".into()
 45    }
 46
 47    fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
 48        false
 49    }
 50
 51    fn description(&self) -> String {
 52        include_str!("./copy_path_tool/description.md").into()
 53    }
 54
 55    fn icon(&self) -> IconName {
 56        IconName::Clipboard
 57    }
 58
 59    fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
 60        json_schema_for::<CopyPathToolInput>(format)
 61    }
 62
 63    fn ui_text(&self, input: &serde_json::Value) -> String {
 64        match serde_json::from_value::<CopyPathToolInput>(input.clone()) {
 65            Ok(input) => {
 66                let src = MarkdownInlineCode(&input.source_path);
 67                let dest = MarkdownInlineCode(&input.destination_path);
 68                format!("Copy {src} to {dest}")
 69            }
 70            Err(_) => "Copy path".to_string(),
 71        }
 72    }
 73
 74    fn run(
 75        self: Arc<Self>,
 76        input: serde_json::Value,
 77        _messages: &[LanguageModelRequestMessage],
 78        project: Entity<Project>,
 79        _action_log: Entity<ActionLog>,
 80        _window: Option<AnyWindowHandle>,
 81        cx: &mut App,
 82    ) -> ToolResult {
 83        let input = match serde_json::from_value::<CopyPathToolInput>(input) {
 84            Ok(input) => input,
 85            Err(err) => return Task::ready(Err(anyhow!(err))).into(),
 86        };
 87        let copy_task = project.update(cx, |project, cx| {
 88            match project
 89                .find_project_path(&input.source_path, cx)
 90                .and_then(|project_path| project.entry_for_path(&project_path, cx))
 91            {
 92                Some(entity) => match project.find_project_path(&input.destination_path, cx) {
 93                    Some(project_path) => {
 94                        project.copy_entry(entity.id, None, project_path.path, cx)
 95                    }
 96                    None => Task::ready(Err(anyhow!(
 97                        "Destination path {} was outside the project.",
 98                        input.destination_path
 99                    ))),
100                },
101                None => Task::ready(Err(anyhow!(
102                    "Source path {} was not found in the project.",
103                    input.source_path
104                ))),
105            }
106        });
107
108        cx.background_spawn(async move {
109            match copy_task.await {
110                Ok(_) => Ok(format!(
111                    "Copied {} to {}",
112                    input.source_path, input.destination_path
113                )),
114                Err(err) => Err(anyhow!(
115                    "Failed to copy {} to {}: {}",
116                    input.source_path,
117                    input.destination_path,
118                    err
119                )),
120            }
121        })
122        .into()
123    }
124}