Skip to main content

deepseek/agent/builtin_tools/
read.rs

1use 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}