From 5e094553faf4c915fb7fe96fcc3677a3d3555267 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 15 Apr 2025 08:28:09 -0600 Subject: [PATCH] agent: Return `ToolResult` from `run` inside `Tool` (#28763) This is just a refactor which adds no functionality. We now return a `ToolResult` from `Tool > run(...)`. For now this just wraps the output task in a struct. We'll use this to implement custom rendering of tools, see #28621. Release Notes: - N/A --- crates/agent/src/thread.rs | 6 ++--- crates/assistant_tool/src/assistant_tool.rs | 15 ++++++++++++- crates/assistant_tools/src/batch_tool.rs | 13 ++++++----- .../assistant_tools/src/code_action_tool.rs | 8 +++---- .../assistant_tools/src/code_symbols_tool.rs | 9 ++++---- crates/assistant_tools/src/contents_tool.rs | 18 +++++++-------- crates/assistant_tools/src/copy_path_tool.rs | 7 +++--- .../src/create_directory_tool.rs | 11 ++++++---- .../assistant_tools/src/create_file_tool.rs | 11 ++++++---- .../assistant_tools/src/delete_path_tool.rs | 13 ++++++----- .../assistant_tools/src/diagnostics_tool.rs | 11 ++++++---- crates/assistant_tools/src/fetch_tool.rs | 22 ++++++++++--------- .../src/find_replace_file_tool.rs | 8 +++---- .../src/list_directory_tool.rs | 20 ++++++++--------- crates/assistant_tools/src/move_path_tool.rs | 7 +++--- crates/assistant_tools/src/now_tool.rs | 8 +++---- crates/assistant_tools/src/open_tool.rs | 7 +++--- .../assistant_tools/src/path_search_tool.rs | 10 ++++----- crates/assistant_tools/src/read_file_tool.rs | 10 ++++----- .../assistant_tools/src/regex_search_tool.rs | 10 ++++----- crates/assistant_tools/src/rename_tool.rs | 8 +++---- .../assistant_tools/src/symbol_info_tool.rs | 8 +++---- crates/assistant_tools/src/terminal_tool.rs | 19 ++++++++++------ crates/assistant_tools/src/thinking_tool.rs | 5 +++-- .../context_server/src/context_server_tool.rs | 7 +++--- 25 files changed, 155 insertions(+), 116 deletions(-) diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index ada5c068a7..4dfefdc3e6 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -1407,8 +1407,8 @@ impl Thread { ) -> Task<()> { let tool_name: Arc = tool.name().into(); - let run_tool = if self.tools.read(cx).is_disabled(&tool.source(), &tool_name) { - Task::ready(Err(anyhow!("tool is disabled: {tool_name}"))) + let tool_result = if self.tools.read(cx).is_disabled(&tool.source(), &tool_name) { + Task::ready(Err(anyhow!("tool is disabled: {tool_name}"))).into() } else { tool.run( input, @@ -1421,7 +1421,7 @@ impl Thread { cx.spawn({ async move |thread: WeakEntity, cx| { - let output = run_tool.await; + let output = tool_result.output.await; thread .update(cx, |thread, cx| { diff --git a/crates/assistant_tool/src/assistant_tool.rs b/crates/assistant_tool/src/assistant_tool.rs index 81ab61d970..89450efb13 100644 --- a/crates/assistant_tool/src/assistant_tool.rs +++ b/crates/assistant_tool/src/assistant_tool.rs @@ -24,6 +24,19 @@ pub fn init(cx: &mut App) { ToolRegistry::default_global(cx); } +/// The result of running a tool +pub struct ToolResult { + /// The asynchronous task that will eventually resolve to the tool's output + pub output: Task>, +} + +impl From>> for ToolResult { + /// Convert from a task to a ToolResult + fn from(output: Task>) -> Self { + Self { output } + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub enum ToolSource { /// A native tool built-in to Zed. @@ -68,7 +81,7 @@ pub trait Tool: 'static + Send + Sync { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task>; + ) -> ToolResult; } impl Debug for dyn Tool { diff --git a/crates/assistant_tools/src/batch_tool.rs b/crates/assistant_tools/src/batch_tool.rs index 7ba1056ad9..bfd27845e4 100644 --- a/crates/assistant_tools/src/batch_tool.rs +++ b/crates/assistant_tools/src/batch_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool, ToolWorkingSet}; +use assistant_tool::{ActionLog, Tool, ToolResult, ToolWorkingSet}; use futures::future::join_all; use gpui::{App, AppContext, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -219,14 +219,14 @@ impl Tool for BatchTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; if input.invocations.is_empty() { - return Task::ready(Err(anyhow!("No tool invocations provided"))); + return Task::ready(Err(anyhow!("No tool invocations provided"))).into(); } let run_tools_concurrently = input.run_tools_concurrently; @@ -257,11 +257,11 @@ impl Tool for BatchTool { let project = project.clone(); let action_log = action_log.clone(); let messages = messages.clone(); - let task = cx + let tool_result = cx .update(|cx| tool.run(invocation.input, &messages, project, action_log, cx)) .map_err(|err| anyhow!("Failed to start tool '{}': {}", tool_name, err))?; - tasks.push(task); + tasks.push(tool_result.output); } Ok((tasks, tool_names)) @@ -306,5 +306,6 @@ impl Tool for BatchTool { Ok(formatted_results.trim().to_string()) }) + .into() } } diff --git a/crates/assistant_tools/src/code_action_tool.rs b/crates/assistant_tools/src/code_action_tool.rs index da62b8014a..8c60c83b56 100644 --- a/crates/assistant_tools/src/code_action_tool.rs +++ b/crates/assistant_tools/src/code_action_tool.rs @@ -1,5 +1,5 @@ use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language::{self, Anchor, Buffer, ToPointUtf16}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -141,10 +141,10 @@ impl Tool for CodeActionTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; cx.spawn(async move |cx| { @@ -319,7 +319,7 @@ impl Tool for CodeActionTool { Ok(response) } - }) + }).into() } } diff --git a/crates/assistant_tools/src/code_symbols_tool.rs b/crates/assistant_tools/src/code_symbols_tool.rs index 25689bd61d..4743c88720 100644 --- a/crates/assistant_tools/src/code_symbols_tool.rs +++ b/crates/assistant_tools/src/code_symbols_tool.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use collections::IndexMap; use gpui::{App, AsyncApp, Entity, Task}; use language::{OutlineItem, ParseStatus, Point}; @@ -129,10 +129,10 @@ impl Tool for CodeSymbolsTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let regex = match input.regex { @@ -141,7 +141,7 @@ impl Tool for CodeSymbolsTool { .build() { Ok(regex) => Some(regex), - Err(err) => return Task::ready(Err(anyhow!("Invalid regex: {err}"))), + Err(err) => return Task::ready(Err(anyhow!("Invalid regex: {err}"))).into(), }, None => None, }; @@ -150,6 +150,7 @@ impl Tool for CodeSymbolsTool { Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await, None => project_symbols(project, regex, input.offset, cx).await, }) + .into() } } diff --git a/crates/assistant_tools/src/contents_tool.rs b/crates/assistant_tools/src/contents_tool.rs index be7c4927cb..5281cfa7c7 100644 --- a/crates/assistant_tools/src/contents_tool.rs +++ b/crates/assistant_tools/src/contents_tool.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{code_symbols_tool::file_outline, schema::json_schema_for}; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use itertools::Itertools; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -103,10 +103,10 @@ impl Tool for ContentsTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; // Sometimes models will return these even though we tell it to give a path and not a glob. @@ -127,23 +127,23 @@ impl Tool for ContentsTool { .collect::>() .join("\n"); - return Task::ready(Ok(output)); + return Task::ready(Ok(output)).into(); } let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else { - return Task::ready(Err(anyhow!("Path {} not found in project", &input.path))); + return Task::ready(Err(anyhow!("Path {} not found in project", &input.path))).into(); }; let Some(worktree) = project .read(cx) .worktree_for_id(project_path.worktree_id, cx) else { - return Task::ready(Err(anyhow!("Worktree not found"))); + return Task::ready(Err(anyhow!("Worktree not found"))).into(); }; let worktree = worktree.read(cx); let Some(entry) = worktree.entry_for_path(&project_path.path) else { - return Task::ready(Err(anyhow!("Path not found: {}", input.path))); + return Task::ready(Err(anyhow!("Path not found: {}", input.path))).into(); }; // If it's a directory, list its contents @@ -184,7 +184,7 @@ impl Tool for ContentsTool { ).ok(); } - Task::ready(Ok(output)) + Task::ready(Ok(output)).into() } else { // It's a file, so read its contents let file_path = input.path.clone(); @@ -233,7 +233,7 @@ impl Tool for ContentsTool { Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start and end fields to see the implementations of symbols in the outline.")) } } - }) + }).into() } } } diff --git a/crates/assistant_tools/src/copy_path_tool.rs b/crates/assistant_tools/src/copy_path_tool.rs index 7e164dfc4d..1b3e10fe84 100644 --- a/crates/assistant_tools/src/copy_path_tool.rs +++ b/crates/assistant_tools/src/copy_path_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AppContext, Entity, Task}; use language_model::LanguageModelRequestMessage; use language_model::LanguageModelToolSchemaFormat; @@ -77,10 +77,10 @@ impl Tool for CopyPathTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let copy_task = project.update(cx, |project, cx| { match project @@ -117,5 +117,6 @@ impl Tool for CopyPathTool { )), } }) + .into() } } diff --git a/crates/assistant_tools/src/create_directory_tool.rs b/crates/assistant_tools/src/create_directory_tool.rs index 960d2b963e..b094ba3769 100644 --- a/crates/assistant_tools/src/create_directory_tool.rs +++ b/crates/assistant_tools/src/create_directory_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use language_model::LanguageModelToolSchemaFormat; @@ -68,14 +68,16 @@ impl Tool for CreateDirectoryTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let project_path = match project.read(cx).find_project_path(&input.path, cx) { Some(project_path) => project_path, - None => return Task::ready(Err(anyhow!("Path to create was outside the project"))), + None => { + return Task::ready(Err(anyhow!("Path to create was outside the project"))).into(); + } }; let destination_path: Arc = input.path.as_str().into(); @@ -89,5 +91,6 @@ impl Tool for CreateDirectoryTool { Ok(format!("Created directory {destination_path}")) }) + .into() } } diff --git a/crates/assistant_tools/src/create_file_tool.rs b/crates/assistant_tools/src/create_file_tool.rs index de111c3ac9..dc777bfb8d 100644 --- a/crates/assistant_tools/src/create_file_tool.rs +++ b/crates/assistant_tools/src/create_file_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; use language_model::LanguageModelToolSchemaFormat; @@ -73,14 +73,16 @@ impl Tool for CreateFileTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let project_path = match project.read(cx).find_project_path(&input.path, cx) { Some(project_path) => project_path, - None => return Task::ready(Err(anyhow!("Path to create was outside the project"))), + None => { + return Task::ready(Err(anyhow!("Path to create was outside the project"))).into(); + } }; let contents: Arc = input.contents.as_str().into(); let destination_path: Arc = input.path.as_str().into(); @@ -106,5 +108,6 @@ impl Tool for CreateFileTool { Ok(format!("Created file {destination_path}")) }) + .into() } } diff --git a/crates/assistant_tools/src/delete_path_tool.rs b/crates/assistant_tools/src/delete_path_tool.rs index 515dbf88af..c7d70da69d 100644 --- a/crates/assistant_tools/src/delete_path_tool.rs +++ b/crates/assistant_tools/src/delete_path_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use futures::{SinkExt, StreamExt, channel::mpsc}; use gpui::{App, AppContext, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -63,15 +63,16 @@ impl Tool for DeletePathTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let path_str = match serde_json::from_value::(input) { Ok(input) => input.path, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let Some(project_path) = project.read(cx).find_project_path(&path_str, cx) else { return Task::ready(Err(anyhow!( "Couldn't delete {path_str} because that path isn't in this project." - ))); + ))) + .into(); }; let Some(worktree) = project @@ -80,7 +81,8 @@ impl Tool for DeletePathTool { else { return Task::ready(Err(anyhow!( "Couldn't delete {path_str} because that path isn't in this project." - ))); + ))) + .into(); }; let worktree_snapshot = worktree.read(cx).snapshot(); @@ -132,5 +134,6 @@ impl Tool for DeletePathTool { )), } }) + .into() } } diff --git a/crates/assistant_tools/src/diagnostics_tool.rs b/crates/assistant_tools/src/diagnostics_tool.rs index acc36ff96b..882544b026 100644 --- a/crates/assistant_tools/src/diagnostics_tool.rs +++ b/crates/assistant_tools/src/diagnostics_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language::{DiagnosticSeverity, OffsetRangeExt}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -83,14 +83,15 @@ impl Tool for DiagnosticsTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { match serde_json::from_value::(input) .ok() .and_then(|input| input.path) { Some(path) if !path.is_empty() => { let Some(project_path) = project.read(cx).find_project_path(&path, cx) else { - return Task::ready(Err(anyhow!("Could not find path {path} in project",))); + return Task::ready(Err(anyhow!("Could not find path {path} in project",))) + .into(); }; let buffer = @@ -125,6 +126,7 @@ impl Tool for DiagnosticsTool { Ok(output) } }) + .into() } _ => { let project = project.read(cx); @@ -155,9 +157,10 @@ impl Tool for DiagnosticsTool { }); if has_diagnostics { - Task::ready(Ok(output)) + Task::ready(Ok(output)).into() } else { Task::ready(Ok("No errors or warnings found in the project.".to_string())) + .into() } } } diff --git a/crates/assistant_tools/src/fetch_tool.rs b/crates/assistant_tools/src/fetch_tool.rs index 33889cc693..9dcc5afa1b 100644 --- a/crates/assistant_tools/src/fetch_tool.rs +++ b/crates/assistant_tools/src/fetch_tool.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::schema::json_schema_for; use anyhow::{Context as _, Result, anyhow, bail}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use futures::AsyncReadExt as _; use gpui::{App, AppContext as _, Entity, Task}; use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown}; @@ -146,10 +146,10 @@ impl Tool for FetchTool { _project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let text = cx.background_spawn({ @@ -158,13 +158,15 @@ impl Tool for FetchTool { async move { Self::build_message(http_client, &url).await } }); - cx.foreground_executor().spawn(async move { - let text = text.await?; - if text.trim().is_empty() { - bail!("no textual content found"); - } + cx.foreground_executor() + .spawn(async move { + let text = text.await?; + if text.trim().is_empty() { + bail!("no textual content found"); + } - Ok(text) - }) + Ok(text) + }) + .into() } } diff --git a/crates/assistant_tools/src/find_replace_file_tool.rs b/crates/assistant_tools/src/find_replace_file_tool.rs index 580f039a27..5cfc777eb0 100644 --- a/crates/assistant_tools/src/find_replace_file_tool.rs +++ b/crates/assistant_tools/src/find_replace_file_tool.rs @@ -1,6 +1,6 @@ use crate::{replace::replace_with_flexible_indent, schema::json_schema_for}; use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AppContext, AsyncApp, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -169,10 +169,10 @@ impl Tool for FindReplaceFileTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; cx.spawn(async move |cx: &mut AsyncApp| { @@ -263,6 +263,6 @@ impl Tool for FindReplaceFileTool { Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str)) - }) + }).into() } } diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 4e581ba26d..9db00d765d 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -77,10 +77,10 @@ impl Tool for ListDirectoryTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; // Sometimes models will return these even though we tell it to give a path and not a glob. @@ -101,26 +101,26 @@ impl Tool for ListDirectoryTool { .collect::>() .join("\n"); - return Task::ready(Ok(output)); + return Task::ready(Ok(output)).into(); } let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else { - return Task::ready(Err(anyhow!("Path {} not found in project", input.path))); + return Task::ready(Err(anyhow!("Path {} not found in project", input.path))).into(); }; let Some(worktree) = project .read(cx) .worktree_for_id(project_path.worktree_id, cx) else { - return Task::ready(Err(anyhow!("Worktree not found"))); + return Task::ready(Err(anyhow!("Worktree not found"))).into(); }; let worktree = worktree.read(cx); let Some(entry) = worktree.entry_for_path(&project_path.path) else { - return Task::ready(Err(anyhow!("Path not found: {}", input.path))); + return Task::ready(Err(anyhow!("Path not found: {}", input.path))).into(); }; if !entry.is_dir() { - return Task::ready(Err(anyhow!("{} is not a directory.", input.path))); + return Task::ready(Err(anyhow!("{} is not a directory.", input.path))).into(); } let mut output = String::new(); @@ -133,8 +133,8 @@ impl Tool for ListDirectoryTool { .unwrap(); } if output.is_empty() { - return Task::ready(Ok(format!("{} is empty.", input.path))); + return Task::ready(Ok(format!("{} is empty.", input.path))).into(); } - Task::ready(Ok(output)) + Task::ready(Ok(output)).into() } } diff --git a/crates/assistant_tools/src/move_path_tool.rs b/crates/assistant_tools/src/move_path_tool.rs index 338c5a2d4d..a0d7875bfb 100644 --- a/crates/assistant_tools/src/move_path_tool.rs +++ b/crates/assistant_tools/src/move_path_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AppContext, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -90,10 +90,10 @@ impl Tool for MovePathTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let rename_task = project.update(cx, |project, cx| { match project @@ -128,5 +128,6 @@ impl Tool for MovePathTool { )), } }) + .into() } } diff --git a/crates/assistant_tools/src/now_tool.rs b/crates/assistant_tools/src/now_tool.rs index d66fd0a5c1..71b57d8dc8 100644 --- a/crates/assistant_tools/src/now_tool.rs +++ b/crates/assistant_tools/src/now_tool.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use chrono::{Local, Utc}; use gpui::{App, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -60,10 +60,10 @@ impl Tool for NowTool { _project: Entity, _action_log: Entity, _cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input: NowToolInput = match serde_json::from_value(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let now = match input.timezone { @@ -72,6 +72,6 @@ impl Tool for NowTool { }; let text = format!("The current datetime is {now}."); - Task::ready(Ok(text)) + Task::ready(Ok(text)).into() } } diff --git a/crates/assistant_tools/src/open_tool.rs b/crates/assistant_tools/src/open_tool.rs index de49f8914b..1ac242fddc 100644 --- a/crates/assistant_tools/src/open_tool.rs +++ b/crates/assistant_tools/src/open_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AppContext, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -53,10 +53,10 @@ impl Tool for OpenTool { _project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input: OpenToolInput = match serde_json::from_value(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; cx.background_spawn(async move { @@ -64,5 +64,6 @@ impl Tool for OpenTool { Ok(format!("Successfully opened {}", input.path_or_url)) }) + .into() } } diff --git a/crates/assistant_tools/src/path_search_tool.rs b/crates/assistant_tools/src/path_search_tool.rs index 919d75f8f4..17b85f8278 100644 --- a/crates/assistant_tools/src/path_search_tool.rs +++ b/crates/assistant_tools/src/path_search_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AppContext, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -71,10 +71,10 @@ impl Tool for PathSearchTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let (offset, glob) = match serde_json::from_value::(input) { Ok(input) => (input.offset, input.glob), - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let path_matcher = match PathMatcher::new([ @@ -82,7 +82,7 @@ impl Tool for PathSearchTool { if glob.is_empty() { "*" } else { &glob }, ]) { Ok(matcher) => matcher, - Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))), + Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))).into(), }; let snapshots: Vec = project .read(cx) @@ -136,6 +136,6 @@ impl Tool for PathSearchTool { Ok(response) } - }) + }).into() } } diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 6e4f23090b..5fe5cf9e97 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::sync::Arc; use crate::{code_symbols_tool::file_outline, schema::json_schema_for}; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use itertools::Itertools; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -88,14 +88,14 @@ impl Tool for ReadFileTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else { - return Task::ready(Err(anyhow!("Path {} not found in project", &input.path,))); + return Task::ready(Err(anyhow!("Path {} not found in project", &input.path,))).into(); }; let file_path = input.path.clone(); @@ -146,6 +146,6 @@ impl Tool for ReadFileTool { Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline.")) } } - }) + }).into() } } diff --git a/crates/assistant_tools/src/regex_search_tool.rs b/crates/assistant_tools/src/regex_search_tool.rs index 0eef07f776..acd408112f 100644 --- a/crates/assistant_tools/src/regex_search_tool.rs +++ b/crates/assistant_tools/src/regex_search_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use futures::StreamExt; use gpui::{App, Entity, Task}; use language::OffsetRangeExt; @@ -92,13 +92,13 @@ impl Tool for RegexSearchTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { const CONTEXT_LINES: u32 = 2; let (offset, regex, case_sensitive) = match serde_json::from_value::(input) { Ok(input) => (input.offset, input.regex, input.case_sensitive), - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let query = match SearchQuery::regex( @@ -112,7 +112,7 @@ impl Tool for RegexSearchTool { None, ) { Ok(query) => query, - Err(error) => return Task::ready(Err(error)), + Err(error) => return Task::ready(Err(error)).into(), }; let results = project.update(cx, |project, cx| project.search(query, cx)); @@ -201,6 +201,6 @@ impl Tool for RegexSearchTool { } else { Ok(format!("Found {matches_found} matches:\n{output}")) } - }) + }).into() } } diff --git a/crates/assistant_tools/src/rename_tool.rs b/crates/assistant_tools/src/rename_tool.rs index 0562fb35aa..a29ea02e5f 100644 --- a/crates/assistant_tools/src/rename_tool.rs +++ b/crates/assistant_tools/src/rename_tool.rs @@ -1,5 +1,5 @@ use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language::{self, Buffer, ToPointUtf16}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -88,10 +88,10 @@ impl Tool for RenameTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; cx.spawn(async move |cx| { @@ -138,7 +138,7 @@ impl Tool for RenameTool { })?; Ok(format!("Renamed '{}' to '{}'", input.symbol, input.new_name)) - }) + }).into() } } diff --git a/crates/assistant_tools/src/symbol_info_tool.rs b/crates/assistant_tools/src/symbol_info_tool.rs index 98000c9b54..4ff48fc11e 100644 --- a/crates/assistant_tools/src/symbol_info_tool.rs +++ b/crates/assistant_tools/src/symbol_info_tool.rs @@ -1,5 +1,5 @@ use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, AsyncApp, Entity, Task}; use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -122,10 +122,10 @@ impl Tool for SymbolInfoTool { project: Entity, action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input = match serde_json::from_value::(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; cx.spawn(async move |cx| { @@ -205,7 +205,7 @@ impl Tool for SymbolInfoTool { } else { Ok(output) } - }) + }).into() } } diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index bb67b312a4..4f7343492e 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -1,6 +1,6 @@ use crate::schema::json_schema_for; use anyhow::{Context as _, Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use futures::io::BufReader; use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt}; use gpui::{App, AppContext, Entity, Task}; @@ -79,10 +79,10 @@ impl Tool for TerminalTool { project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { let input: TerminalToolInput = match serde_json::from_value(input) { Ok(input) => input, - Err(err) => return Task::ready(Err(anyhow!(err))), + Err(err) => return Task::ready(Err(anyhow!(err))).into(), }; let project = project.read(cx); @@ -93,13 +93,15 @@ impl Tool for TerminalTool { let only_worktree = match worktrees.next() { Some(worktree) => worktree, - None => return Task::ready(Err(anyhow!("No worktrees found in the project"))), + None => { + return Task::ready(Err(anyhow!("No worktrees found in the project"))).into(); + } }; if worktrees.next().is_some() { return Task::ready(Err(anyhow!( "'.' is ambiguous in multi-root workspaces. Please specify a root directory explicitly." - ))); + ))).into(); } only_worktree.read(cx).abs_path() @@ -111,7 +113,8 @@ impl Tool for TerminalTool { { return Task::ready(Err(anyhow!( "The absolute path must be within one of the project's worktrees" - ))); + ))) + .into(); } input_path.into() @@ -120,13 +123,15 @@ impl Tool for TerminalTool { return Task::ready(Err(anyhow!( "`cd` directory {} not found in the project", &input.cd - ))); + ))) + .into(); }; worktree.read(cx).abs_path() }; cx.background_spawn(run_command_limited(working_dir, input.command)) + .into() } } diff --git a/crates/assistant_tools/src/thinking_tool.rs b/crates/assistant_tools/src/thinking_tool.rs index e94f21692f..dc4e752158 100644 --- a/crates/assistant_tools/src/thinking_tool.rs +++ b/crates/assistant_tools/src/thinking_tool.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::schema::json_schema_for; use anyhow::{Result, anyhow}; -use assistant_tool::{ActionLog, Tool}; +use assistant_tool::{ActionLog, Tool, ToolResult}; use gpui::{App, Entity, Task}; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; use project::Project; @@ -51,11 +51,12 @@ impl Tool for ThinkingTool { _project: Entity, _action_log: Entity, _cx: &mut App, - ) -> Task> { + ) -> ToolResult { // This tool just "thinks out loud" and doesn't perform any actions. Task::ready(match serde_json::from_value::(input) { Ok(_input) => Ok("Finished thinking.".to_string()), Err(err) => Err(anyhow!(err)), }) + .into() } } diff --git a/crates/context_server/src/context_server_tool.rs b/crates/context_server/src/context_server_tool.rs index a3afc3ef7b..2d03986a39 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::{Result, anyhow, bail}; -use assistant_tool::{ActionLog, Tool, ToolSource}; +use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource}; use gpui::{App, Entity, Task}; use icons::IconName; use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat}; @@ -78,7 +78,7 @@ impl Tool for ContextServerTool { _project: Entity, _action_log: Entity, cx: &mut App, - ) -> Task> { + ) -> ToolResult { if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) { let tool_name = self.tool.name.clone(); let server_clone = server.clone(); @@ -118,8 +118,9 @@ impl Tool for ContextServerTool { } Ok(result) }) + .into() } else { - Task::ready(Err(anyhow!("Context server not found"))) + Task::ready(Err(anyhow!("Context server not found"))).into() } } }