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, _: &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        _request: Arc<LanguageModelRequest>,
 78        project: Entity<Project>,
 79        _action_log: Entity<ActionLog>,
 80        _model: Arc<dyn LanguageModel>,
 81        _window: Option<AnyWindowHandle>,
 82        cx: &mut App,
 83    ) -> ToolResult {
 84        let input = match serde_json::from_value::<CopyPathToolInput>(input) {
 85            Ok(input) => input,
 86            Err(err) => return Task::ready(Err(anyhow!(err))).into(),
 87        };
 88        let copy_task = project.update(cx, |project, cx| {
 89            match project
 90                .find_project_path(&input.source_path, cx)
 91                .and_then(|project_path| project.entry_for_path(&project_path, cx))
 92            {
 93                Some(entity) => match project.find_project_path(&input.destination_path, cx) {
 94                    Some(project_path) => {
 95                        project.copy_entry(entity.id, None, project_path.path, cx)
 96                    }
 97                    None => Task::ready(Err(anyhow!(
 98                        "Destination path {} was outside the project.",
 99                        input.destination_path
100                    ))),
101                },
102                None => Task::ready(Err(anyhow!(
103                    "Source path {} was not found in the project.",
104                    input.source_path
105                ))),
106            }
107        });
108
109        cx.background_spawn(async move {
110            let _ = copy_task.await.with_context(|| {
111                format!(
112                    "Copying {} to {}",
113                    input.source_path, input.destination_path
114                )
115            })?;
116            Ok(format!("Copied {} to {}", input.source_path, input.destination_path).into())
117        })
118        .into()
119    }
120}