| 879 | } |
| 880 | |
| 881 | fn exec_read_file(&self, args: &serde_json::Value) -> Result<String, String> { |
| 882 | let path_str = args["path"] |
| 883 | .as_str() |
| 884 | .ok_or("missing required parameter: path")?; |
| 885 | |
| 886 | // Prevent path traversal. |
| 887 | if path_str.contains("..") { |
| 888 | return Err("path must not contain '..'".into()); |
| 889 | } |
| 890 | |
| 891 | let full_path = self.root.join(path_str); |
| 892 | let content = std::fs::read_to_string(&full_path) |
| 893 | .map_err(|e| format!("cannot read {path_str}: {e}"))?; |
| 894 | |
| 895 | // Optionally slice by line range. |
| 896 | let start_line = args["start_line"].as_u64().map(|n| n as usize); |
| 897 | let end_line = args["end_line"].as_u64().map(|n| n as usize); |
| 898 | |
| 899 | let has_line_range = start_line.is_some() || end_line.is_some(); |
| 900 | let total_lines = content.lines().count(); |
| 901 | |
| 902 | // If no line range and file is large, return a structured outline. |
| 903 | // The LLM should use list_entities or code_search to find the |
| 904 | // right line range first. |
| 905 | if !has_line_range && content.len() > READ_FILE_PREVIEW_THRESHOLD { |
| 906 | return Ok(build_file_outline(path_str, &content, total_lines)); |
| 907 | } |
| 908 | |
| 909 | let output = match (start_line, end_line) { |
| 910 | (Some(s), Some(e)) => { |
| 911 | let s = s.saturating_sub(1); // 1-based to 0-based |
| 912 | content |
| 913 | .lines() |
| 914 | .skip(s) |
| 915 | .take(e.saturating_sub(s)) |
| 916 | .collect::<Vec<_>>() |
| 917 | .join("\n") |
| 918 | } |
| 919 | (Some(s), None) => { |
| 920 | let s = s.saturating_sub(1); |
| 921 | content |
| 922 | .lines() |
| 923 | .skip(s) |
| 924 | .take(200) // cap open-ended reads at 200 lines |
| 925 | .collect::<Vec<_>>() |
| 926 | .join("\n") |
| 927 | } |
| 928 | (None, Some(e)) => content.lines().take(e).collect::<Vec<_>>().join("\n"), |
| 929 | (None, None) => content, |
| 930 | }; |
| 931 | |
| 932 | // Final cap on output size. |
| 933 | if output.len() > MAX_READ_FILE_BYTES { |
| 934 | Ok(format!( |
| 935 | "{}\n\n... truncated ({} bytes total)", |
| 936 | &output[..MAX_READ_FILE_BYTES], |
| 937 | output.len() |
| 938 | )) |