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