Cargo.lock 🔗
@@ -658,6 +658,7 @@ dependencies = [
"assistant_tool",
"chrono",
"gpui",
+ "project",
"schemars",
"serde",
"serde_json",
Marshall Bowers created
This PR adds two new tools to Assistant 2:
- `list-worktrees` - Lists the worktrees in a project
- `read-file` - Reads a file at the given path in the project
I don't see `list-worktrees` sticking around long-term, as when we have
tools for listing files those will include the worktree IDs along with
the path, but making this tool available allows the model to utilize
`read-file` when it otherwise wouldn't be able to.
Release Notes:
- N/A
Cargo.lock | 1
crates/assistant_tools/Cargo.toml | 1
crates/assistant_tools/src/assistant_tools.rs | 6 +
crates/assistant_tools/src/list_worktrees_tool.rs | 84 +++++++++++++++++
crates/assistant_tools/src/read_file_tool.rs | 69 +++++++++++++
5 files changed, 161 insertions(+)
@@ -658,6 +658,7 @@ dependencies = [
"assistant_tool",
"chrono",
"gpui",
+ "project",
"schemars",
"serde",
"serde_json",
@@ -16,6 +16,7 @@ anyhow.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
gpui.workspace = true
+project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
@@ -1,13 +1,19 @@
+mod list_worktrees_tool;
mod now_tool;
+mod read_file_tool;
use assistant_tool::ToolRegistry;
use gpui::App;
+use crate::list_worktrees_tool::ListWorktreesTool;
use crate::now_tool::NowTool;
+use crate::read_file_tool::ReadFileTool;
pub fn init(cx: &mut App) {
assistant_tool::init(cx);
let registry = ToolRegistry::global(cx);
registry.register_tool(NowTool);
+ registry.register_tool(ListWorktreesTool);
+ registry.register_tool(ReadFileTool);
}
@@ -0,0 +1,84 @@
+use std::sync::Arc;
+
+use anyhow::{anyhow, Result};
+use assistant_tool::Tool;
+use gpui::{App, Task, WeakEntity, Window};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use workspace::Workspace;
+
+#[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,
+ workspace: WeakEntity<Workspace>,
+ _window: &mut Window,
+ cx: &mut App,
+ ) -> Task<Result<String>> {
+ let Some(workspace) = workspace.upgrade() else {
+ return Task::ready(Err(anyhow!("workspace dropped")));
+ };
+
+ let project = workspace.read(cx).project().clone();
+
+ 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)
+ })?
+ })
+ }
+}
@@ -0,0 +1,69 @@
+use std::path::Path;
+use std::sync::Arc;
+
+use anyhow::{anyhow, Result};
+use assistant_tool::Tool;
+use gpui::{App, Task, WeakEntity, Window};
+use project::{ProjectPath, WorktreeId};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use workspace::Workspace;
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+pub struct ReadFileToolInput {
+ /// The ID of the worktree in which the file resides.
+ pub worktree_id: usize,
+ /// The path to the file to read.
+ ///
+ /// This path is relative to the worktree root, it must not be an absolute path.
+ pub path: Arc<Path>,
+}
+
+pub struct ReadFileTool;
+
+impl Tool for ReadFileTool {
+ fn name(&self) -> String {
+ "read-file".into()
+ }
+
+ 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()
+ }
+
+ fn input_schema(&self) -> serde_json::Value {
+ let schema = schemars::schema_for!(ReadFileToolInput);
+ serde_json::to_value(&schema).unwrap()
+ }
+
+ fn run(
+ self: Arc<Self>,
+ input: serde_json::Value,
+ workspace: WeakEntity<Workspace>,
+ _window: &mut Window,
+ cx: &mut App,
+ ) -> Task<Result<String>> {
+ let Some(workspace) = workspace.upgrade() else {
+ return Task::ready(Err(anyhow!("workspace dropped")));
+ };
+
+ let input = match serde_json::from_value::<ReadFileToolInput>(input) {
+ Ok(input) => input,
+ Err(err) => return Task::ready(Err(anyhow!(err))),
+ };
+
+ let project = workspace.read(cx).project().clone();
+ let project_path = ProjectPath {
+ worktree_id: WorktreeId::from_usize(input.worktree_id),
+ path: input.path,
+ };
+ cx.spawn(|cx| async move {
+ let buffer = cx
+ .update(|cx| {
+ project.update(cx, |project, cx| project.open_buffer(project_path, cx))
+ })?
+ .await?;
+
+ cx.update(|cx| buffer.read(cx).text())
+ })
+ }
+}