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}