Add Create Directory Tool (#27505)

Richard Feldman created

`mkdir -p` but it works cross-platform and uses project abstractions.

<img width="629" alt="Screenshot 2025-03-26 at 11 02 37 AM"
src="https://github.com/user-attachments/assets/9ef58d53-3343-4c94-a8f3-b82ab942611b"
/>

Release Notes:

- N/A

Change summary

crates/assistant_tools/src/assistant_tools.rs                   |  3 
crates/assistant_tools/src/create_directory_tool.rs             | 89 +++
crates/assistant_tools/src/create_directory_tool/description.md |  3 
3 files changed, 95 insertions(+)

Detailed changes

crates/assistant_tools/src/assistant_tools.rs 🔗

@@ -1,5 +1,6 @@
 mod bash_tool;
 mod copy_path_tool;
+mod create_directory_tool;
 mod create_file_tool;
 mod delete_path_tool;
 mod diagnostics_tool;
@@ -24,6 +25,7 @@ use http_client::HttpClientWithUrl;
 use move_path_tool::MovePathTool;
 
 use crate::bash_tool::BashTool;
+use crate::create_directory_tool::CreateDirectoryTool;
 use crate::create_file_tool::CreateFileTool;
 use crate::delete_path_tool::DeletePathTool;
 use crate::diagnostics_tool::DiagnosticsTool;
@@ -43,6 +45,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
 
     let registry = ToolRegistry::global(cx);
     registry.register_tool(BashTool);
+    registry.register_tool(CreateDirectoryTool);
     registry.register_tool(CreateFileTool);
     registry.register_tool(CopyPathTool);
     registry.register_tool(DeletePathTool);

crates/assistant_tools/src/create_directory_tool.rs 🔗

@@ -0,0 +1,89 @@
+use anyhow::{anyhow, Result};
+use assistant_tool::{ActionLog, Tool};
+use gpui::{App, Entity, Task};
+use language_model::LanguageModelRequestMessage;
+use project::Project;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+use ui::IconName;
+use util::markdown::MarkdownString;
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+pub struct CreateDirectoryToolInput {
+    /// The path of the new directory.
+    ///
+    /// <example>
+    /// If the project has the following structure:
+    ///
+    /// - directory1/
+    /// - directory2/
+    ///
+    /// You can create a new directory by providing a path of "directory1/new_directory"
+    /// </example>
+    pub path: String,
+}
+
+pub struct CreateDirectoryTool;
+
+impl Tool for CreateDirectoryTool {
+    fn name(&self) -> String {
+        "create-directory".into()
+    }
+
+    fn needs_confirmation(&self) -> bool {
+        true
+    }
+
+    fn description(&self) -> String {
+        include_str!("./create_directory_tool/description.md").into()
+    }
+
+    fn icon(&self) -> IconName {
+        IconName::Folder
+    }
+
+    fn input_schema(&self) -> serde_json::Value {
+        let schema = schemars::schema_for!(CreateDirectoryToolInput);
+        serde_json::to_value(&schema).unwrap()
+    }
+
+    fn ui_text(&self, input: &serde_json::Value) -> String {
+        match serde_json::from_value::<CreateDirectoryToolInput>(input.clone()) {
+            Ok(input) => {
+                format!("Create directory `{}`", MarkdownString::escape(&input.path))
+            }
+            Err(_) => "Create directory".to_string(),
+        }
+    }
+
+    fn run(
+        self: Arc<Self>,
+        input: serde_json::Value,
+        _messages: &[LanguageModelRequestMessage],
+        project: Entity<Project>,
+        _action_log: Entity<ActionLog>,
+        cx: &mut App,
+    ) -> Task<Result<String>> {
+        let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {
+            Ok(input) => input,
+            Err(err) => return Task::ready(Err(anyhow!(err))),
+        };
+        let project_path = match project.read(cx).find_project_path(&input.path, cx) {
+            Some(project_path) => project_path,
+            None => return Task::ready(Err(anyhow!("Path to create was outside the project"))),
+        };
+        let destination_path: Arc<str> = input.path.as_str().into();
+
+        cx.spawn(async move |cx| {
+            project
+                .update(cx, |project, cx| {
+                    project.create_entry(project_path.clone(), true, cx)
+                })?
+                .await
+                .map_err(|err| anyhow!("Unable to create directory {destination_path}: {err}"))?;
+
+            Ok(format!("Created directory {destination_path}"))
+        })
+    }
+}

crates/assistant_tools/src/create_directory_tool/description.md 🔗

@@ -0,0 +1,3 @@
+Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
+
+This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project.