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
|
@ -112,7 +112,7 @@ impl AssistantPanel {
|
|||
log::info!("[assistant2-debug] initializing ThreadStore");
|
||||
let thread_store = workspace.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
ThreadStore::new(project, tools.clone(), cx)
|
||||
ThreadStore::new(project, tools.clone(), prompt_builder.clone(), cx)
|
||||
})??;
|
||||
log::info!("[assistant2-debug] finished initializing ThreadStore");
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
You are an AI assistant integrated into a text editor. Your goal is to do one of the following two things:
|
||||
|
||||
1. Help users answer questions and perform tasks related to their codebase.
|
||||
2. Answer general-purpose questions unrelated to their particular codebase.
|
||||
|
||||
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
|
||||
|
||||
You should only perform actions that modify the user’s system if explicitly requested by the user:
|
||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user’s system without explicit instruction.
|
||||
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
||||
|
||||
Be concise and direct in your responses.
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
|
@ -13,9 +13,10 @@ use language_model::{
|
|||
Role, StopReason,
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use scripting_tool::{ScriptingSession, ScriptingTool};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use util::{post_inc, ResultExt, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||
|
@ -74,6 +75,7 @@ pub struct Thread {
|
|||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
project: Entity<Project>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
tool_use: ToolUseState,
|
||||
scripting_session: Entity<ScriptingSession>,
|
||||
|
@ -84,6 +86,7 @@ impl Thread {
|
|||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
|
||||
|
@ -100,6 +103,7 @@ impl Thread {
|
|||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
project,
|
||||
prompt_builder,
|
||||
tools,
|
||||
tool_use: ToolUseState::new(),
|
||||
scripting_session,
|
||||
|
@ -112,6 +116,7 @@ impl Thread {
|
|||
saved: SavedThread,
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let next_message_id = MessageId(
|
||||
|
@ -147,6 +152,7 @@ impl Thread {
|
|||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
project,
|
||||
prompt_builder,
|
||||
tools,
|
||||
tool_use,
|
||||
scripting_session,
|
||||
|
@ -373,14 +379,25 @@ impl Thread {
|
|||
pub fn to_completion_request(
|
||||
&self,
|
||||
request_kind: RequestKind,
|
||||
_cx: &App,
|
||||
cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
let worktree_root_names = self
|
||||
.project
|
||||
.read(cx)
|
||||
.worktree_root_names(cx)
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>();
|
||||
let system_prompt = self
|
||||
.prompt_builder
|
||||
.generate_assistant_system_prompt(worktree_root_names)
|
||||
.context("failed to generate assistant system prompt")
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut request = LanguageModelRequest {
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
content: vec![MessageContent::Text(
|
||||
include_str!("./system_prompt.md").to_string(),
|
||||
)],
|
||||
content: vec![MessageContent::Text(system_prompt)],
|
||||
cache: true,
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
|
|
|
@ -16,6 +16,7 @@ use heed::types::{SerdeBincode, SerdeJson};
|
|||
use heed::Database;
|
||||
use language_model::{LanguageModelToolUseId, Role};
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::ResultExt as _;
|
||||
|
||||
|
@ -28,6 +29,7 @@ pub fn init(cx: &mut App) {
|
|||
pub struct ThreadStore {
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
context_server_manager: Entity<ContextServerManager>,
|
||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||
threads: Vec<SavedThreadMetadata>,
|
||||
|
@ -37,6 +39,7 @@ impl ThreadStore {
|
|||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut App,
|
||||
) -> Result<Entity<Self>> {
|
||||
let this = cx.new(|cx| {
|
||||
|
@ -48,6 +51,7 @@ impl ThreadStore {
|
|||
let this = Self {
|
||||
project,
|
||||
tools,
|
||||
prompt_builder,
|
||||
context_server_manager,
|
||||
context_server_tool_ids: HashMap::default(),
|
||||
threads: Vec::new(),
|
||||
|
@ -77,7 +81,14 @@ impl ThreadStore {
|
|||
}
|
||||
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
||||
cx.new(|cx| Thread::new(self.project.clone(), self.tools.clone(), cx))
|
||||
cx.new(|cx| {
|
||||
Thread::new(
|
||||
self.project.clone(),
|
||||
self.tools.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_thread(
|
||||
|
@ -101,6 +112,7 @@ impl ThreadStore {
|
|||
thread,
|
||||
this.project.clone(),
|
||||
this.tools.clone(),
|
||||
this.prompt_builder.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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.
|
|
@ -1589,6 +1589,11 @@ impl Project {
|
|||
self.worktree_store.read(cx).visible_worktrees(cx)
|
||||
}
|
||||
|
||||
pub fn worktree_for_root_name(&self, root_name: &str, cx: &App) -> Option<Entity<Worktree>> {
|
||||
self.visible_worktrees(cx)
|
||||
.find(|tree| tree.read(cx).root_name() == root_name)
|
||||
}
|
||||
|
||||
pub fn worktree_root_names<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a str> {
|
||||
self.visible_worktrees(cx)
|
||||
.map(|tree| tree.read(cx).root_name())
|
||||
|
|
|
@ -11,6 +11,11 @@ use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
|||
use text::LineEnding;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AssistantSystemPromptContext {
|
||||
pub worktree_root_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ContentPromptDiagnosticContext {
|
||||
pub line_number: usize,
|
||||
|
@ -216,6 +221,18 @@ impl PromptBuilder {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_assistant_system_prompt(
|
||||
&self,
|
||||
worktree_root_names: Vec<String>,
|
||||
) -> Result<String, RenderError> {
|
||||
let prompt = AssistantSystemPromptContext {
|
||||
worktree_root_names,
|
||||
};
|
||||
self.handlebars
|
||||
.lock()
|
||||
.render("assistant_system_prompt", &prompt)
|
||||
}
|
||||
|
||||
pub fn generate_inline_transformation_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue