1use std::path::Path;
2use std::sync::Arc;
3
4use anyhow::{anyhow, Result};
5use assistant_tool::Tool;
6use gpui::{App, Entity, Task};
7use language_model::LanguageModelRequestMessage;
8use project::{Project, ProjectPath};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Serialize, Deserialize, JsonSchema)]
13pub struct ReadFileToolInput {
14 /// The relative path of the file to read.
15 ///
16 /// This path should never be absolute, and the first component
17 /// of the path should always be a top-level directory in a project.
18 ///
19 /// For example, if the project has the following top-level directories:
20 ///
21 /// - directory1
22 /// - directory2
23 ///
24 /// If you wanna access `file.txt` in `directory1`, you should use the path `directory1/file.txt`.
25 /// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
26 pub path: Arc<Path>,
27}
28
29pub struct ReadFileTool;
30
31impl Tool for ReadFileTool {
32 fn name(&self) -> String {
33 "read-file".into()
34 }
35
36 fn description(&self) -> String {
37 include_str!("./read_file_tool/description.md").into()
38 }
39
40 fn input_schema(&self) -> serde_json::Value {
41 let schema = schemars::schema_for!(ReadFileToolInput);
42 serde_json::to_value(&schema).unwrap()
43 }
44
45 fn run(
46 self: Arc<Self>,
47 input: serde_json::Value,
48 _messages: &[LanguageModelRequestMessage],
49 project: Entity<Project>,
50 cx: &mut App,
51 ) -> Task<Result<String>> {
52 let input = match serde_json::from_value::<ReadFileToolInput>(input) {
53 Ok(input) => input,
54 Err(err) => return Task::ready(Err(anyhow!(err))),
55 };
56
57 let Some(worktree_root_name) = input.path.components().next() else {
58 return Task::ready(Err(anyhow!("Invalid path")));
59 };
60 let Some(worktree) = project
61 .read(cx)
62 .worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx)
63 else {
64 return Task::ready(Err(anyhow!("Directory not found in the project")));
65 };
66 let project_path = ProjectPath {
67 worktree_id: worktree.read(cx).id(),
68 path: Arc::from(input.path.strip_prefix(worktree_root_name).unwrap()),
69 };
70 cx.spawn(|cx| async move {
71 let buffer = cx
72 .update(|cx| {
73 project.update(cx, |project, cx| project.open_buffer(project_path, cx))
74 })?
75 .await?;
76
77 buffer.read_with(&cx, |buffer, _cx| {
78 if buffer
79 .file()
80 .map_or(false, |file| file.disk_state().exists())
81 {
82 Ok(buffer.text())
83 } else {
84 Err(anyhow!("File does not exist"))
85 }
86 })?
87 })
88 }
89}