From 1bf1c7223f5549217170e28eb30c8f9c8a2cd7c5 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Fri, 14 Mar 2025 17:07:43 -0300 Subject: [PATCH] assistant edit tool: Fix editing files in context (#26751) When the user attached context in the thread, the editor model request would fail because its tool use wouldn't be removed properly leading to an API error. Also, after an edit, we'd keep the old file snapshot in the context. This would make the model think that the edits didn't apply and make it go in a loop. Release Notes: - N/A --- Cargo.lock | 1 + crates/assistant2/src/active_thread.rs | 51 ++++++++++++++- crates/assistant2/src/assistant_panel.rs | 15 +++++ crates/assistant2/src/context_store.rs | 65 +++++++++++++------ crates/assistant2/src/message_editor.rs | 6 +- crates/assistant2/src/thread.rs | 24 ++++++- crates/assistant2/src/tool_use.rs | 4 +- crates/assistant_tool/Cargo.toml | 3 +- crates/assistant_tool/src/assistant_tool.rs | 36 ++++++++++ crates/assistant_tools/src/bash_tool.rs | 3 +- .../assistant_tools/src/delete_path_tool.rs | 3 +- .../assistant_tools/src/diagnostics_tool.rs | 3 +- crates/assistant_tools/src/edit_files_tool.rs | 62 +++++++++++++----- .../src/list_directory_tool.rs | 3 +- crates/assistant_tools/src/now_tool.rs | 3 +- .../assistant_tools/src/path_search_tool.rs | 3 +- crates/assistant_tools/src/read_file_tool.rs | 3 +- crates/assistant_tools/src/regex_search.rs | 5 +- .../context_server/src/context_server_tool.rs | 3 +- 19 files changed, 239 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85dce1d932360ebb9169466ee0c7a4a5c9cb9879..750e3e0ea8d38254bd6937052aed236d72d722d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,6 +660,7 @@ dependencies = [ "collections", "derive_more", "gpui", + "language", "language_model", "parking_lot", "project", diff --git a/crates/assistant2/src/active_thread.rs b/crates/assistant2/src/active_thread.rs index f7668da10fd38b3ed3c873e63f935f11113e85bc..212ef3496f6ae3fe3e6e4c6a3cba714d49748b7d 100644 --- a/crates/assistant2/src/active_thread.rs +++ b/crates/assistant2/src/active_thread.rs @@ -22,10 +22,13 @@ use ui::Color; use ui::{prelude::*, Disclosure, KeyBinding}; use util::ResultExt as _; +use crate::context_store::{refresh_context_store_text, ContextStore}; + pub struct ActiveThread { language_registry: Arc, thread_store: Entity, thread: Entity, + context_store: Entity, save_thread_task: Option>, messages: Vec, list_state: ListState, @@ -46,6 +49,7 @@ impl ActiveThread { thread: Entity, thread_store: Entity, language_registry: Arc, + context_store: Entity, window: &mut Window, cx: &mut Context, ) -> Self { @@ -58,6 +62,7 @@ impl ActiveThread { language_registry, thread_store, thread: thread.clone(), + context_store, save_thread_task: None, messages: Vec::new(), rendered_messages_by_id: HashMap::default(), @@ -350,11 +355,51 @@ impl ActiveThread { } if self.thread.read(cx).all_tools_finished() { + let pending_refresh_buffers = self.thread.update(cx, |thread, cx| { + thread.action_log().update(cx, |action_log, _cx| { + action_log.take_pending_refresh_buffers() + }) + }); + + let context_update_task = if !pending_refresh_buffers.is_empty() { + let refresh_task = refresh_context_store_text( + self.context_store.clone(), + &pending_refresh_buffers, + cx, + ); + + cx.spawn(|this, mut cx| async move { + let updated_context_ids = refresh_task.await; + + this.update(&mut cx, |this, cx| { + this.context_store.read_with(cx, |context_store, cx| { + context_store + .context() + .iter() + .filter(|context| { + updated_context_ids.contains(&context.id()) + }) + .flat_map(|context| context.snapshot(cx)) + .collect() + }) + }) + }) + } else { + Task::ready(anyhow::Ok(Vec::new())) + }; + let model_registry = LanguageModelRegistry::read_global(cx); if let Some(model) = model_registry.active_model() { - self.thread.update(cx, |thread, cx| { - thread.send_tool_results_to_model(model, cx); - }); + cx.spawn(|this, mut cx| async move { + let updated_context = context_update_task.await?; + + this.update(&mut cx, |this, cx| { + this.thread.update(cx, |thread, cx| { + thread.send_tool_results_to_model(model, updated_context, cx); + }); + }) + }) + .detach(); } } } diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 486931a9c3a13829ca86022f01eca92ac11a097c..cb73bfe09ca964064eceb592fd6e46cda5bc6bad 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -155,10 +155,14 @@ impl AssistantPanel { let workspace = workspace.weak_handle(); let weak_self = cx.entity().downgrade(); + let message_editor_context_store = + cx.new(|_cx| crate::context_store::ContextStore::new(workspace.clone())); + let message_editor = cx.new(|cx| { MessageEditor::new( fs.clone(), workspace.clone(), + message_editor_context_store.clone(), thread_store.downgrade(), thread.clone(), window, @@ -174,6 +178,7 @@ impl AssistantPanel { thread.clone(), thread_store.clone(), language_registry.clone(), + message_editor_context_store.clone(), window, cx, ) @@ -242,11 +247,16 @@ impl AssistantPanel { .update(cx, |this, cx| this.create_thread(cx)); self.active_view = ActiveView::Thread; + + let message_editor_context_store = + cx.new(|_cx| crate::context_store::ContextStore::new(self.workspace.clone())); + self.thread = cx.new(|cx| { ActiveThread::new( thread.clone(), self.thread_store.clone(), self.language_registry.clone(), + message_editor_context_store.clone(), window, cx, ) @@ -255,6 +265,7 @@ impl AssistantPanel { MessageEditor::new( self.fs.clone(), self.workspace.clone(), + message_editor_context_store, self.thread_store.downgrade(), thread, window, @@ -375,11 +386,14 @@ impl AssistantPanel { let thread = open_thread_task.await?; this.update_in(&mut cx, |this, window, cx| { this.active_view = ActiveView::Thread; + let message_editor_context_store = + cx.new(|_cx| crate::context_store::ContextStore::new(this.workspace.clone())); this.thread = cx.new(|cx| { ActiveThread::new( thread.clone(), this.thread_store.clone(), this.language_registry.clone(), + message_editor_context_store.clone(), window, cx, ) @@ -388,6 +402,7 @@ impl AssistantPanel { MessageEditor::new( this.fs.clone(), this.workspace.clone(), + message_editor_context_store, this.thread_store.downgrade(), thread, window, diff --git a/crates/assistant2/src/context_store.rs b/crates/assistant2/src/context_store.rs index 5922f87ca470ab178daf082b3db975e508ab713c..f060bb0a408659928f8cfd36a2e82ca99343d4d5 100644 --- a/crates/assistant2/src/context_store.rs +++ b/crates/assistant2/src/context_store.rs @@ -9,6 +9,7 @@ use language::Buffer; use project::{ProjectPath, Worktree}; use rope::Rope; use text::BufferId; +use util::maybe; use workspace::Workspace; use crate::context::{ @@ -531,35 +532,59 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec> { pub fn refresh_context_store_text( context_store: Entity, + changed_buffers: &HashSet>, cx: &App, -) -> impl Future { +) -> impl Future> { let mut tasks = Vec::new(); + for context in &context_store.read(cx).context { - match context { - AssistantContext::File(file_context) => { - let context_store = context_store.clone(); - if let Some(task) = refresh_file_text(context_store, file_context, cx) { - tasks.push(task); + let id = context.id(); + + let task = maybe!({ + match context { + AssistantContext::File(file_context) => { + if changed_buffers.is_empty() + || changed_buffers.contains(&file_context.context_buffer.buffer) + { + let context_store = context_store.clone(); + return refresh_file_text(context_store, file_context, cx); + } } - } - AssistantContext::Directory(directory_context) => { - let context_store = context_store.clone(); - if let Some(task) = refresh_directory_text(context_store, directory_context, cx) { - tasks.push(task); + AssistantContext::Directory(directory_context) => { + let should_refresh = changed_buffers.is_empty() + || changed_buffers.iter().any(|buffer| { + let buffer = buffer.read(cx); + + buffer_path_log_err(&buffer) + .map_or(false, |path| path.starts_with(&directory_context.path)) + }); + + if should_refresh { + let context_store = context_store.clone(); + return refresh_directory_text(context_store, directory_context, cx); + } } + AssistantContext::Thread(thread_context) => { + if changed_buffers.is_empty() { + let context_store = context_store.clone(); + return Some(refresh_thread_text(context_store, thread_context, cx)); + } + } + // Intentionally omit refreshing fetched URLs as it doesn't seem all that useful, + // and doing the caching properly could be tricky (unless it's already handled by + // the HttpClient?). + AssistantContext::FetchedUrl(_) => {} } - AssistantContext::Thread(thread_context) => { - let context_store = context_store.clone(); - tasks.push(refresh_thread_text(context_store, thread_context, cx)); - } - // Intentionally omit refreshing fetched URLs as it doesn't seem all that useful, - // and doing the caching properly could be tricky (unless it's already handled by - // the HttpClient?). - AssistantContext::FetchedUrl(_) => {} + + None + }); + + if let Some(task) = task { + tasks.push(task.map(move |_| id)); } } - future::join_all(tasks).map(|_| ()) + future::join_all(tasks) } fn refresh_file_text( diff --git a/crates/assistant2/src/message_editor.rs b/crates/assistant2/src/message_editor.rs index 26b509fd3ea75813868b09fbe63b5ee8087acd07..52e9b56ed23d50f79c5f212306421d1e67d1bbe4 100644 --- a/crates/assistant2/src/message_editor.rs +++ b/crates/assistant2/src/message_editor.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use collections::HashSet; use editor::actions::MoveUp; use editor::{Editor, EditorElement, EditorEvent, EditorStyle}; use file_icons::FileIcons; @@ -51,13 +52,13 @@ impl MessageEditor { pub fn new( fs: Arc, workspace: WeakEntity, + context_store: Entity, thread_store: WeakEntity, thread: Entity, window: &mut Window, cx: &mut Context, ) -> Self { let tools = thread.read(cx).tools().clone(); - let context_store = cx.new(|_cx| ContextStore::new(workspace.clone())); let context_picker_menu_handle = PopoverMenuHandle::default(); let inline_context_picker_menu_handle = PopoverMenuHandle::default(); let model_selector_menu_handle = PopoverMenuHandle::default(); @@ -200,7 +201,8 @@ impl MessageEditor { text }); - let refresh_task = refresh_context_store_text(self.context_store.clone(), cx); + let refresh_task = + refresh_context_store_text(self.context_store.clone(), &HashSet::default(), cx); let thread = self.thread.clone(); let context_store = self.context_store.clone(); diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs index 871795a6d5d4498b9ae4e38bad95bcdc752dd120..9c744ab123ea3c1533130493a8ee4d2f689651e8 100644 --- a/crates/assistant2/src/thread.rs +++ b/crates/assistant2/src/thread.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::sync::Arc; use anyhow::{Context as _, Result}; -use assistant_tool::ToolWorkingSet; +use assistant_tool::{ActionLog, ToolWorkingSet}; use chrono::{DateTime, Utc}; use collections::{BTreeMap, HashMap, HashSet}; use futures::future::Shared; @@ -104,6 +104,7 @@ pub struct Thread { prompt_builder: Arc, tools: Arc, tool_use: ToolUseState, + action_log: Entity, scripting_session: Entity, scripting_tool_use: ToolUseState, initial_project_snapshot: Shared>>>, @@ -134,6 +135,7 @@ impl Thread { tool_use: ToolUseState::new(), scripting_session: cx.new(|cx| ScriptingSession::new(project.clone(), cx)), scripting_tool_use: ToolUseState::new(), + action_log: cx.new(|_| ActionLog::new()), initial_project_snapshot: { let project_snapshot = Self::project_snapshot(project, cx); cx.foreground_executor() @@ -191,6 +193,7 @@ impl Thread { prompt_builder, tools, tool_use, + action_log: cx.new(|_| ActionLog::new()), scripting_session, scripting_tool_use, initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(), @@ -750,7 +753,13 @@ impl Thread { for tool_use in pending_tool_uses { if let Some(tool) = self.tools.tool(&tool_use.name, cx) { - let task = tool.run(tool_use.input, &request.messages, self.project.clone(), cx); + let task = tool.run( + tool_use.input, + &request.messages, + self.project.clone(), + self.action_log.clone(), + cx, + ); self.insert_tool_output(tool_use.id.clone(), task, cx); } @@ -857,8 +866,15 @@ impl Thread { pub fn send_tool_results_to_model( &mut self, model: Arc, + updated_context: Vec, cx: &mut Context, ) { + self.context.extend( + updated_context + .into_iter() + .map(|context| (context.id, context)), + ); + // Insert a user message to contain the tool results. self.insert_user_message( // TODO: Sending up a user message without any content results in the model sending back @@ -1057,6 +1073,10 @@ impl Thread { Ok(String::from_utf8_lossy(&markdown).to_string()) } + pub fn action_log(&self) -> &Entity { + &self.action_log + } + pub fn cumulative_token_usage(&self) -> TokenUsage { self.cumulative_token_usage.clone() } diff --git a/crates/assistant2/src/tool_use.rs b/crates/assistant2/src/tool_use.rs index 01b0379071bacb3e1806e1316b69789f59f4e326..34821d45f91096971f75dd1f1f5447c46aa0b5ce 100644 --- a/crates/assistant2/src/tool_use.rs +++ b/crates/assistant2/src/tool_use.rs @@ -226,12 +226,12 @@ impl ToolUseState { output: Result, ) -> Option { match output { - Ok(output) => { + Ok(tool_result) => { self.tool_results.insert( tool_use_id.clone(), LanguageModelToolResult { tool_use_id: tool_use_id.clone(), - content: output.into(), + content: tool_result.into(), is_error: false, }, ); diff --git a/crates/assistant_tool/Cargo.toml b/crates/assistant_tool/Cargo.toml index f30c6d313484ecddda3f23f30c6226e386209059..70022fc02ca618709c010000153d00c8b580bddc 100644 --- a/crates/assistant_tool/Cargo.toml +++ b/crates/assistant_tool/Cargo.toml @@ -15,8 +15,9 @@ path = "src/assistant_tool.rs" anyhow.workspace = true collections.workspace = true derive_more.workspace = true -language_model.workspace = true gpui.workspace = true +language.workspace = true +language_model.workspace = true parking_lot.workspace = true project.workspace = true serde.workspace = true diff --git a/crates/assistant_tool/src/assistant_tool.rs b/crates/assistant_tool/src/assistant_tool.rs index 5002866287d7480054cea7a38a97f516b0e9d8d2..b466931d89ab116bd2ca3163f8798f6617ee2b16 100644 --- a/crates/assistant_tool/src/assistant_tool.rs +++ b/crates/assistant_tool/src/assistant_tool.rs @@ -4,7 +4,10 @@ mod tool_working_set; use std::sync::Arc; use anyhow::Result; +use collections::HashSet; +use gpui::Context; use gpui::{App, Entity, SharedString, Task}; +use language::Buffer; use language_model::LanguageModelRequestMessage; use project::Project; @@ -47,6 +50,39 @@ pub trait Tool: 'static + Send + Sync { input: serde_json::Value, messages: &[LanguageModelRequestMessage], project: Entity, + action_log: Entity, cx: &mut App, ) -> Task>; } + +/// Tracks actions performed by tools in a thread +#[derive(Debug)] +pub struct ActionLog { + changed_buffers: HashSet>, + pending_refresh: HashSet>, +} + +impl ActionLog { + /// Creates a new, empty action log. + pub fn new() -> Self { + Self { + changed_buffers: HashSet::default(), + pending_refresh: HashSet::default(), + } + } + + /// Registers buffers that have changed and need refreshing. + pub fn notify_buffers_changed( + &mut self, + buffers: HashSet>, + _cx: &mut Context, + ) { + self.changed_buffers.extend(buffers.clone()); + self.pending_refresh.extend(buffers); + } + + /// Takes and returns the set of buffers pending refresh, clearing internal state. + pub fn take_pending_refresh_buffers(&mut self) -> HashSet> { + std::mem::take(&mut self.pending_refresh) + } +} diff --git a/crates/assistant_tools/src/bash_tool.rs b/crates/assistant_tools/src/bash_tool.rs index 147c5ecc239118bec43805dcd799b99588e5a349..5f6d9575087d4a0b8b1dfc1ba8e034643db0b19b 100644 --- a/crates/assistant_tools/src/bash_tool.rs +++ b/crates/assistant_tools/src/bash_tool.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Context as _, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -37,6 +37,7 @@ impl Tool for BashTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let input: BashToolInput = match serde_json::from_value(input) { diff --git a/crates/assistant_tools/src/delete_path_tool.rs b/crates/assistant_tools/src/delete_path_tool.rs index 6a5b3a7ad53aeeec2f040fde21c60a2e92dd4b8d..d613cbed125833526648834e6096cda8205ca5a3 100644 --- a/crates/assistant_tools/src/delete_path_tool.rs +++ b/crates/assistant_tools/src/delete_path_tool.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -45,6 +45,7 @@ impl Tool for DeletePathTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let glob = match serde_json::from_value::(input) { diff --git a/crates/assistant_tools/src/diagnostics_tool.rs b/crates/assistant_tools/src/diagnostics_tool.rs index 5a86b7d5d3f81b55ecf196c852a841c51dbd9da9..e9d36cc01673208a6efff337b554c7172c0c2229 100644 --- a/crates/assistant_tools/src/diagnostics_tool.rs +++ b/crates/assistant_tools/src/diagnostics_tool.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language::{DiagnosticSeverity, OffsetRangeExt}; use language_model::LanguageModelRequestMessage; @@ -51,6 +51,7 @@ impl Tool for DiagnosticsTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let input = match serde_json::from_value::(input) { diff --git a/crates/assistant_tools/src/edit_files_tool.rs b/crates/assistant_tools/src/edit_files_tool.rs index 5ef7e7d77aab9b782bae448dfad0071f6b428394..553459175527ff9908c5aded07d3121775c49cc1 100644 --- a/crates/assistant_tools/src/edit_files_tool.rs +++ b/crates/assistant_tools/src/edit_files_tool.rs @@ -2,13 +2,13 @@ mod edit_action; pub mod log; use anyhow::{anyhow, Context, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use collections::HashSet; use edit_action::{EditAction, EditActionParser}; use futures::StreamExt; use gpui::{App, AsyncApp, Entity, Task}; use language_model::{ - LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, + LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, MessageContent, Role, }; use log::{EditToolLog, EditToolRequestId}; use project::{search::SearchQuery, Project}; @@ -80,6 +80,7 @@ impl Tool for EditFilesTool { input: serde_json::Value, messages: &[LanguageModelRequestMessage], project: Entity, + action_log: Entity, cx: &mut App, ) -> Task> { let input = match serde_json::from_value::(input) { @@ -93,8 +94,14 @@ impl Tool for EditFilesTool { log.new_request(input.edit_instructions.clone(), cx) }); - let task = - EditToolRequest::new(input, messages, project, Some((log.clone(), req_id)), cx); + let task = EditToolRequest::new( + input, + messages, + project, + action_log, + Some((log.clone(), req_id)), + cx, + ); cx.spawn(|mut cx| async move { let result = task.await; @@ -113,7 +120,7 @@ impl Tool for EditFilesTool { }) } - None => EditToolRequest::new(input, messages, project, None, cx), + None => EditToolRequest::new(input, messages, project, action_log, None, cx), } } } @@ -123,7 +130,8 @@ struct EditToolRequest { changed_buffers: HashSet>, bad_searches: Vec, project: Entity, - log: Option<(Entity, EditToolRequestId)>, + action_log: Entity, + tool_log: Option<(Entity, EditToolRequestId)>, } #[derive(Debug)] @@ -143,7 +151,8 @@ impl EditToolRequest { input: EditFilesToolInput, messages: &[LanguageModelRequestMessage], project: Entity, - log: Option<(Entity, EditToolRequestId)>, + action_log: Entity, + tool_log: Option<(Entity, EditToolRequestId)>, cx: &mut App, ) -> Task> { let model_registry = LanguageModelRegistry::read_global(cx); @@ -152,12 +161,23 @@ impl EditToolRequest { }; let mut messages = messages.to_vec(); - if let Some(last_message) = messages.last_mut() { - // Strip out tool use from the last message because we're in the middle of executing a tool call. - last_message - .content - .retain(|content| !matches!(content, language_model::MessageContent::ToolUse(_))) + // Remove the last tool use (this run) to prevent an invalid request + 'outer: for message in messages.iter_mut().rev() { + for (index, content) in message.content.iter().enumerate().rev() { + match content { + MessageContent::ToolUse(_) => { + message.content.remove(index); + break 'outer; + } + MessageContent::ToolResult(_) => { + // If we find any tool results before a tool use, the request is already valid + break 'outer; + } + MessageContent::Text(_) | MessageContent::Image(_) => {} + } + } } + messages.push(LanguageModelRequestMessage { role: Role::User, content: vec![ @@ -182,8 +202,9 @@ impl EditToolRequest { parser: EditActionParser::new(), changed_buffers: HashSet::default(), bad_searches: Vec::new(), + action_log, project, - log, + tool_log, }; while let Some(chunk) = chunks.stream.next().await { @@ -197,7 +218,7 @@ impl EditToolRequest { async fn process_response_chunk(&mut self, chunk: &str, cx: &mut AsyncApp) -> Result<()> { let new_actions = self.parser.parse_chunk(chunk); - if let Some((ref log, req_id)) = self.log { + if let Some((ref log, req_id)) = self.tool_log { log.update(cx, |log, cx| { log.push_editor_response_chunk(req_id, chunk, &new_actions, cx) }) @@ -310,7 +331,7 @@ impl EditToolRequest { }; // Save each buffer once at the end - for buffer in self.changed_buffers { + for buffer in &self.changed_buffers { let (path, save_task) = self.project.update(cx, |project, cx| { let path = buffer .read(cx) @@ -329,10 +350,17 @@ impl EditToolRequest { } } + self.action_log + .update(cx, |log, cx| { + log.notify_buffers_changed(self.changed_buffers, cx) + }) + .log_err(); + let errors = self.parser.errors(); if errors.is_empty() && self.bad_searches.is_empty() { - Ok(answer.trim_end().to_string()) + let answer = answer.trim_end().to_string(); + Ok(answer) } else { if !self.bad_searches.is_empty() { writeln!( @@ -369,7 +397,7 @@ impl EditToolRequest { but errors are part of the conversation so you don't need to repeat them." )?; - Err(anyhow!(answer)) + Err(anyhow!(answer.trim_end().to_string())) } } } diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 3737319f26d5ca9afbcfbc660155f509de0ad18f..b43010b6ef59fe429a22549b153470272ecd15c9 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -55,6 +55,7 @@ impl Tool for ListDirectoryTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let input = match serde_json::from_value::(input) { diff --git a/crates/assistant_tools/src/now_tool.rs b/crates/assistant_tools/src/now_tool.rs index 80332a184e71c98c48d5a0629b5a38f3146cc517..e10bddb6692205614b6c94148d6db714ba96a358 100644 --- a/crates/assistant_tools/src/now_tool.rs +++ b/crates/assistant_tools/src/now_tool.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use chrono::{Local, Utc}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; @@ -45,6 +45,7 @@ impl Tool for NowTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], _project: Entity, + _action_log: Entity, _cx: &mut App, ) -> Task> { let input: NowToolInput = match serde_json::from_value(input) { diff --git a/crates/assistant_tools/src/path_search_tool.rs b/crates/assistant_tools/src/path_search_tool.rs index 00ac7032166f2530b0732a8069d1624f3a89b087..dfa7ab0f7376072ef1ad32c6264cb1d6de164c1a 100644 --- a/crates/assistant_tools/src/path_search_tool.rs +++ b/crates/assistant_tools/src/path_search_tool.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -45,6 +45,7 @@ impl Tool for PathSearchTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let glob = match serde_json::from_value::(input) { diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 2190c721e9f174319a31c270171461b9360fe9b0..d4c69df8e552909c11f0b5fa7d14a094dce70218 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -2,7 +2,7 @@ use std::path::Path; use std::sync::Arc; use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -49,6 +49,7 @@ impl Tool for ReadFileTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { let input = match serde_json::from_value::(input) { diff --git a/crates/assistant_tools/src/regex_search.rs b/crates/assistant_tools/src/regex_search.rs index 9cdf269594bc6b5ebe1f8e3b5593acdf3cbea0db..a6eca996a73927ffe88df83cf5b5e08d8738b49b 100644 --- a/crates/assistant_tools/src/regex_search.rs +++ b/crates/assistant_tools/src/regex_search.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use assistant_tool::Tool; +use assistant_tool::{ActionLog, Tool}; use futures::StreamExt; use gpui::{App, Entity, Task}; use language::OffsetRangeExt; @@ -38,6 +38,7 @@ impl Tool for RegexSearchTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { const CONTEXT_LINES: u32 = 2; @@ -110,7 +111,7 @@ impl Tool for RegexSearchTool { } if output.is_empty() { - Ok("No matches found".into()) + Ok("No matches found".to_string()) } else { Ok(output) } diff --git a/crates/context_server/src/context_server_tool.rs b/crates/context_server/src/context_server_tool.rs index 899db58c7d6bd1c1d023d9cfa5c5d598dfaad234..315bb6bce3c3773b63d4b70be5cca5b4d817c253 100644 --- a/crates/context_server/src/context_server_tool.rs +++ b/crates/context_server/src/context_server_tool.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::{anyhow, bail, Result}; -use assistant_tool::{Tool, ToolSource}; +use assistant_tool::{ActionLog, Tool, ToolSource}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use project::Project; @@ -61,6 +61,7 @@ impl Tool for ContextServerTool { input: serde_json::Value, _messages: &[LanguageModelRequestMessage], _project: Entity, + _action_log: Entity, cx: &mut App, ) -> Task> { if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {