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