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