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