diff --git a/crates/assistant_tools/src/assistant_tools.rs b/crates/assistant_tools/src/assistant_tools.rs index 0659fedcce1294391c2105a67a3e5ca9072db62c..4e788e8205831a982fd52f88be70bcc74305520e 100644 --- a/crates/assistant_tools/src/assistant_tools.rs +++ b/crates/assistant_tools/src/assistant_tools.rs @@ -1,5 +1,6 @@ mod bash_tool; mod delete_path_tool; +mod diagnostics_tool; mod edit_files_tool; mod list_directory_tool; mod now_tool; @@ -12,6 +13,7 @@ use gpui::App; use crate::bash_tool::BashTool; use crate::delete_path_tool::DeletePathTool; +use crate::diagnostics_tool::DiagnosticsTool; use crate::edit_files_tool::EditFilesTool; use crate::list_directory_tool::ListDirectoryTool; use crate::now_tool::NowTool; @@ -24,13 +26,13 @@ pub fn init(cx: &mut App) { crate::edit_files_tool::log::init(cx); let registry = ToolRegistry::global(cx); - registry.register_tool(NowTool); - registry.register_tool(ReadFileTool); - registry.register_tool(ListDirectoryTool); + registry.register_tool(BashTool); + registry.register_tool(DeletePathTool); + registry.register_tool(DiagnosticsTool); registry.register_tool(EditFilesTool); + registry.register_tool(ListDirectoryTool); + registry.register_tool(NowTool); registry.register_tool(PathSearchTool); + registry.register_tool(ReadFileTool); registry.register_tool(RegexSearchTool); - - registry.register_tool(DeletePathTool); - registry.register_tool(BashTool); } diff --git a/crates/assistant_tools/src/diagnostics_tool.rs b/crates/assistant_tools/src/diagnostics_tool.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a86b7d5d3f81b55ecf196c852a841c51dbd9da9 --- /dev/null +++ b/crates/assistant_tools/src/diagnostics_tool.rs @@ -0,0 +1,127 @@ +use anyhow::{anyhow, Result}; +use assistant_tool::Tool; +use gpui::{App, Entity, Task}; +use language::{DiagnosticSeverity, OffsetRangeExt}; +use language_model::LanguageModelRequestMessage; +use project::Project; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + sync::Arc, +}; + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct DiagnosticsToolInput { + /// The path to get diagnostics for. If not provided, returns a project-wide summary. + /// + /// This path should never be absolute, and the first component + /// of the path should always be a root directory in a project. + /// + /// + /// If the project has the following root directories: + /// + /// - lorem + /// - ipsum + /// + /// If you wanna access diagnostics for `dolor.txt` in `ipsum`, you should use the path `ipsum/dolor.txt`. + /// + pub path: Option, +} + +pub struct DiagnosticsTool; + +impl Tool for DiagnosticsTool { + fn name(&self) -> String { + "diagnostics".into() + } + + fn description(&self) -> String { + include_str!("./diagnostics_tool/description.md").into() + } + + fn input_schema(&self) -> serde_json::Value { + let schema = schemars::schema_for!(DiagnosticsToolInput); + serde_json::to_value(&schema).unwrap() + } + + fn run( + self: Arc, + input: serde_json::Value, + _messages: &[LanguageModelRequestMessage], + project: Entity, + cx: &mut App, + ) -> Task> { + let input = match serde_json::from_value::(input) { + Ok(input) => input, + Err(err) => return Task::ready(Err(anyhow!(err))), + }; + + if let Some(path) = input.path { + let Some(project_path) = project.read(cx).find_project_path(&path, cx) else { + return Task::ready(Err(anyhow!("Could not find path in project"))); + }; + let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx)); + + cx.spawn(|cx| async move { + let mut output = String::new(); + let buffer = buffer.await?; + let snapshot = buffer.read_with(&cx, |buffer, _cx| buffer.snapshot())?; + + for (_, group) in snapshot.diagnostic_groups(None) { + let entry = &group.entries[group.primary_ix]; + let range = entry.range.to_point(&snapshot); + let severity = match entry.diagnostic.severity { + DiagnosticSeverity::ERROR => "error", + DiagnosticSeverity::WARNING => "warning", + _ => continue, + }; + + writeln!( + output, + "{} at line {}: {}", + severity, + range.start.row + 1, + entry.diagnostic.message + )?; + } + + if output.is_empty() { + Ok("File doesn't have errors or warnings!".to_string()) + } else { + Ok(output) + } + }) + } else { + let project = project.read(cx); + let mut output = String::new(); + let mut has_diagnostics = false; + + for (project_path, _, summary) in project.diagnostic_summaries(true, cx) { + if summary.error_count > 0 || summary.warning_count > 0 { + let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx) + else { + continue; + }; + + has_diagnostics = true; + output.push_str(&format!( + "{}: {} error(s), {} warning(s)\n", + Path::new(worktree.read(cx).root_name()) + .join(project_path.path) + .display(), + summary.error_count, + summary.warning_count + )); + } + } + + if has_diagnostics { + Task::ready(Ok(output)) + } else { + Task::ready(Ok("No errors or warnings found in the project.".to_string())) + } + } + } +} diff --git a/crates/assistant_tools/src/diagnostics_tool/description.md b/crates/assistant_tools/src/diagnostics_tool/description.md new file mode 100644 index 0000000000000000000000000000000000000000..556daf04fef10dfe8cca6b1d6c7dc85c37766b10 --- /dev/null +++ b/crates/assistant_tools/src/diagnostics_tool/description.md @@ -0,0 +1,16 @@ +Get errors and warnings for the project or a specific file. + +This tool can be invoked after a series of edits to determine if further edits are necessary, or if the user asks to fix errors or warnings in their codebase. + +When a path is provided, shows all diagnostics for that specific file. +When no path is provided, shows a summary of error and warning counts for all files in the project. + + +To get diagnostics for a specific file: +{ + "path": "src/main.rs" +} + +To get a project-wide diagnostic summary: +{} + diff --git a/crates/assistant_tools/src/edit_files_tool.rs b/crates/assistant_tools/src/edit_files_tool.rs index 2cb3f06f82f0559a9484200342910c188dd9efc0..3ad8113fbf3263facff5827600ce2465d33ea88d 100644 --- a/crates/assistant_tools/src/edit_files_tool.rs +++ b/crates/assistant_tools/src/edit_files_tool.rs @@ -11,7 +11,7 @@ use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; use log::{EditToolLog, EditToolRequestId}; -use project::{Project, ProjectPath}; +use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -178,23 +178,9 @@ impl EditFilesTool { for action in new_actions { let project_path = project.read_with(&cx, |project, cx| { - let worktree_root_name = action - .file_path() - .components() - .next() - .context("Invalid path")?; - let worktree = project - .worktree_for_root_name( - &worktree_root_name.as_os_str().to_string_lossy(), - cx, - ) - .context("Directory not found in project")?; - anyhow::Ok(ProjectPath { - worktree_id: worktree.read(cx).id(), - path: Arc::from( - action.file_path().strip_prefix(worktree_root_name).unwrap(), - ), - }) + project + .find_project_path(action.file_path(), cx) + .context("Path not found in project") })??; let buffer = project diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 989ecdf9937c6a809f6c67bdb747e5f781891bb9..3737319f26d5ca9afbcfbc660155f509de0ad18f 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -62,19 +62,18 @@ impl Tool for ListDirectoryTool { 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(project_path) = project.read(cx).find_project_path(&input.path, cx) else { + return Task::ready(Err(anyhow!("Path not found in project"))); }; let Some(worktree) = project .read(cx) - .worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx) + .worktree_for_id(project_path.worktree_id, cx) else { - return Task::ready(Err(anyhow!("Directory not found in the project"))); + return Task::ready(Err(anyhow!("Worktree not found"))); }; - let path = input.path.strip_prefix(worktree_root_name).unwrap(); let worktree = worktree.read(cx); - let Some(entry) = worktree.entry_for_path(path) else { + let Some(entry) = worktree.entry_for_path(&project_path.path) else { return Task::ready(Err(anyhow!("Path not found: {}", input.path.display()))); }; @@ -83,13 +82,11 @@ impl Tool for ListDirectoryTool { } let mut output = String::new(); - for entry in worktree.child_entries(path) { + for entry in worktree.child_entries(&project_path.path) { writeln!( output, "{}", - Path::new(worktree_root_name.as_os_str()) - .join(&entry.path) - .display(), + Path::new(worktree.root_name()).join(&entry.path).display(), ) .unwrap(); } diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 53d73f2064b507df54aa549f0c54a6f8deee667f..2190c721e9f174319a31c270171461b9360fe9b0 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use assistant_tool::Tool; use gpui::{App, Entity, Task}; use language_model::LanguageModelRequestMessage; -use project::{Project, ProjectPath}; +use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -56,18 +56,8 @@ impl Tool for ReadFileTool { Err(err) => return Task::ready(Err(anyhow!(err))), }; - let Some(worktree_root_name) = input.path.components().next() else { - return Task::ready(Err(anyhow!("Invalid path"))); - }; - let Some(worktree) = project - .read(cx) - .worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx) - else { - return Task::ready(Err(anyhow!("Directory not found in the project"))); - }; - let project_path = ProjectPath { - worktree_id: worktree.read(cx).id(), - path: Arc::from(input.path.strip_prefix(worktree_root_name).unwrap()), + let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else { + return Task::ready(Err(anyhow!("Path not found in project"))); }; cx.spawn(|cx| async move { let buffer = cx