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}