echo_agent 0.1.2

Production-grade AI Agent framework for Rust — ReAct engine, multi-agent, memory, streaming, MCP, IM channels, workflows
use std::path::PathBuf;

use crate::skills::Skill;
use crate::tools::Tool;
use crate::tools::files::files::{
    AppendFileTool, CreateFileTool, DeleteFileTool, ListDirTool, MoveFileTool, ReadFileTool,
    UpdateFileTool, WriteFileTool,
};

/// Filesystem skill
///
/// Provides the Agent with local filesystem read/write capabilities:
/// - `create_file`: create files
/// - `delete_file`: delete files
/// - `read_file`: read file content
/// - `write_file`: overwrite file
/// - `update_file`: update file
/// - `append_file`: append to file
/// - `move_file`: move files
/// - `list_dir`: list directory contents
///
/// # Security
/// Use `with_base_dir()` to restrict the Agent to only access a specified
/// directory and its subdirectories, preventing path traversal attacks
/// (`../../../etc/passwd`, etc.).
///
/// # Usage
/// ```rust
/// use echo_agent::prelude::{FileSystemSkill, AgentConfig, ReactAgent};
///
/// let config = AgentConfig::new("qwen3-max", "filesystem", "You are a file manager");
/// let mut agent = ReactAgent::new(config);
/// // No path restriction (use with caution)
/// agent.add_skill(Box::new(FileSystemSkill::new()));
///
/// // Restrict to /workspace directory
/// agent.add_skill(Box::new(FileSystemSkill::with_base_dir("/workspace")));
/// ```
pub struct FileSystemSkill {
    base_dir: Option<PathBuf>,
}

impl FileSystemSkill {
    /// Create a filesystem Skill without path restrictions
    pub fn new() -> Self {
        Self { base_dir: None }
    }

    /// Create a filesystem Skill restricted to a specified directory
    pub fn with_base_dir(base: impl Into<PathBuf>) -> Self {
        Self {
            base_dir: Some(base.into()),
        }
    }
}

impl Default for FileSystemSkill {
    fn default() -> Self {
        Self::new()
    }
}

impl Skill for FileSystemSkill {
    fn name(&self) -> &str {
        "filesystem"
    }

    fn description(&self) -> &str {
        "Local filesystem read/write capability: create files, delete files, move file paths, read file content, write file content, append to files, modify file content, and list directory contents"
    }

    fn tools(&self) -> Vec<Box<dyn Tool>> {
        let base = self.base_dir.clone();
        vec![
            Box::new(match &base {
                Some(b) => ReadFileTool::with_base_dir(b),
                None => ReadFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => WriteFileTool::with_base_dir(b),
                None => WriteFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => AppendFileTool::with_base_dir(b),
                None => AppendFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => ListDirTool::with_base_dir(b),
                None => ListDirTool::new(),
            }),
            Box::new(match &base {
                Some(b) => CreateFileTool::with_base_dir(b),
                None => CreateFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => DeleteFileTool::with_base_dir(b),
                None => DeleteFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => UpdateFileTool::with_base_dir(b),
                None => UpdateFileTool::new(),
            }),
            Box::new(match &base {
                Some(b) => MoveFileTool::with_base_dir(b),
                None => MoveFileTool::new(),
            }),
        ]
    }

    fn system_prompt_injection(&self) -> Option<String> {
        let restriction = if let Some(base) = &self.base_dir {
            format!(" (operations restricted to '{}' directory)", base.display())
        } else {
            " (no path restriction, exercise caution when operating)".to_string()
        };

        Some(format!(
            "\n\n## Filesystem Capability (FileSystem Skill){restriction}\n\
             You can operate on the local filesystem. Please use the following tools appropriately:\n\
             - `create_file(path)`: Create a file, suitable for creating an empty file, etc.\n\
             - `delete_file(path)`: Delete a file, suitable for removing unwanted old files such as configs, logs, code, etc.\n\
             - `move_file(old_path, new_path)`: Move a file path, for relocating files\n\
             - `read_file(path)`: Read file content, suitable for viewing configs, logs, code, etc.\n\
             - `write_file(path, content)`: Overwrite file content, clears existing content\n\
             - `update_file(path, old_content, new_content)`: Modify file content, replace old content with new content (exact match, first occurrence)\n\
             - `append_file(path, content)`: Append content to the end of a file, does not clear existing content\n\
             - `list_dir(path)`: List files and subdirectories in a directory\n\
             **Note**: write_file overwrites the original file; if you need to preserve original content, read_file first, then decide whether to use write_file or append_file."
        ))
    }
}