deepseek/agent/builtin_tools/
read.rs1use async_trait::async_trait;
2use serde_json::{json, Value};
3
4use crate::agent::tool::{Tool, ToolDefinition};
5
6
7pub struct ReadTool;
8
9#[async_trait]
10impl Tool for ReadTool {
11 fn name(&self) -> &str {
12 "Read"
13 }
14
15 fn read_only_hint(&self) -> bool {
16 true
17 }
18
19 fn definition(&self) -> ToolDefinition {
20 ToolDefinition {
21 name: self.name().to_string(),
22 description: "Read a UTF-8 text file from disk. Optionally slice by 1-based line offset and limit.".into(),
23 parameters: json!({
24 "type": "object",
25 "properties": {
26 "path": { "type": "string", "description": "Absolute or cwd-relative path." },
27 "offset": { "type": "integer", "description": "1-based line to start at.", "minimum": 1 },
28 "limit": { "type": "integer", "description": "Maximum number of lines to return.", "minimum": 1 }
29 },
30 "required": ["path"]
31 }),
32 }
33 }
34
35 async fn call_json(&self, args: Value) -> Result<String, String> {
36 let path = args
37 .get("path")
38 .and_then(Value::as_str)
39 .ok_or_else(|| "Read: missing string `path`".to_string())?;
40 let offset = args.get("offset").and_then(Value::as_u64).map(|n| n as usize);
41 let limit = args.get("limit").and_then(Value::as_u64).map(|n| n as usize);
42
43 let body = tokio::fs::read_to_string(path)
44 .await
45 .map_err(|e| format!("Read({path}): {e}"))?;
46
47 if offset.is_none() && limit.is_none() {
48 return Ok(body);
49 }
50
51 let mut out = String::new();
52 let start = offset.unwrap_or(1).saturating_sub(1);
53 let count = limit.unwrap_or(usize::MAX);
54 for line in body.lines().skip(start).take(count) {
55 out.push_str(line);
56 out.push('\n');
57 }
58 Ok(out)
59 }
60}