@@ -4,6 +4,7 @@ mod diagnostics_tool;
mod edit_files_tool;
mod fetch_tool;
mod list_directory_tool;
+mod move_path_tool;
mod now_tool;
mod path_search_tool;
mod read_file_tool;
@@ -15,6 +16,7 @@ use std::sync::Arc;
use assistant_tool::ToolRegistry;
use gpui::App;
use http_client::HttpClientWithUrl;
+use move_path_tool::MovePathTool;
use crate::bash_tool::BashTool;
use crate::delete_path_tool::DeletePathTool;
@@ -35,6 +37,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
let registry = ToolRegistry::global(cx);
registry.register_tool(BashTool);
registry.register_tool(DeletePathTool);
+ registry.register_tool(MovePathTool);
registry.register_tool(DiagnosticsTool);
registry.register_tool(EditFilesTool);
registry.register_tool(ListDirectoryTool);
@@ -0,0 +1,125 @@
+use anyhow::{anyhow, Result};
+use assistant_tool::{ActionLog, Tool};
+use gpui::{App, AppContext, Entity, Task};
+use language_model::LanguageModelRequestMessage;
+use project::Project;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::{path::Path, sync::Arc};
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+pub struct MovePathToolInput {
+ /// The source path of the file or directory to move/rename.
+ ///
+ /// <example>
+ /// If the project has the following files:
+ ///
+ /// - directory1/a/something.txt
+ /// - directory2/a/things.txt
+ /// - directory3/a/other.txt
+ ///
+ /// You can move the first file by providing a source_path of "directory1/a/something.txt"
+ /// </example>
+ pub source_path: String,
+
+ /// The destination path where the file or directory should be moved/renamed to.
+ /// If the paths are the same except for the filename, then this will be a rename.
+ ///
+ /// <example>
+ /// To move "directory1/a/something.txt" to "directory2/b/renamed.txt",
+ /// provide a destination_path of "directory2/b/renamed.txt"
+ /// </example>
+ pub destination_path: String,
+}
+
+pub struct MovePathTool;
+
+impl Tool for MovePathTool {
+ fn name(&self) -> String {
+ "move-path".into()
+ }
+
+ fn needs_confirmation(&self) -> bool {
+ true
+ }
+
+ fn description(&self) -> String {
+ include_str!("./move_path_tool/description.md").into()
+ }
+
+ fn input_schema(&self) -> serde_json::Value {
+ let schema = schemars::schema_for!(MovePathToolInput);
+ serde_json::to_value(&schema).unwrap()
+ }
+
+ fn ui_text(&self, input: &serde_json::Value) -> String {
+ match serde_json::from_value::<MovePathToolInput>(input.clone()) {
+ Ok(input) => {
+ let src = input.source_path.as_str();
+ let dest = input.destination_path.as_str();
+ let src_path = Path::new(src);
+ let dest_path = Path::new(dest);
+
+ match dest_path
+ .file_name()
+ .and_then(|os_str| os_str.to_os_string().into_string().ok())
+ {
+ Some(filename) if src_path.parent() == dest_path.parent() => {
+ format!("Rename `{src}` to `{filename}`")
+ }
+ _ => {
+ format!("Move `{src}` to `{dest}`")
+ }
+ }
+ }
+ Err(_) => "Move path".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::<MovePathToolInput>(input) {
+ Ok(input) => input,
+ Err(err) => return Task::ready(Err(anyhow!(err))),
+ };
+ let rename_task = project.update(cx, |project, cx| {
+ match project
+ .find_project_path(&input.source_path, cx)
+ .and_then(|project_path| project.entry_for_path(&project_path, cx))
+ {
+ Some(entity) => match project.find_project_path(&input.destination_path, cx) {
+ Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
+ None => Task::ready(Err(anyhow!(
+ "Destination path {} was outside the project.",
+ input.destination_path
+ ))),
+ },
+ None => Task::ready(Err(anyhow!(
+ "Source path {} was not found in the project.",
+ input.source_path
+ ))),
+ }
+ });
+
+ cx.background_spawn(async move {
+ match rename_task.await {
+ Ok(_) => Ok(format!(
+ "Moved {} to {}",
+ input.source_path, input.destination_path
+ )),
+ Err(err) => Err(anyhow!(
+ "Failed to move {} to {}: {}",
+ input.source_path,
+ input.destination_path,
+ err
+ )),
+ }
+ })
+ }
+}
@@ -0,0 +1,5 @@
+Moves or rename a file or directory in the project, and returns confirmation that the move succeeded.
+If the source and destination directories are the same, but the filename is different, this performs
+a rename. Otherwise, it performs a move.
+
+This tool should be used when it's desirable to move or rename a file or directory without changing its contents at all.