agent: Indiciate files and folders in list_directory
(#31448)
Otherwise, the agent confuses directories with files in cases where dirs are named like files (`TODO`, `task.js`, etc.) Release Notes: - N/A
This commit is contained in:
parent
119beb210a
commit
7ec61ceec9
1 changed files with 277 additions and 8 deletions
|
@ -125,18 +125,287 @@ impl Tool for ListDirectoryTool {
|
||||||
return Task::ready(Err(anyhow!("{} is not a directory.", input.path))).into();
|
return Task::ready(Err(anyhow!("{} is not a directory.", input.path))).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = String::new();
|
let mut folders = Vec::new();
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
for entry in worktree.child_entries(&project_path.path) {
|
for entry in worktree.child_entries(&project_path.path) {
|
||||||
writeln!(
|
let full_path = Path::new(worktree.root_name())
|
||||||
output,
|
.join(&entry.path)
|
||||||
"{}",
|
.display()
|
||||||
Path::new(worktree.root_name()).join(&entry.path).display(),
|
.to_string();
|
||||||
)
|
if entry.is_dir() {
|
||||||
.unwrap();
|
folders.push(full_path);
|
||||||
|
} else {
|
||||||
|
files.push(full_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
if !folders.is_empty() {
|
||||||
|
writeln!(output, "# Folders:\n{}", folders.join("\n")).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !files.is_empty() {
|
||||||
|
writeln!(output, "\n# Files:\n{}", files.join("\n")).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
if output.is_empty() {
|
if output.is_empty() {
|
||||||
return Task::ready(Ok(format!("{} is empty.", input.path).into())).into();
|
writeln!(output, "{} is empty.", input.path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::ready(Ok(output.into())).into()
|
Task::ready(Ok(output.into())).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use assistant_tool::Tool;
|
||||||
|
use gpui::{AppContext, TestAppContext};
|
||||||
|
use indoc::indoc;
|
||||||
|
use language_model::fake_provider::FakeLanguageModel;
|
||||||
|
use project::{FakeFs, Project};
|
||||||
|
use serde_json::json;
|
||||||
|
use settings::SettingsStore;
|
||||||
|
use util::path;
|
||||||
|
|
||||||
|
fn platform_paths(path_str: &str) -> String {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
path_str.replace("/", "\\")
|
||||||
|
} else {
|
||||||
|
path_str.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
cx.set_global(settings_store);
|
||||||
|
language::init(cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_list_directory_separates_files_and_dirs(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/project",
|
||||||
|
json!({
|
||||||
|
"src": {
|
||||||
|
"main.rs": "fn main() {}",
|
||||||
|
"lib.rs": "pub fn hello() {}",
|
||||||
|
"models": {
|
||||||
|
"user.rs": "struct User {}",
|
||||||
|
"post.rs": "struct Post {}"
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"helper.rs": "pub fn help() {}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tests": {
|
||||||
|
"integration_test.rs": "#[test] fn test() {}"
|
||||||
|
},
|
||||||
|
"README.md": "# Project",
|
||||||
|
"Cargo.toml": "[package]"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||||
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
|
let model = Arc::new(FakeLanguageModel::default());
|
||||||
|
let tool = Arc::new(ListDirectoryTool);
|
||||||
|
|
||||||
|
// Test listing root directory
|
||||||
|
let input = json!({
|
||||||
|
"path": "project"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| {
|
||||||
|
tool.clone().run(
|
||||||
|
input,
|
||||||
|
Arc::default(),
|
||||||
|
project.clone(),
|
||||||
|
action_log.clone(),
|
||||||
|
model.clone(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.output
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let content = result.content.as_str().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
content,
|
||||||
|
platform_paths(indoc! {"
|
||||||
|
# Folders:
|
||||||
|
project/src
|
||||||
|
project/tests
|
||||||
|
|
||||||
|
# Files:
|
||||||
|
project/Cargo.toml
|
||||||
|
project/README.md
|
||||||
|
"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test listing src directory
|
||||||
|
let input = json!({
|
||||||
|
"path": "project/src"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| {
|
||||||
|
tool.clone().run(
|
||||||
|
input,
|
||||||
|
Arc::default(),
|
||||||
|
project.clone(),
|
||||||
|
action_log.clone(),
|
||||||
|
model.clone(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.output
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let content = result.content.as_str().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
content,
|
||||||
|
platform_paths(indoc! {"
|
||||||
|
# Folders:
|
||||||
|
project/src/models
|
||||||
|
project/src/utils
|
||||||
|
|
||||||
|
# Files:
|
||||||
|
project/src/lib.rs
|
||||||
|
project/src/main.rs
|
||||||
|
"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test listing directory with only files
|
||||||
|
let input = json!({
|
||||||
|
"path": "project/tests"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| {
|
||||||
|
tool.clone().run(
|
||||||
|
input,
|
||||||
|
Arc::default(),
|
||||||
|
project.clone(),
|
||||||
|
action_log.clone(),
|
||||||
|
model.clone(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.output
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let content = result.content.as_str().unwrap();
|
||||||
|
assert!(!content.contains("# Folders:"));
|
||||||
|
assert!(content.contains("# Files:"));
|
||||||
|
assert!(content.contains(&platform_paths("project/tests/integration_test.rs")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_list_directory_empty_directory(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/project",
|
||||||
|
json!({
|
||||||
|
"empty_dir": {}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||||
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
|
let model = Arc::new(FakeLanguageModel::default());
|
||||||
|
let tool = Arc::new(ListDirectoryTool);
|
||||||
|
|
||||||
|
let input = json!({
|
||||||
|
"path": "project/empty_dir"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| tool.run(input, Arc::default(), project, action_log, model, None, cx))
|
||||||
|
.output
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let content = result.content.as_str().unwrap();
|
||||||
|
assert_eq!(content, "project/empty_dir is empty.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_list_directory_error_cases(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/project",
|
||||||
|
json!({
|
||||||
|
"file.txt": "content"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||||
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
|
let model = Arc::new(FakeLanguageModel::default());
|
||||||
|
let tool = Arc::new(ListDirectoryTool);
|
||||||
|
|
||||||
|
// Test non-existent path
|
||||||
|
let input = json!({
|
||||||
|
"path": "project/nonexistent"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| {
|
||||||
|
tool.clone().run(
|
||||||
|
input,
|
||||||
|
Arc::default(),
|
||||||
|
project.clone(),
|
||||||
|
action_log.clone(),
|
||||||
|
model.clone(),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.output
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("Path not found"));
|
||||||
|
|
||||||
|
// Test trying to list a file instead of directory
|
||||||
|
let input = json!({
|
||||||
|
"path": "project/file.txt"
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cx
|
||||||
|
.update(|cx| tool.run(input, Arc::default(), project, action_log, model, None, cx))
|
||||||
|
.output
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("is not a directory")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue