diff --git a/crates/agent/src/tools/read_file_tool.rs b/crates/agent/src/tools/read_file_tool.rs index 4fa27114c8e2ea77e93afdc3dd9f0c710907a15c..da0cbddb86aadb85706e276afe6c8075aeba9a45 100644 --- a/crates/agent/src/tools/read_file_tool.rs +++ b/crates/agent/src/tools/read_file_tool.rs @@ -184,6 +184,13 @@ impl AgentTool for ReadFileTool { anyhow::Ok(()) }).map_err(tool_content_err)?; + if fs.is_dir(&abs_path).await { + return Err(tool_content_err(format!( + "{} is a directory, not a file. Use the list_directory tool to explore directory contents.", + &input.path + ))); + } + if let Some(canonical_target) = &symlink_canonical_target { let authorize = cx.update(|cx| { authorize_symlink_access( @@ -356,6 +363,39 @@ mod test { use std::sync::Arc; use util::path; + #[gpui::test] + async fn test_read_directory_path(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + "some_dir": {} + }), + ) + .await; + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let action_log = cx.new(|_| ActionLog::new(project.clone())); + let tool = Arc::new(ReadFileTool::new(project, action_log, true)); + let (event_stream, _) = ToolCallEventStream::test(); + + let result = cx + .update(|cx| { + let input = ReadFileToolInput { + path: "root/some_dir".to_string(), + start_line: None, + end_line: None, + }; + tool.run(ToolInput::resolved(input), event_stream, cx) + }) + .await; + assert_eq!( + error_text(result.unwrap_err()), + "root/some_dir is a directory, not a file. Use the list_directory tool to explore directory contents." + ); + } + #[gpui::test] async fn test_read_nonexistent_file(cx: &mut TestAppContext) { init_test(cx);