Remove list_worktrees
and use relative paths instead (#26546)
Release Notes: - N/A
This commit is contained in:
parent
6bf6fcaa51
commit
41eb586ec8
13 changed files with 150 additions and 132 deletions
|
@ -1,5 +1,4 @@
|
|||
mod edit_files_tool;
|
||||
mod list_worktrees_tool;
|
||||
mod now_tool;
|
||||
mod read_file_tool;
|
||||
|
||||
|
@ -7,7 +6,6 @@ use assistant_tool::ToolRegistry;
|
|||
use gpui::App;
|
||||
|
||||
use crate::edit_files_tool::EditFilesTool;
|
||||
use crate::list_worktrees_tool::ListWorktreesTool;
|
||||
use crate::now_tool::NowTool;
|
||||
use crate::read_file_tool::ReadFileTool;
|
||||
|
||||
|
@ -16,7 +14,6 @@ pub fn init(cx: &mut App) {
|
|||
|
||||
let registry = ToolRegistry::global(cx);
|
||||
registry.register_tool(NowTool);
|
||||
registry.register_tool(ListWorktreesTool);
|
||||
registry.register_tool(ReadFileTool);
|
||||
registry.register_tool(EditFilesTool);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
mod edit_action;
|
||||
|
||||
use collections::HashSet;
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assistant_tool::Tool;
|
||||
use collections::HashSet;
|
||||
use edit_action::{EditAction, EditActionParser};
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use project::{Project, ProjectPath, WorktreeId};
|
||||
use project::{Project, ProjectPath};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditFilesToolInput {
|
||||
/// The ID of the worktree in which the files reside.
|
||||
pub worktree_id: usize,
|
||||
/// Instruct how to modify the files.
|
||||
/// High-level edit instructions. These will be interpreted by a smaller model,
|
||||
/// so explain the edits you want that model to make and to which files need changing.
|
||||
/// The description should be concise and clear. We will show this description to the user
|
||||
/// as well.
|
||||
///
|
||||
/// <example>
|
||||
/// If you want to rename a function you can say "Rename the function 'foo' to 'bar'".
|
||||
/// </example>
|
||||
///
|
||||
/// <example>
|
||||
/// If you want to add a new function you can say "Add a new method to the `User` struct that prints the age".
|
||||
/// </example>
|
||||
pub edit_instructions: String,
|
||||
}
|
||||
|
||||
|
@ -90,10 +98,25 @@ impl Tool for EditFilesTool {
|
|||
|
||||
while let Some(chunk) = chunks.stream.next().await {
|
||||
for action in parser.parse_chunk(&chunk?) {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(input.worktree_id),
|
||||
path: Path::new(action.file_path()).into(),
|
||||
};
|
||||
let project_path = project.read_with(&cx, |project, cx| {
|
||||
let worktree_root_name = action
|
||||
.file_path()
|
||||
.components()
|
||||
.next()
|
||||
.context("Invalid path")?;
|
||||
let worktree = project
|
||||
.worktree_for_root_name(
|
||||
&worktree_root_name.as_os_str().to_string_lossy(),
|
||||
cx,
|
||||
)
|
||||
.context("Directory not found in project")?;
|
||||
anyhow::Ok(ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Arc::from(
|
||||
action.file_path().strip_prefix(worktree_root_name).unwrap(),
|
||||
),
|
||||
})
|
||||
})??;
|
||||
|
||||
let buffer = project
|
||||
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
Edit files in a worktree by providing its id and a description of how to modify the code to complete the request.
|
||||
Edit files in the current project.
|
||||
|
||||
Make instructions unambiguous and complete. Explain all needed code changes clearly and completely, but concisely. Just show the changes needed. DO NOT show the entire updated function/file/etc!
|
||||
When using this tool, you should suggest one coherent edit that can be made to the codebase.
|
||||
|
||||
When the set of edits you want to make is large or complex, feel free to invoke this tool multiple times, each time focusing on a specific change you wanna make.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use util::ResultExt;
|
||||
|
||||
/// Represents an edit action to be performed on a file.
|
||||
|
@ -5,16 +6,16 @@ use util::ResultExt;
|
|||
pub enum EditAction {
|
||||
/// Replace specific content in a file with new content
|
||||
Replace {
|
||||
file_path: String,
|
||||
file_path: PathBuf,
|
||||
old: String,
|
||||
new: String,
|
||||
},
|
||||
/// Write content to a file (create or overwrite)
|
||||
Write { file_path: String, content: String },
|
||||
Write { file_path: PathBuf, content: String },
|
||||
}
|
||||
|
||||
impl EditAction {
|
||||
pub fn file_path(&self) -> &str {
|
||||
pub fn file_path(&self) -> &Path {
|
||||
match self {
|
||||
EditAction::Replace { file_path, .. } => file_path,
|
||||
EditAction::Write { file_path, .. } => file_path,
|
||||
|
@ -180,7 +181,7 @@ impl EditActionParser {
|
|||
pop_carriage_return(&mut pre_fence_line);
|
||||
}
|
||||
|
||||
let file_path = String::from_utf8(pre_fence_line).log_err()?;
|
||||
let file_path = PathBuf::from(String::from_utf8(pre_fence_line).log_err()?);
|
||||
let content = String::from_utf8(std::mem::take(&mut self.new_bytes)).log_err()?;
|
||||
|
||||
if self.old_bytes.is_empty() {
|
||||
|
@ -374,7 +375,7 @@ fn replacement() {}
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {}".to_string(),
|
||||
new: "fn replacement() {}".to_string(),
|
||||
}
|
||||
|
@ -401,7 +402,7 @@ fn replacement() {}
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {}".to_string(),
|
||||
new: "fn replacement() {}".to_string(),
|
||||
}
|
||||
|
@ -432,7 +433,7 @@ This change makes the function better.
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {}".to_string(),
|
||||
new: "fn replacement() {}".to_string(),
|
||||
}
|
||||
|
@ -470,7 +471,7 @@ fn new_util() -> bool { true }
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {}".to_string(),
|
||||
new: "fn replacement() {}".to_string(),
|
||||
}
|
||||
|
@ -478,7 +479,7 @@ fn new_util() -> bool { true }
|
|||
assert_eq!(
|
||||
actions[1],
|
||||
EditAction::Replace {
|
||||
file_path: "src/utils.rs".to_string(),
|
||||
file_path: PathBuf::from("src/utils.rs"),
|
||||
old: "fn old_util() -> bool { false }".to_string(),
|
||||
new: "fn new_util() -> bool { true }".to_string(),
|
||||
}
|
||||
|
@ -519,7 +520,7 @@ fn replacement() {
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {\n println!(\"This is the original function\");\n let x = 42;\n if x > 0 {\n println!(\"Positive number\");\n }\n}".to_string(),
|
||||
new: "fn replacement() {\n println!(\"This is the replacement function\");\n let x = 100;\n if x > 50 {\n println!(\"Large number\");\n } else {\n println!(\"Small number\");\n }\n}".to_string(),
|
||||
}
|
||||
|
@ -549,7 +550,7 @@ fn new_function() {
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Write {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
content: "fn new_function() {\n println!(\"This function is being added\");\n}"
|
||||
.to_string(),
|
||||
}
|
||||
|
@ -576,7 +577,7 @@ fn this_will_be_deleted() {
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn this_will_be_deleted() {\n println!(\"Deleting this function\");\n}"
|
||||
.to_string(),
|
||||
new: "".to_string(),
|
||||
|
@ -589,7 +590,7 @@ fn this_will_be_deleted() {
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old:
|
||||
"fn this_will_be_deleted() {\r\n println!(\"Deleting this function\");\r\n}"
|
||||
.to_string(),
|
||||
|
@ -655,7 +656,7 @@ fn replacement() {}"#;
|
|||
assert_eq!(
|
||||
actions3[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/main.rs".to_string(),
|
||||
file_path: PathBuf::from("src/main.rs"),
|
||||
old: "fn original() {}".to_string(),
|
||||
new: "fn replacement() {}".to_string(),
|
||||
}
|
||||
|
@ -747,7 +748,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "src/utils.rs".to_string(),
|
||||
file_path: PathBuf::from("src/utils.rs"),
|
||||
old: "fn utils_func() {}".to_string(),
|
||||
new: "fn new_utils_func() {}".to_string(),
|
||||
}
|
||||
|
@ -795,7 +796,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[0],
|
||||
EditAction::Replace {
|
||||
file_path: "mathweb/flask/app.py".to_string(),
|
||||
file_path: PathBuf::from("mathweb/flask/app.py"),
|
||||
old: "from flask import Flask".to_string(),
|
||||
new: "import math\nfrom flask import Flask".to_string(),
|
||||
}
|
||||
|
@ -804,7 +805,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[1],
|
||||
EditAction::Replace {
|
||||
file_path: "mathweb/flask/app.py".to_string(),
|
||||
file_path: PathBuf::from("mathweb/flask/app.py"),
|
||||
old: "def factorial(n):\n \"compute factorial\"\n\n if n == 0:\n return 1\n else:\n return n * factorial(n-1)\n".to_string(),
|
||||
new: "".to_string(),
|
||||
}
|
||||
|
@ -813,7 +814,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[2],
|
||||
EditAction::Replace {
|
||||
file_path: "mathweb/flask/app.py".to_string(),
|
||||
file_path: PathBuf::from("mathweb/flask/app.py"),
|
||||
old: " return str(factorial(n))".to_string(),
|
||||
new: " return str(math.factorial(n))".to_string(),
|
||||
}
|
||||
|
@ -822,7 +823,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[3],
|
||||
EditAction::Write {
|
||||
file_path: "hello.py".to_string(),
|
||||
file_path: PathBuf::from("hello.py"),
|
||||
content: "def hello():\n \"print a greeting\"\n\n print(\"hello\")"
|
||||
.to_string(),
|
||||
}
|
||||
|
@ -831,7 +832,7 @@ fn new_utils_func() {}
|
|||
assert_eq!(
|
||||
actions[4],
|
||||
EditAction::Replace {
|
||||
file_path: "main.py".to_string(),
|
||||
file_path: PathBuf::from("main.py"),
|
||||
old: "def hello():\n \"print a greeting\"\n\n print(\"hello\")".to_string(),
|
||||
new: "from hello import hello".to_string(),
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tool::Tool;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ListWorktreesToolInput {}
|
||||
|
||||
pub struct ListWorktreesTool;
|
||||
|
||||
impl Tool for ListWorktreesTool {
|
||||
fn name(&self) -> String {
|
||||
"list-worktrees".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Lists all worktrees in the current project. Use this tool when you need to find available worktrees and their IDs.".into()
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
serde_json::json!(
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: serde_json::Value,
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
cx.spawn(|cx| async move {
|
||||
cx.update(|cx| {
|
||||
#[derive(Debug, Serialize)]
|
||||
struct WorktreeInfo {
|
||||
id: usize,
|
||||
root_name: String,
|
||||
root_dir: Option<String>,
|
||||
}
|
||||
|
||||
let worktrees = project.update(cx, |project, cx| {
|
||||
project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| {
|
||||
worktree.read_with(cx, |worktree, _cx| WorktreeInfo {
|
||||
id: worktree.id().to_usize(),
|
||||
root_dir: worktree
|
||||
.root_dir()
|
||||
.map(|root_dir| root_dir.to_string_lossy().to_string()),
|
||||
root_name: worktree.root_name().to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
if worktrees.is_empty() {
|
||||
return Ok("No worktrees found in the current project.".to_string());
|
||||
}
|
||||
|
||||
let mut result = String::from("Worktrees in the current project:\n\n");
|
||||
for worktree in worktrees {
|
||||
result.push_str(&serde_json::to_string(&worktree)?);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
})?
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,17 +5,24 @@ use anyhow::{anyhow, Result};
|
|||
use assistant_tool::Tool;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::{Project, ProjectPath, WorktreeId};
|
||||
use project::{Project, ProjectPath};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadFileToolInput {
|
||||
/// The ID of the worktree in which the file resides.
|
||||
pub worktree_id: usize,
|
||||
/// The path to the file to read.
|
||||
/// The relative path of the file to read.
|
||||
///
|
||||
/// This path is relative to the worktree root, it must not be an absolute path.
|
||||
/// This path should never be absolute, and the first component
|
||||
/// of the path should always be a top-level directory in a project.
|
||||
///
|
||||
/// For example, if the project has the following top-level directories:
|
||||
///
|
||||
/// - directory1
|
||||
/// - directory2
|
||||
///
|
||||
/// If you wanna access `file.txt` in `directory1`, you should use the path `directory1/file.txt`.
|
||||
/// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
|
||||
pub path: Arc<Path>,
|
||||
}
|
||||
|
||||
|
@ -27,7 +34,7 @@ impl Tool for ReadFileTool {
|
|||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Reads the content of a file specified by a worktree ID and path. Use this tool when you need to access the contents of a file in the project.".into()
|
||||
include_str!("./read_file_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
|
@ -47,9 +54,18 @@ impl Tool for ReadFileTool {
|
|||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
let Some(worktree_root_name) = input.path.components().next() else {
|
||||
return Task::ready(Err(anyhow!("Invalid path")));
|
||||
};
|
||||
let Some(worktree) = project
|
||||
.read(cx)
|
||||
.worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx)
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("Directory not found in the project")));
|
||||
};
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(input.worktree_id),
|
||||
path: input.path,
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Arc::from(input.path.strip_prefix(worktree_root_name).unwrap()),
|
||||
};
|
||||
cx.spawn(|cx| async move {
|
||||
let buffer = cx
|
||||
|
|
1
crates/assistant_tools/src/read_file_tool/description.md
Normal file
1
crates/assistant_tools/src/read_file_tool/description.md
Normal file
|
@ -0,0 +1 @@
|
|||
Reads the content of the given file in the project.
|
Loading…
Add table
Add a link
Reference in a new issue