mentra 0.6.0

An agent runtime for tool-using LLM applications
Documentation
#[path = "files/execution.rs"]
mod execution;
#[path = "files/input.rs"]
mod input;
#[path = "files/preview.rs"]
mod preview;
#[path = "files/schema.rs"]
mod schema;
#[path = "files/workspace.rs"]
mod workspace;

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

use crate::tool::{
    ParallelToolContext, RuntimeToolDescriptor, ToolApprovalCategory, ToolAuthorizationPreview,
    ToolCapability, ToolContext, ToolDefinition, ToolDurability, ToolExecutionCategory,
    ToolExecutor, ToolResult, ToolSideEffectLevel,
};

use self::{
    execution::execute_files_tool, input::file_execution_category,
    preview::build_files_authorization_preview,
};

pub struct FilesTool;

impl ToolDefinition for FilesTool {
    fn descriptor(&self) -> RuntimeToolDescriptor {
        RuntimeToolDescriptor::builder("files")
            .description("Read, search, list, create, update, move, and delete files within the workspace.")
            .input_schema(json!({
                "type": "object",
                "properties": {
                    "workingDirectory": {
                        "type": "string",
                        "description": "Optional directory used to resolve relative operation paths"
                    },
                    "operations": {
                        "type": "array",
                        "description": "Ordered file operations to execute. Later reads can observe earlier staged writes.",
                        "items": {
                            "oneOf": [
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "read" },
                                        "path": { "type": "string" },
                                        "offset": { "type": "integer", "minimum": 1 },
                                        "limit": { "type": "integer", "minimum": 0 }
                                    },
                                    "required": ["op", "path"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "list" },
                                        "path": { "type": "string" },
                                        "depth": { "type": "integer", "minimum": 0 },
                                        "limit": { "type": "integer", "minimum": 0 }
                                    },
                                    "required": ["op", "path"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "search" },
                                        "path": { "type": "string" },
                                        "pattern": { "type": "string" },
                                        "limit": { "type": "integer", "minimum": 0 }
                                    },
                                    "required": ["op", "path", "pattern"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "create" },
                                        "path": { "type": "string" },
                                        "content": { "type": "string" }
                                    },
                                    "required": ["op", "path", "content"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "set" },
                                        "path": { "type": "string" },
                                        "content": { "type": "string" }
                                    },
                                    "required": ["op", "path", "content"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "replace" },
                                        "path": { "type": "string" },
                                        "old": { "type": "string" },
                                        "new": { "type": "string" },
                                        "replaceAll": { "type": "boolean" },
                                        "expectedReplacements": { "type": "integer", "minimum": 0 }
                                    },
                                    "required": ["op", "path", "old", "new"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "insert" },
                                        "path": { "type": "string" },
                                        "anchor": { "type": "string" },
                                        "position": {
                                            "type": "string",
                                            "enum": ["before", "after"]
                                        },
                                        "content": { "type": "string" },
                                        "occurrence": { "type": "integer", "minimum": 1 }
                                    },
                                    "required": ["op", "path", "anchor", "position", "content"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "move" },
                                        "from": { "type": "string" },
                                        "to": { "type": "string" }
                                    },
                                    "required": ["op", "from", "to"]
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "op": { "const": "delete" },
                                        "path": { "type": "string" }
                                    },
                                    "required": ["op", "path"]
                                }
                            ]
                        }
                    }
                },
                "required": ["operations"]
            }))
            .capabilities([
                ToolCapability::FilesystemRead,
                ToolCapability::FilesystemWrite,
            ])
            .side_effect_level(ToolSideEffectLevel::LocalState)
            .durability(ToolDurability::Ephemeral)
            .execution_category(ToolExecutionCategory::ExclusiveLocalMutation)
            .approval_category(ToolApprovalCategory::Filesystem)
            .build()
    }
}

#[async_trait]
impl ToolExecutor for FilesTool {
    fn execution_category(&self, input: &Value) -> ToolExecutionCategory {
        file_execution_category(input)
    }

    fn authorization_preview(
        &self,
        ctx: &ParallelToolContext,
        input: &Value,
    ) -> Result<ToolAuthorizationPreview, String> {
        build_files_authorization_preview(self.descriptor(), ctx, input)
    }

    async fn execute(&self, ctx: ParallelToolContext, input: Value) -> ToolResult {
        execute_files_tool(
            ctx.agent_id.clone(),
            ctx.tool_call_id.clone(),
            ctx.tool_name.clone(),
            ctx.runtime.clone(),
            ctx.resolve_working_directory(None)
                .unwrap_or_else(|_| ctx.working_directory().to_path_buf()),
            ctx.event_tx.clone(),
            input,
        )
        .await
    }

    async fn execute_mut(&self, ctx: ToolContext<'_>, input: Value) -> ToolResult {
        execute_files_tool(
            ctx.agent_id.clone(),
            ctx.tool_call_id.clone(),
            ctx.tool_name.clone(),
            ctx.runtime.clone(),
            ctx.resolve_working_directory(None)
                .unwrap_or_else(|_| ctx.working_directory().to_path_buf()),
            ctx.event_tx.clone(),
            input,
        )
        .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn files_tool_metadata_marks_local_mutation() {
        let spec = FilesTool.descriptor();
        assert!(!spec.capabilities.contains(&ToolCapability::ReadOnly));
        assert_eq!(spec.durability, ToolDurability::Ephemeral);
        assert!(spec.capabilities.contains(&ToolCapability::FilesystemRead));
        assert!(spec.capabilities.contains(&ToolCapability::FilesystemWrite));
        assert_eq!(
            spec.execution_category,
            ToolExecutionCategory::ExclusiveLocalMutation
        );
    }

    #[test]
    fn read_only_operations_opt_into_parallel_execution() {
        let category = FilesTool.execution_category(&json!({
            "operations": [
                { "op": "read", "path": "README.md" },
                { "op": "search", "path": ".", "pattern": "mentra" }
            ]
        }));

        assert_eq!(category, ToolExecutionCategory::ReadOnlyParallel);
    }

    #[test]
    fn mutating_operations_stay_exclusive() {
        let category = FilesTool.execution_category(&json!({
            "operations": [
                { "op": "read", "path": "README.md" },
                { "op": "set", "path": "README.md", "content": "updated" }
            ]
        }));

        assert_eq!(category, ToolExecutionCategory::ExclusiveLocalMutation);
    }
}