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
|
@ -10,3 +10,9 @@ You should only perform actions that modify the user’s system if explicitly re
|
||||||
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
- 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.
|
Be concise and direct in your responses.
|
||||||
|
|
||||||
|
The user has opened a project that contains the following top-level directories/files:
|
||||||
|
|
||||||
|
{{#each worktree_root_names}}
|
||||||
|
- {{this}}
|
||||||
|
{{/each}}
|
|
@ -112,7 +112,7 @@ impl AssistantPanel {
|
||||||
log::info!("[assistant2-debug] initializing ThreadStore");
|
log::info!("[assistant2-debug] initializing ThreadStore");
|
||||||
let thread_store = workspace.update(&mut cx, |workspace, cx| {
|
let thread_store = workspace.update(&mut cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
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");
|
log::info!("[assistant2-debug] finished initializing ThreadStore");
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
|
@ -13,9 +13,10 @@ use language_model::{
|
||||||
Role, StopReason,
|
Role, StopReason,
|
||||||
};
|
};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use prompt_store::PromptBuilder;
|
||||||
use scripting_tool::{ScriptingSession, ScriptingTool};
|
use scripting_tool::{ScriptingSession, ScriptingTool};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use util::{post_inc, TryFutureExt as _};
|
use util::{post_inc, ResultExt, TryFutureExt as _};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||||
|
@ -74,6 +75,7 @@ pub struct Thread {
|
||||||
completion_count: usize,
|
completion_count: usize,
|
||||||
pending_completions: Vec<PendingCompletion>,
|
pending_completions: Vec<PendingCompletion>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
tool_use: ToolUseState,
|
tool_use: ToolUseState,
|
||||||
scripting_session: Entity<ScriptingSession>,
|
scripting_session: Entity<ScriptingSession>,
|
||||||
|
@ -84,6 +86,7 @@ impl Thread {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
|
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
|
||||||
|
@ -100,6 +103,7 @@ impl Thread {
|
||||||
completion_count: 0,
|
completion_count: 0,
|
||||||
pending_completions: Vec::new(),
|
pending_completions: Vec::new(),
|
||||||
project,
|
project,
|
||||||
|
prompt_builder,
|
||||||
tools,
|
tools,
|
||||||
tool_use: ToolUseState::new(),
|
tool_use: ToolUseState::new(),
|
||||||
scripting_session,
|
scripting_session,
|
||||||
|
@ -112,6 +116,7 @@ impl Thread {
|
||||||
saved: SavedThread,
|
saved: SavedThread,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let next_message_id = MessageId(
|
let next_message_id = MessageId(
|
||||||
|
@ -147,6 +152,7 @@ impl Thread {
|
||||||
completion_count: 0,
|
completion_count: 0,
|
||||||
pending_completions: Vec::new(),
|
pending_completions: Vec::new(),
|
||||||
project,
|
project,
|
||||||
|
prompt_builder,
|
||||||
tools,
|
tools,
|
||||||
tool_use,
|
tool_use,
|
||||||
scripting_session,
|
scripting_session,
|
||||||
|
@ -373,14 +379,25 @@ impl Thread {
|
||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
request_kind: RequestKind,
|
request_kind: RequestKind,
|
||||||
_cx: &App,
|
cx: &App,
|
||||||
) -> LanguageModelRequest {
|
) -> 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 {
|
let mut request = LanguageModelRequest {
|
||||||
messages: vec![LanguageModelRequestMessage {
|
messages: vec![LanguageModelRequestMessage {
|
||||||
role: Role::System,
|
role: Role::System,
|
||||||
content: vec![MessageContent::Text(
|
content: vec![MessageContent::Text(system_prompt)],
|
||||||
include_str!("./system_prompt.md").to_string(),
|
|
||||||
)],
|
|
||||||
cache: true,
|
cache: true,
|
||||||
}],
|
}],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
|
|
|
@ -16,6 +16,7 @@ use heed::types::{SerdeBincode, SerdeJson};
|
||||||
use heed::Database;
|
use heed::Database;
|
||||||
use language_model::{LanguageModelToolUseId, Role};
|
use language_model::{LanguageModelToolUseId, Role};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use prompt_store::PromptBuilder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ pub fn init(cx: &mut App) {
|
||||||
pub struct ThreadStore {
|
pub struct ThreadStore {
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||||
threads: Vec<SavedThreadMetadata>,
|
threads: Vec<SavedThreadMetadata>,
|
||||||
|
@ -37,6 +39,7 @@ impl ThreadStore {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<Entity<Self>> {
|
) -> Result<Entity<Self>> {
|
||||||
let this = cx.new(|cx| {
|
let this = cx.new(|cx| {
|
||||||
|
@ -48,6 +51,7 @@ impl ThreadStore {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
project,
|
project,
|
||||||
tools,
|
tools,
|
||||||
|
prompt_builder,
|
||||||
context_server_manager,
|
context_server_manager,
|
||||||
context_server_tool_ids: HashMap::default(),
|
context_server_tool_ids: HashMap::default(),
|
||||||
threads: Vec::new(),
|
threads: Vec::new(),
|
||||||
|
@ -77,7 +81,14 @@ impl ThreadStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
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(
|
pub fn open_thread(
|
||||||
|
@ -101,6 +112,7 @@ impl ThreadStore {
|
||||||
thread,
|
thread,
|
||||||
this.project.clone(),
|
this.project.clone(),
|
||||||
this.tools.clone(),
|
this.tools.clone(),
|
||||||
|
this.prompt_builder.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
mod edit_files_tool;
|
mod edit_files_tool;
|
||||||
mod list_worktrees_tool;
|
|
||||||
mod now_tool;
|
mod now_tool;
|
||||||
mod read_file_tool;
|
mod read_file_tool;
|
||||||
|
|
||||||
|
@ -7,7 +6,6 @@ use assistant_tool::ToolRegistry;
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
|
|
||||||
use crate::edit_files_tool::EditFilesTool;
|
use crate::edit_files_tool::EditFilesTool;
|
||||||
use crate::list_worktrees_tool::ListWorktreesTool;
|
|
||||||
use crate::now_tool::NowTool;
|
use crate::now_tool::NowTool;
|
||||||
use crate::read_file_tool::ReadFileTool;
|
use crate::read_file_tool::ReadFileTool;
|
||||||
|
|
||||||
|
@ -16,7 +14,6 @@ pub fn init(cx: &mut App) {
|
||||||
|
|
||||||
let registry = ToolRegistry::global(cx);
|
let registry = ToolRegistry::global(cx);
|
||||||
registry.register_tool(NowTool);
|
registry.register_tool(NowTool);
|
||||||
registry.register_tool(ListWorktreesTool);
|
|
||||||
registry.register_tool(ReadFileTool);
|
registry.register_tool(ReadFileTool);
|
||||||
registry.register_tool(EditFilesTool);
|
registry.register_tool(EditFilesTool);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,33 @@
|
||||||
mod edit_action;
|
mod edit_action;
|
||||||
|
|
||||||
use collections::HashSet;
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::{path::Path, sync::Arc};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use assistant_tool::Tool;
|
use assistant_tool::Tool;
|
||||||
|
use collections::HashSet;
|
||||||
use edit_action::{EditAction, EditActionParser};
|
use edit_action::{EditAction, EditActionParser};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{App, Entity, Task};
|
use gpui::{App, Entity, Task};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
};
|
};
|
||||||
use project::{Project, ProjectPath, WorktreeId};
|
use project::{Project, ProjectPath};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct EditFilesToolInput {
|
pub struct EditFilesToolInput {
|
||||||
/// The ID of the worktree in which the files reside.
|
/// High-level edit instructions. These will be interpreted by a smaller model,
|
||||||
pub worktree_id: usize,
|
/// so explain the edits you want that model to make and to which files need changing.
|
||||||
/// Instruct how to modify the files.
|
/// 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,
|
pub edit_instructions: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,10 +98,25 @@ impl Tool for EditFilesTool {
|
||||||
|
|
||||||
while let Some(chunk) = chunks.stream.next().await {
|
while let Some(chunk) = chunks.stream.next().await {
|
||||||
for action in parser.parse_chunk(&chunk?) {
|
for action in parser.parse_chunk(&chunk?) {
|
||||||
let project_path = ProjectPath {
|
let project_path = project.read_with(&cx, |project, cx| {
|
||||||
worktree_id: WorktreeId::from_usize(input.worktree_id),
|
let worktree_root_name = action
|
||||||
path: Path::new(action.file_path()).into(),
|
.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
|
let buffer = project
|
||||||
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
|
.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;
|
use util::ResultExt;
|
||||||
|
|
||||||
/// Represents an edit action to be performed on a file.
|
/// Represents an edit action to be performed on a file.
|
||||||
|
@ -5,16 +6,16 @@ use util::ResultExt;
|
||||||
pub enum EditAction {
|
pub enum EditAction {
|
||||||
/// Replace specific content in a file with new content
|
/// Replace specific content in a file with new content
|
||||||
Replace {
|
Replace {
|
||||||
file_path: String,
|
file_path: PathBuf,
|
||||||
old: String,
|
old: String,
|
||||||
new: String,
|
new: String,
|
||||||
},
|
},
|
||||||
/// Write content to a file (create or overwrite)
|
/// Write content to a file (create or overwrite)
|
||||||
Write { file_path: String, content: String },
|
Write { file_path: PathBuf, content: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditAction {
|
impl EditAction {
|
||||||
pub fn file_path(&self) -> &str {
|
pub fn file_path(&self) -> &Path {
|
||||||
match self {
|
match self {
|
||||||
EditAction::Replace { file_path, .. } => file_path,
|
EditAction::Replace { file_path, .. } => file_path,
|
||||||
EditAction::Write { file_path, .. } => file_path,
|
EditAction::Write { file_path, .. } => file_path,
|
||||||
|
@ -180,7 +181,7 @@ impl EditActionParser {
|
||||||
pop_carriage_return(&mut pre_fence_line);
|
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()?;
|
let content = String::from_utf8(std::mem::take(&mut self.new_bytes)).log_err()?;
|
||||||
|
|
||||||
if self.old_bytes.is_empty() {
|
if self.old_bytes.is_empty() {
|
||||||
|
@ -374,7 +375,7 @@ fn replacement() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old: "fn original() {}".to_string(),
|
old: "fn original() {}".to_string(),
|
||||||
new: "fn replacement() {}".to_string(),
|
new: "fn replacement() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -401,7 +402,7 @@ fn replacement() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old: "fn original() {}".to_string(),
|
old: "fn original() {}".to_string(),
|
||||||
new: "fn replacement() {}".to_string(),
|
new: "fn replacement() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -432,7 +433,7 @@ This change makes the function better.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old: "fn original() {}".to_string(),
|
old: "fn original() {}".to_string(),
|
||||||
new: "fn replacement() {}".to_string(),
|
new: "fn replacement() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -470,7 +471,7 @@ fn new_util() -> bool { true }
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old: "fn original() {}".to_string(),
|
old: "fn original() {}".to_string(),
|
||||||
new: "fn replacement() {}".to_string(),
|
new: "fn replacement() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -478,7 +479,7 @@ fn new_util() -> bool { true }
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[1],
|
actions[1],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/utils.rs".to_string(),
|
file_path: PathBuf::from("src/utils.rs"),
|
||||||
old: "fn old_util() -> bool { false }".to_string(),
|
old: "fn old_util() -> bool { false }".to_string(),
|
||||||
new: "fn new_util() -> bool { true }".to_string(),
|
new: "fn new_util() -> bool { true }".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -519,7 +520,7 @@ fn replacement() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
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(),
|
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(),
|
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!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Write {
|
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}"
|
content: "fn new_function() {\n println!(\"This function is being added\");\n}"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
}
|
}
|
||||||
|
@ -576,7 +577,7 @@ fn this_will_be_deleted() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
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}"
|
old: "fn this_will_be_deleted() {\n println!(\"Deleting this function\");\n}"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
new: "".to_string(),
|
new: "".to_string(),
|
||||||
|
@ -589,7 +590,7 @@ fn this_will_be_deleted() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old:
|
old:
|
||||||
"fn this_will_be_deleted() {\r\n println!(\"Deleting this function\");\r\n}"
|
"fn this_will_be_deleted() {\r\n println!(\"Deleting this function\");\r\n}"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
@ -655,7 +656,7 @@ fn replacement() {}"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions3[0],
|
actions3[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/main.rs".to_string(),
|
file_path: PathBuf::from("src/main.rs"),
|
||||||
old: "fn original() {}".to_string(),
|
old: "fn original() {}".to_string(),
|
||||||
new: "fn replacement() {}".to_string(),
|
new: "fn replacement() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -747,7 +748,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
EditAction::Replace {
|
||||||
file_path: "src/utils.rs".to_string(),
|
file_path: PathBuf::from("src/utils.rs"),
|
||||||
old: "fn utils_func() {}".to_string(),
|
old: "fn utils_func() {}".to_string(),
|
||||||
new: "fn new_utils_func() {}".to_string(),
|
new: "fn new_utils_func() {}".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -795,7 +796,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[0],
|
actions[0],
|
||||||
EditAction::Replace {
|
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(),
|
old: "from flask import Flask".to_string(),
|
||||||
new: "import math\nfrom flask import Flask".to_string(),
|
new: "import math\nfrom flask import Flask".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -804,7 +805,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[1],
|
actions[1],
|
||||||
EditAction::Replace {
|
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(),
|
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(),
|
new: "".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -813,7 +814,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[2],
|
actions[2],
|
||||||
EditAction::Replace {
|
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(),
|
old: " return str(factorial(n))".to_string(),
|
||||||
new: " return str(math.factorial(n))".to_string(),
|
new: " return str(math.factorial(n))".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -822,7 +823,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[3],
|
actions[3],
|
||||||
EditAction::Write {
|
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\")"
|
content: "def hello():\n \"print a greeting\"\n\n print(\"hello\")"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
}
|
}
|
||||||
|
@ -831,7 +832,7 @@ fn new_utils_func() {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actions[4],
|
actions[4],
|
||||||
EditAction::Replace {
|
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(),
|
old: "def hello():\n \"print a greeting\"\n\n print(\"hello\")".to_string(),
|
||||||
new: "from hello import 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 assistant_tool::Tool;
|
||||||
use gpui::{App, Entity, Task};
|
use gpui::{App, Entity, Task};
|
||||||
use language_model::LanguageModelRequestMessage;
|
use language_model::LanguageModelRequestMessage;
|
||||||
use project::{Project, ProjectPath, WorktreeId};
|
use project::{Project, ProjectPath};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ReadFileToolInput {
|
pub struct ReadFileToolInput {
|
||||||
/// The ID of the worktree in which the file resides.
|
/// The relative path of the file to read.
|
||||||
pub worktree_id: usize,
|
|
||||||
/// The path to 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>,
|
pub path: Arc<Path>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +34,7 @@ impl Tool for ReadFileTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
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 {
|
fn input_schema(&self) -> serde_json::Value {
|
||||||
|
@ -47,9 +54,18 @@ impl Tool for ReadFileTool {
|
||||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
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 {
|
let project_path = ProjectPath {
|
||||||
worktree_id: WorktreeId::from_usize(input.worktree_id),
|
worktree_id: worktree.read(cx).id(),
|
||||||
path: input.path,
|
path: Arc::from(input.path.strip_prefix(worktree_root_name).unwrap()),
|
||||||
};
|
};
|
||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
let buffer = cx
|
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)
|
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> {
|
pub fn worktree_root_names<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a str> {
|
||||||
self.visible_worktrees(cx)
|
self.visible_worktrees(cx)
|
||||||
.map(|tree| tree.read(cx).root_name())
|
.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 text::LineEnding;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AssistantSystemPromptContext {
|
||||||
|
pub worktree_root_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ContentPromptDiagnosticContext {
|
pub struct ContentPromptDiagnosticContext {
|
||||||
pub line_number: usize,
|
pub line_number: usize,
|
||||||
|
@ -216,6 +221,18 @@ impl PromptBuilder {
|
||||||
Ok(())
|
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(
|
pub fn generate_inline_transformation_prompt(
|
||||||
&self,
|
&self,
|
||||||
user_prompt: String,
|
user_prompt: String,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue