agent-orchestrator-sdk 0.1.1

Rust SDK for orchestrating LLM-powered agents, shared task execution, and teammate coordination
Documentation
use std::path::PathBuf;

use async_trait::async_trait;
use serde_json::json;

use crate::error::SdkResult;
use crate::traits::tool::{Tool, ToolDefinition};

pub struct SearchFilesTool {
    pub source_root: PathBuf,
}

#[async_trait]
impl Tool for SearchFilesTool {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "search_files".to_string(),
            description: "Search for files by glob pattern and/or search for text content within files.".to_string(),
            parameters: json!({
                "type": "object",
                "properties": {
                    "file_pattern": { "type": "string", "description": "Glob pattern (e.g., '**/*.rs')" },
                    "content_pattern": { "type": "string", "description": "Text pattern to search within files" },
                    "max_results": { "type": "integer", "description": "Maximum results (default: 20)" }
                }
            }),
        }
    }

    async fn execute(&self, arguments: serde_json::Value) -> SdkResult<serde_json::Value> {
        let file_pattern = arguments["file_pattern"].as_str();
        let content_pattern = arguments["content_pattern"].as_str();
        let max_results = arguments["max_results"].as_u64().unwrap_or(20) as usize;

        if file_pattern.is_none() && content_pattern.is_none() {
            return Ok(json!({ "error": "At least one of 'file_pattern' or 'content_pattern' must be provided" }));
        }

        let mut matching_files: Vec<PathBuf> = Vec::new();

        if let Some(pattern) = file_pattern {
            let full_pattern = format!("{}/{}", self.source_root.display(), pattern);
            match glob::glob(&full_pattern) {
                Ok(paths) => {
                    for entry in paths.flatten() {
                        if entry.is_file() {
                            matching_files.push(entry);
                        }
                    }
                }
                Err(e) => return Ok(json!({ "error": format!("Invalid glob pattern: {}", e) })),
            }
        }

        if file_pattern.is_none() && content_pattern.is_some() {
            let full_pattern = format!("{}/**/*", self.source_root.display());
            if let Ok(paths) = glob::glob(&full_pattern) {
                for entry in paths.flatten() {
                    if entry.is_file() {
                        matching_files.push(entry);
                    }
                }
            }
        }

        if let Some(pattern) = content_pattern {
            let mut results = Vec::new();

            for file_path in &matching_files {
                if results.len() >= max_results {
                    break;
                }

                if let Ok(content) = tokio::fs::read_to_string(file_path).await {
                    let mut file_matches = Vec::new();
                    for (line_num, line) in content.lines().enumerate() {
                        if line.contains(pattern) {
                            file_matches.push(json!({ "line": line_num + 1, "text": line.trim() }));
                        }
                    }
                    if !file_matches.is_empty() {
                        let rel_path = file_path
                            .strip_prefix(&self.source_root)
                            .unwrap_or(file_path)
                            .to_string_lossy();
                        results.push(json!({ "file": rel_path, "matches": file_matches }));
                    }
                }
            }

            Ok(json!({
                "results": results,
                "total_files_searched": matching_files.len(),
                "files_with_matches": results.len()
            }))
        } else {
            let results: Vec<String> = matching_files
                .iter()
                .take(max_results)
                .map(|p| {
                    p.strip_prefix(&self.source_root)
                        .unwrap_or(p)
                        .to_string_lossy()
                        .to_string()
                })
                .collect();

            Ok(json!({ "files": results, "total_matches": matching_files.len(), "shown": results.len() }))
        }
    }
}