create_file_tool.rs

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