1use std::path::Path;
2use std::sync::Arc;
3
4use anyhow::{anyhow, Result};
5use assistant_tool::{ActionLog, Tool};
6use gpui::{App, Entity, Task};
7use language_model::LanguageModelRequestMessage;
8use project::Project;
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 root directory in a project.
18 ///
19 /// <example>
20 /// If the project has the following root 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 _action_log: Entity<ActionLog>,
53 cx: &mut App,
54 ) -> Task<Result<String>> {
55 let input = match serde_json::from_value::<ReadFileToolInput>(input) {
56 Ok(input) => input,
57 Err(err) => return Task::ready(Err(anyhow!(err))),
58 };
59
60 let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
61 return Task::ready(Err(anyhow!("Path not found in project")));
62 };
63 cx.spawn(|cx| async move {
64 let buffer = cx
65 .update(|cx| {
66 project.update(cx, |project, cx| project.open_buffer(project_path, cx))
67 })?
68 .await?;
69
70 buffer.read_with(&cx, |buffer, _cx| {
71 if buffer
72 .file()
73 .map_or(false, |file| file.disk_state().exists())
74 {
75 Ok(buffer.text())
76 } else {
77 Err(anyhow!("File does not exist"))
78 }
79 })?
80 })
81 }
82}