create_file_tool.rs

  1use crate::schema::json_schema_for;
  2use anyhow::{Result, anyhow};
  3use assistant_tool::{ActionLog, Tool, ToolResult};
  4use gpui::{App, Entity, Task};
  5use language_model::LanguageModelRequestMessage;
  6use language_model::LanguageModelToolSchemaFormat;
  7use project::Project;
  8use schemars::JsonSchema;
  9use serde::{Deserialize, Serialize};
 10use std::sync::Arc;
 11use ui::IconName;
 12use util::markdown::MarkdownString;
 13
 14#[derive(Debug, Serialize, Deserialize, JsonSchema)]
 15pub struct CreateFileToolInput {
 16    /// The path where the file should be created.
 17    ///
 18    /// <example>
 19    /// If the project has the following structure:
 20    ///
 21    /// - directory1/
 22    /// - directory2/
 23    ///
 24    /// You can create a new file by providing a path of "directory1/new_file.txt"
 25    /// </example>
 26    pub path: String,
 27
 28    /// The text contents of the file to create.
 29    ///
 30    /// <example>
 31    /// To create a file with the text "Hello, World!", provide contents of "Hello, World!"
 32    /// </example>
 33    pub contents: String,
 34}
 35
 36pub struct CreateFileTool;
 37
 38impl Tool for CreateFileTool {
 39    fn name(&self) -> String {
 40        "create_file".into()
 41    }
 42
 43    fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
 44        false
 45    }
 46
 47    fn description(&self) -> String {
 48        include_str!("./create_file_tool/description.md").into()
 49    }
 50
 51    fn icon(&self) -> IconName {
 52        IconName::FileCreate
 53    }
 54
 55    fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
 56        json_schema_for::<CreateFileToolInput>(format)
 57    }
 58
 59    fn ui_text(&self, input: &serde_json::Value) -> String {
 60        match serde_json::from_value::<CreateFileToolInput>(input.clone()) {
 61            Ok(input) => {
 62                let path = MarkdownString::inline_code(&input.path);
 63                format!("Create file {path}")
 64            }
 65            Err(_) => "Create file".to_string(),
 66        }
 67    }
 68
 69    fn run(
 70        self: Arc<Self>,
 71        input: serde_json::Value,
 72        _messages: &[LanguageModelRequestMessage],
 73        project: Entity<Project>,
 74        action_log: Entity<ActionLog>,
 75        cx: &mut App,
 76    ) -> ToolResult {
 77        let input = match serde_json::from_value::<CreateFileToolInput>(input) {
 78            Ok(input) => input,
 79            Err(err) => return Task::ready(Err(anyhow!(err))).into(),
 80        };
 81        let project_path = match project.read(cx).find_project_path(&input.path, cx) {
 82            Some(project_path) => project_path,
 83            None => {
 84                return Task::ready(Err(anyhow!("Path to create was outside the project"))).into();
 85            }
 86        };
 87        let contents: Arc<str> = input.contents.as_str().into();
 88        let destination_path: Arc<str> = input.path.as_str().into();
 89
 90        cx.spawn(async move |cx| {
 91            let buffer = project
 92                .update(cx, |project, cx| {
 93                    project.open_buffer(project_path.clone(), cx)
 94                })?
 95                .await
 96                .map_err(|err| anyhow!("Unable to open buffer for {destination_path}: {err}"))?;
 97            cx.update(|cx| {
 98                buffer.update(cx, |buffer, cx| buffer.set_text(contents, cx));
 99                action_log.update(cx, |action_log, cx| {
100                    action_log.will_create_buffer(buffer.clone(), cx)
101                });
102            })?;
103
104            project
105                .update(cx, |project, cx| project.save_buffer(buffer, cx))?
106                .await
107                .map_err(|err| anyhow!("Unable to save buffer for {destination_path}: {err}"))?;
108
109            Ok(format!("Created file {destination_path}"))
110        })
111        .into()
112    }
113}