//! Tool descriptions and file operations for the agent. use crate::llm::Tool; use sausage_actor::{Value, json}; use std::io; pub fn definitions() -> Vec { vec![ Tool { name: "send_message", description: "Send an actor a JSON body with a `type`. `broker` and `cargo` are built-in actors; see `/actor/broker/public/PROTOCOL.md`. With `wait_seconds`, the reply's `reply_to` matches the returned ID. Returns the ID, reply, and other messages received.", parameters: schema( json!({ "to": {"type": "string", "description": "Actor ID"}, "body": {"type": "object", "description": "JSON object with a type field"}, "wait_seconds": {"type": "number", "description": "Seconds to wait for a reply. Default 0."} }), &["to", "body"], ), }, Tool { name: "read_file", description: "Read a text file under `/data`, `/public`, or `/actor/`. Reads all bytes by default; `offset` and `max_bytes` are optional.", parameters: schema( json!({ "path": {"type": "string"}, "offset": {"type": "integer", "description": "Byte offset (default 0)"}, "max_bytes": {"type": "integer", "description": "Optional maximum number of bytes to read"} }), &["path"], ), }, Tool { name: "write_file", description: "Write text under `/data` or `/public`. Parent folders are created.", parameters: schema( json!({"path": {"type": "string"}, "content": {"type": "string"}}), &["path", "content"], ), }, Tool { name: "list_dir", description: "List entries in a directory under `/data`, `/public`, or `/actor/`. Returns each name, type, and size.", parameters: schema(json!({"path": {"type": "string"}}), &["path"]), }, Tool { name: "copy_dir", description: "Copy a directory tree, for example `/actor//source` to `/public/drafts/`. Skips `target/` folders.", parameters: schema( json!({"from": {"type": "string"}, "to": {"type": "string"}}), &["from", "to"], ), }, ] } fn schema(properties: Value, required: &[&str]) -> Value { json!({"type": "object", "properties": properties, "required": required}) } pub fn run(name: &str, args: &Value) -> io::Result { match name { "read_file" => read_file(args), "write_file" => write_file(args), "list_dir" => list_dir(args), "copy_dir" => copy_dir(args), other => Ok(json!({"error": format!("unknown tool {other}")})), } } fn read_file(args: &Value) -> io::Result { let path = args["path"].as_str().unwrap_or_default(); let bytes = std::fs::read(path)?; let offset = (args["offset"].as_u64().unwrap_or(0) as usize).min(bytes.len()); let end = args["max_bytes"].as_u64().map_or(bytes.len(), |max| { offset.saturating_add(max as usize).min(bytes.len()) }); Ok(json!({ "path": path, "size": bytes.len(), "offset": offset, "content": String::from_utf8_lossy(&bytes[offset..end]), "truncated": end < bytes.len(), })) } fn write_file(args: &Value) -> io::Result { let path = args["path"].as_str().unwrap_or_default(); if let Some(parent) = std::path::Path::new(path).parent() { let _ = std::fs::create_dir_all(parent); } std::fs::write(path, args["content"].as_str().unwrap_or_default())?; Ok(json!({"written": path})) } fn list_dir(args: &Value) -> io::Result { let mut entries: Vec = std::fs::read_dir(args["path"].as_str().unwrap_or_default())? .filter_map(|entry| entry.ok()) .map(|entry| { let metadata = entry.metadata().ok(); json!({ "name": entry.file_name().to_string_lossy(), "type": if metadata.as_ref().is_some_and(|meta| meta.is_dir()) { "dir" } else { "file" }, "size": metadata.map(|meta| meta.len()), }) }) .collect(); entries.sort_by(|left, right| left["name"].as_str().cmp(&right["name"].as_str())); Ok(json!({"entries": entries})) } fn copy_dir(args: &Value) -> io::Result { let count = copy_tree( args["from"].as_str().unwrap_or_default(), args["to"].as_str().unwrap_or_default(), )?; Ok(json!({"copied_files": count})) } fn copy_tree(from: &str, to: &str) -> io::Result { std::fs::create_dir_all(to)?; let mut count = 0; for entry in std::fs::read_dir(from)? { let entry = entry?; let name = entry.file_name().to_string_lossy().to_string(); let src = format!("{from}/{name}"); let dst = format!("{to}/{name}"); if entry.file_type()?.is_dir() { if name != "target" { count += copy_tree(&src, &dst)?; } } else { std::fs::copy(src, dst)?; count += 1; } } Ok(count) }