Allow read-file tool to read a subset of a file (#26966)

Richard Feldman created

Release Notes:

- N/A

Change summary

Cargo.lock                                   |  1 
crates/assistant_tools/Cargo.toml            |  1 
crates/assistant_tools/src/read_file_tool.rs | 23 +++++++++++++++++++++
3 files changed, 24 insertions(+), 1 deletion(-)

Detailed changes

Cargo.lock 🔗

@@ -715,6 +715,7 @@ dependencies = [
  "feature_flags",
  "futures 0.3.31",
  "gpui",
+ "itertools 0.14.0",
  "language",
  "language_model",
  "pretty_assertions",

crates/assistant_tools/Cargo.toml 🔗

@@ -18,6 +18,7 @@ chrono.workspace = true
 collections.workspace = true
 feature_flags.workspace = true
 futures.workspace = true
+itertools.workspace = true
 gpui.workspace = true
 language.workspace = true
 language_model.workspace = true

crates/assistant_tools/src/read_file_tool.rs 🔗

@@ -4,6 +4,7 @@ use std::sync::Arc;
 use anyhow::{anyhow, Result};
 use assistant_tool::{ActionLog, Tool};
 use gpui::{App, Entity, Task};
+use itertools::Itertools;
 use language_model::LanguageModelRequestMessage;
 use project::Project;
 use schemars::JsonSchema;
@@ -26,6 +27,14 @@ pub struct ReadFileToolInput {
     /// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
     /// </example>
     pub path: Arc<Path>,
+
+    /// Optional line number to start reading from (0-based index)
+    #[serde(default)]
+    pub start_line: Option<usize>,
+
+    /// Optional number of lines to read
+    #[serde(default)]
+    pub line_count: Option<usize>,
 }
 
 pub struct ReadFileTool;
@@ -73,7 +82,19 @@ impl Tool for ReadFileTool {
                     .file()
                     .map_or(false, |file| file.disk_state().exists())
                 {
-                    Ok(buffer.text())
+                    let text = buffer.text();
+                    let string = if input.start_line.is_some() || input.line_count.is_some() {
+                        let lines = text.split('\n').skip(input.start_line.unwrap_or(0));
+                        if let Some(line_count) = input.line_count {
+                            Itertools::intersperse(lines.take(line_count), "\n").collect()
+                        } else {
+                            Itertools::intersperse(lines, "\n").collect()
+                        }
+                    } else {
+                        text
+                    };
+
+                    Ok(string)
                 } else {
                     Err(anyhow!("File does not exist"))
                 }