copy_path_tool.rs

  1use crate::schema::json_schema_for;
  2use anyhow::{Context as _, Result, anyhow};
  3use assistant_tool::{ActionLog, Tool, ToolResult};
  4use gpui::AnyWindowHandle;
  5use gpui::{App, AppContext, Entity, Task};
  6use language_model::LanguageModel;
  7use language_model::{LanguageModelRequest, 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, _: &Entity<Project>, _: &App) -> bool {
 48        false
 49    }
 50
 51    fn may_perform_edits(&self) -> bool {
 52        true
 53    }
 54
 55    fn description(&self) -> String {
 56        include_str!("./copy_path_tool/description.md").into()
 57    }
 58
 59    fn icon(&self) -> IconName {
 60        IconName::ToolCopy
 61    }
 62
 63    fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
 64        json_schema_for::<CopyPathToolInput>(format)
 65    }
 66
 67    fn ui_text(&self, input: &serde_json::Value) -> String {
 68        match serde_json::from_value::<CopyPathToolInput>(input.clone()) {
 69            Ok(input) => {
 70                let src = MarkdownInlineCode(&input.source_path);
 71                let dest = MarkdownInlineCode(&input.destination_path);
 72                format!("Copy {src} to {dest}")
 73            }
 74            Err(_) => "Copy path".to_string(),
 75        }
 76    }
 77
 78    fn run(
 79        self: Arc<Self>,
 80        input: serde_json::Value,
 81        _request: Arc<LanguageModelRequest>,
 82        project: Entity<Project>,
 83        _action_log: Entity<ActionLog>,
 84        _model: Arc<dyn LanguageModel>,
 85        _window: Option<AnyWindowHandle>,
 86        cx: &mut App,
 87    ) -> ToolResult {
 88        let input = match serde_json::from_value::<CopyPathToolInput>(input) {
 89            Ok(input) => input,
 90            Err(err) => return Task::ready(Err(anyhow!(err))).into(),
 91        };
 92        let copy_task = project.update(cx, |project, cx| {
 93            match project
 94                .find_project_path(&input.source_path, cx)
 95                .and_then(|project_path| project.entry_for_path(&project_path, cx))
 96            {
 97                Some(entity) => match project.find_project_path(&input.destination_path, cx) {
 98                    Some(project_path) => {
 99                        project.copy_entry(entity.id, None, project_path.path, cx)
100                    }
101                    None => Task::ready(Err(anyhow!(
102                        "Destination path {} was outside the project.",
103                        input.destination_path
104                    ))),
105                },
106                None => Task::ready(Err(anyhow!(
107                    "Source path {} was not found in the project.",
108                    input.source_path
109                ))),
110            }
111        });
112
113        cx.background_spawn(async move {
114            let _ = copy_task.await.with_context(|| {
115                format!(
116                    "Copying {} to {}",
117                    input.source_path, input.destination_path
118                )
119            })?;
120            Ok(format!("Copied {} to {}", input.source_path, input.destination_path).into())
121        })
122        .into()
123    }
124}