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