1use super::edit_file_tool::{SensitiveSettingsKind, sensitive_settings_kind};
2use agent_client_protocol::ToolKind;
3use agent_settings::AgentSettings;
4use anyhow::{Context as _, Result, anyhow};
5use futures::FutureExt as _;
6use gpui::{App, Entity, SharedString, Task};
7use project::Project;
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10use settings::Settings;
11use std::path::Path;
12use std::sync::Arc;
13use util::markdown::MarkdownInlineCode;
14
15use crate::{AgentTool, ToolCallEventStream, ToolPermissionDecision, decide_permission_for_path};
16
17/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
18///
19/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
20#[derive(Debug, Serialize, Deserialize, JsonSchema)]
21pub struct CreateDirectoryToolInput {
22 /// The path of the new directory.
23 ///
24 /// <example>
25 /// If the project has the following structure:
26 ///
27 /// - directory1/
28 /// - directory2/
29 ///
30 /// You can create a new directory by providing a path of "directory1/new_directory"
31 /// </example>
32 pub path: String,
33}
34
35pub struct CreateDirectoryTool {
36 project: Entity<Project>,
37}
38
39impl CreateDirectoryTool {
40 pub fn new(project: Entity<Project>) -> Self {
41 Self { project }
42 }
43}
44
45impl AgentTool for CreateDirectoryTool {
46 type Input = CreateDirectoryToolInput;
47 type Output = String;
48
49 const NAME: &'static str = "create_directory";
50
51 fn kind() -> ToolKind {
52 ToolKind::Read
53 }
54
55 fn initial_title(
56 &self,
57 input: Result<Self::Input, serde_json::Value>,
58 _cx: &mut App,
59 ) -> SharedString {
60 if let Ok(input) = input {
61 format!("Create directory {}", MarkdownInlineCode(&input.path)).into()
62 } else {
63 "Create directory".into()
64 }
65 }
66
67 fn run(
68 self: Arc<Self>,
69 input: Self::Input,
70 event_stream: ToolCallEventStream,
71 cx: &mut App,
72 ) -> Task<Result<Self::Output>> {
73 let settings = AgentSettings::get_global(cx);
74 let mut decision = decide_permission_for_path(Self::NAME, &input.path, settings);
75 let sensitive_kind = sensitive_settings_kind(Path::new(&input.path));
76
77 if matches!(decision, ToolPermissionDecision::Allow) && sensitive_kind.is_some() {
78 decision = ToolPermissionDecision::Confirm;
79 }
80
81 let authorize = match decision {
82 ToolPermissionDecision::Allow => None,
83 ToolPermissionDecision::Deny(reason) => {
84 return Task::ready(Err(anyhow!("{}", reason)));
85 }
86 ToolPermissionDecision::Confirm => {
87 let title = format!("Create directory {}", MarkdownInlineCode(&input.path));
88 let title = match &sensitive_kind {
89 Some(SensitiveSettingsKind::Local) => format!("{title} (local settings)"),
90 Some(SensitiveSettingsKind::Global) => format!("{title} (settings)"),
91 None => title,
92 };
93 let context = crate::ToolPermissionContext {
94 tool_name: Self::NAME.to_string(),
95 input_values: vec![input.path.clone()],
96 };
97 Some(event_stream.authorize(title, context, cx))
98 }
99 };
100
101 let destination_path: Arc<str> = input.path.as_str().into();
102
103 let project = self.project.clone();
104 cx.spawn(async move |cx| {
105 if let Some(authorize) = authorize {
106 authorize.await?;
107 }
108
109 let create_entry = project.update(cx, |project, cx| {
110 match project.find_project_path(&input.path, cx) {
111 Some(project_path) => Ok(project.create_entry(project_path, true, cx)),
112 None => Err(anyhow!("Path to create was outside the project")),
113 }
114 })?;
115
116 futures::select! {
117 result = create_entry.fuse() => {
118 result.with_context(|| format!("Creating directory {destination_path}"))?;
119 }
120 _ = event_stream.cancelled_by_user().fuse() => {
121 anyhow::bail!("Create directory cancelled by user");
122 }
123 }
124
125 Ok(format!("Created directory {destination_path}"))
126 })
127 }
128}