llama-cpp-v3-agent-sdk 0.1.7

Agentic tool-use loop on top of llama-cpp-v3 — local LLM agents with built-in tools
Documentation
use crate::error::AgentError;
use crate::tool::{Tool, ToolResult};

/// Read file contents with line numbers.
pub struct ReadFileTool;

impl Tool for ReadFileTool {
    fn name(&self) -> &str {
        "read"
    }

    fn description(&self) -> &str {
        "Read file contents with line numbers. Optionally specify a line range \
         with 'start_line' and 'end_line' (1-indexed, inclusive)."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Path to the file to read"
                },
                "start_line": {
                    "type": "integer",
                    "description": "First line to read (1-indexed, inclusive). Omit to read from the beginning."
                },
                "end_line": {
                    "type": "integer",
                    "description": "Last line to read (1-indexed, inclusive). Omit to read to the end."
                }
            },
            "required": ["path"]
        })
    }

    fn execute(&self, args: &serde_json::Value) -> Result<ToolResult, AgentError> {
        let path = args["path"].as_str().ok_or_else(|| AgentError::Tool {
            tool: "read".to_string(),
            message: "Missing 'path' argument".to_string(),
        })?;

        let content = match std::fs::read_to_string(path) {
            Ok(c) => c,
            Err(e) => return Ok(ToolResult::err(format!("Failed to read '{}': {}", path, e))),
        };

        let lines: Vec<&str> = content.lines().collect();
        let total_lines = lines.len();

        let start = args["start_line"]
            .as_u64()
            .map(|n| n.max(1) as usize - 1)
            .unwrap_or(0);
        let end = args["end_line"]
            .as_u64()
            .map(|n| (n as usize).min(total_lines))
            .unwrap_or(total_lines);

        if start >= total_lines {
            return Ok(ToolResult::err(format!(
                "start_line {} is beyond file length ({} lines)",
                start + 1,
                total_lines
            )));
        }

        let mut output = String::new();
        output.push_str(&format!("{} ({} lines total)\n", path, total_lines));

        for (i, line) in lines[start..end].iter().enumerate() {
            let line_num = start + i + 1;
            output.push_str(&format!("{:>4}| {}\n", line_num, line));
        }

        Ok(ToolResult::ok(output))
    }

    fn requires_permission(&self) -> bool {
        false
    }
}