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    /// 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}