create_directory_tool.rs

  1use agent_client_protocol::ToolKind;
  2use agent_settings::AgentSettings;
  3use anyhow::{Context as _, Result, anyhow};
  4use futures::FutureExt as _;
  5use gpui::{App, Entity, SharedString, Task};
  6use project::Project;
  7use schemars::JsonSchema;
  8use serde::{Deserialize, Serialize};
  9use settings::Settings;
 10use std::sync::Arc;
 11use util::markdown::MarkdownInlineCode;
 12
 13use crate::{
 14    AgentTool, ToolCallEventStream, ToolPermissionDecision, decide_permission_from_settings,
 15};
 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    fn name() -> &'static str {
 50        "create_directory"
 51    }
 52
 53    fn kind() -> ToolKind {
 54        ToolKind::Read
 55    }
 56
 57    fn initial_title(
 58        &self,
 59        input: Result<Self::Input, serde_json::Value>,
 60        _cx: &mut App,
 61    ) -> SharedString {
 62        if let Ok(input) = input {
 63            format!("Create directory {}", MarkdownInlineCode(&input.path)).into()
 64        } else {
 65            "Create directory".into()
 66        }
 67    }
 68
 69    fn run(
 70        self: Arc<Self>,
 71        input: Self::Input,
 72        event_stream: ToolCallEventStream,
 73        cx: &mut App,
 74    ) -> Task<Result<Self::Output>> {
 75        let settings = AgentSettings::get_global(cx);
 76        let decision = decide_permission_from_settings(Self::name(), &input.path, settings);
 77
 78        let authorize = match decision {
 79            ToolPermissionDecision::Allow => None,
 80            ToolPermissionDecision::Deny(reason) => {
 81                return Task::ready(Err(anyhow!("{}", reason)));
 82            }
 83            ToolPermissionDecision::Confirm => Some(event_stream.authorize(
 84                format!("Create directory {}", MarkdownInlineCode(&input.path)),
 85                cx,
 86            )),
 87        };
 88
 89        let project_path = match self.project.read(cx).find_project_path(&input.path, cx) {
 90            Some(project_path) => project_path,
 91            None => {
 92                return Task::ready(Err(anyhow!("Path to create was outside the project")));
 93            }
 94        };
 95        let destination_path: Arc<str> = input.path.as_str().into();
 96
 97        let create_entry = self.project.update(cx, |project, cx| {
 98            project.create_entry(project_path.clone(), true, cx)
 99        });
100
101        cx.spawn(async move |_cx| {
102            if let Some(authorize) = authorize {
103                authorize.await?;
104            }
105
106            futures::select! {
107                result = create_entry.fuse() => {
108                    result.with_context(|| format!("Creating directory {destination_path}"))?;
109                }
110                _ = event_stream.cancelled_by_user().fuse() => {
111                    anyhow::bail!("Create directory cancelled by user");
112                }
113            }
114
115            Ok(format!("Created directory {destination_path}"))
116        })
117    }
118}