1use agent_client_protocol::ToolKind;
2use anyhow::{Context as _, Result, anyhow};
3use futures::FutureExt as _;
4use gpui::{App, Entity, SharedString, Task};
5use project::Project;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use util::markdown::MarkdownInlineCode;
10
11use crate::{AgentTool, ToolCallEventStream};
12
13/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
14///
15/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
16#[derive(Debug, Serialize, Deserialize, JsonSchema)]
17pub struct CreateDirectoryToolInput {
18 /// The path of the new directory.
19 ///
20 /// <example>
21 /// If the project has the following structure:
22 ///
23 /// - directory1/
24 /// - directory2/
25 ///
26 /// You can create a new directory by providing a path of "directory1/new_directory"
27 /// </example>
28 pub path: String,
29}
30
31pub struct CreateDirectoryTool {
32 project: Entity<Project>,
33}
34
35impl CreateDirectoryTool {
36 pub fn new(project: Entity<Project>) -> Self {
37 Self { project }
38 }
39}
40
41impl AgentTool for CreateDirectoryTool {
42 type Input = CreateDirectoryToolInput;
43 type Output = String;
44
45 fn name() -> &'static str {
46 "create_directory"
47 }
48
49 fn kind() -> ToolKind {
50 ToolKind::Read
51 }
52
53 fn initial_title(
54 &self,
55 input: Result<Self::Input, serde_json::Value>,
56 _cx: &mut App,
57 ) -> SharedString {
58 if let Ok(input) = input {
59 format!("Create directory {}", MarkdownInlineCode(&input.path)).into()
60 } else {
61 "Create directory".into()
62 }
63 }
64
65 fn run(
66 self: Arc<Self>,
67 input: Self::Input,
68 event_stream: ToolCallEventStream,
69 cx: &mut App,
70 ) -> Task<Result<Self::Output>> {
71 let project_path = match self.project.read(cx).find_project_path(&input.path, cx) {
72 Some(project_path) => project_path,
73 None => {
74 return Task::ready(Err(anyhow!("Path to create was outside the project")));
75 }
76 };
77 let destination_path: Arc<str> = input.path.as_str().into();
78
79 let create_entry = self.project.update(cx, |project, cx| {
80 project.create_entry(project_path.clone(), true, cx)
81 });
82
83 cx.spawn(async move |_cx| {
84 futures::select! {
85 result = create_entry.fuse() => {
86 result.with_context(|| format!("Creating directory {destination_path}"))?;
87 }
88 _ = event_stream.cancelled_by_user().fuse() => {
89 anyhow::bail!("Create directory cancelled by user");
90 }
91 }
92
93 Ok(format!("Created directory {destination_path}"))
94 })
95 }
96}