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