139 lines
5.2 KiB
Rust
139 lines
5.2 KiB
Rust
//! Tool descriptions and file operations for the agent.
|
|
|
|
use crate::llm::Tool;
|
|
use sausage_actor::{Value, json};
|
|
use std::io;
|
|
|
|
pub fn definitions() -> Vec<Tool> {
|
|
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/<id>`. 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/<id>`. 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/<id>/source` to `/public/drafts/<id>`. 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<Value> {
|
|
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<Value> {
|
|
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<Value> {
|
|
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<Value> {
|
|
let mut entries: Vec<Value> = 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<Value> {
|
|
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<u64> {
|
|
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)
|
|
}
|