copy_path_tool.rs

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