read_file_tool.rs

 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    /// <example>
20    /// If the project has the following top-level directories:
21    ///
22    /// - directory1
23    /// - directory2
24    ///
25    /// If you wanna access `file.txt` in `directory1`, you should use the path `directory1/file.txt`.
26    /// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
27    /// </example>
28    pub path: Arc<Path>,
29}
30
31pub struct ReadFileTool;
32
33impl Tool for ReadFileTool {
34    fn name(&self) -> String {
35        "read-file".into()
36    }
37
38    fn description(&self) -> String {
39        include_str!("./read_file_tool/description.md").into()
40    }
41
42    fn input_schema(&self) -> serde_json::Value {
43        let schema = schemars::schema_for!(ReadFileToolInput);
44        serde_json::to_value(&schema).unwrap()
45    }
46
47    fn run(
48        self: Arc<Self>,
49        input: serde_json::Value,
50        _messages: &[LanguageModelRequestMessage],
51        project: Entity<Project>,
52        cx: &mut App,
53    ) -> Task<Result<String>> {
54        let input = match serde_json::from_value::<ReadFileToolInput>(input) {
55            Ok(input) => input,
56            Err(err) => return Task::ready(Err(anyhow!(err))),
57        };
58
59        let Some(worktree_root_name) = input.path.components().next() else {
60            return Task::ready(Err(anyhow!("Invalid path")));
61        };
62        let Some(worktree) = project
63            .read(cx)
64            .worktree_for_root_name(&worktree_root_name.as_os_str().to_string_lossy(), cx)
65        else {
66            return Task::ready(Err(anyhow!("Directory not found in the project")));
67        };
68        let project_path = ProjectPath {
69            worktree_id: worktree.read(cx).id(),
70            path: Arc::from(input.path.strip_prefix(worktree_root_name).unwrap()),
71        };
72        cx.spawn(|cx| async move {
73            let buffer = cx
74                .update(|cx| {
75                    project.update(cx, |project, cx| project.open_buffer(project_path, cx))
76                })?
77                .await?;
78
79            buffer.read_with(&cx, |buffer, _cx| {
80                if buffer
81                    .file()
82                    .map_or(false, |file| file.disk_state().exists())
83                {
84                    Ok(buffer.text())
85                } else {
86                    Err(anyhow!("File does not exist"))
87                }
88            })?
89        })
90    }
91}