agent-works 0.1.1

Batteries-included Agent toolbox built on agent-base — MCP multi-server, Skills, built-in file tools, CLI REPL
Documentation
use std::path::PathBuf;

use async_trait::async_trait;
use serde_json::{json, Value};

use agent_base::{AgentError, AgentResult, Tool, ToolContext, ToolControlFlow, ToolOutput};

pub struct ListDirectoryTool {
    pub workspace: PathBuf,
}

#[async_trait]
impl Tool for ListDirectoryTool {
    fn name(&self) -> &'static str {
        "list_directory"
    }

    fn definition(&self) -> Value {
        json!({
            "type": "function",
            "function": {
                "name": "list_directory",
                "description": "List the contents of a directory",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path to the directory to list"
                        }
                    },
                    "required": []
                }
            }
        })
    }

    async fn call(&self, args: &Value, _ctx: &ToolContext) -> AgentResult<ToolOutput> {
        let path = args["path"].as_str().unwrap_or(".");
        let full_path = self.workspace.join(path);

        let mut entries = tokio::fs::read_dir(&full_path)
            .await
            .map_err(|e| AgentError::internal(format!("failed to read dir {}: {e}", full_path.display())))?;

        let mut items: Vec<Value> = Vec::new();
        loop {
            let entry = entries.next_entry().await.map_err(|e| {
                AgentError::internal(format!("failed to read entry: {e}"))
            })?;
            let Some(entry) = entry else {
                break;
            };
            let file_type = entry.file_type().await.ok();
            let is_dir = file_type.as_ref().is_some_and(|ft| ft.is_dir());
            let is_file = file_type.as_ref().is_some_and(|ft| ft.is_file());
            items.push(json!({
                "name": entry.file_name().to_string_lossy(),
                "is_dir": is_dir,
                "is_file": is_file,
            }));
        }

        let names: Vec<String> = items
            .iter()
            .map(|v| v["name"].as_str().unwrap_or("").to_string())
            .collect();
        let summary = if names.is_empty() {
            format!("Directory {} is empty", path)
        } else {
            names.join("\n")
        };

        Ok(ToolOutput {
            summary,
            raw: Some(json!({"path": path, "entries": items})),
            control_flow: ToolControlFlow::Break,
            truncation: None,
        })
    }
}