gigi-cli 1.0.0

Gigi — A Claude Code-like AI coding assistant CLI in Rust
use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;

use crate::query::ToolDefinition;

// =============================================================================
// Tool Trait — Every tool the AI can invoke must implement this.
// =============================================================================

/// Output from a tool execution.
#[derive(Debug, Clone)]
pub struct ToolOutput {
    /// The text content returned by the tool.
    pub content: String,
    /// Whether the tool execution resulted in an error.
    pub is_error: bool,
}

impl ToolOutput {
    pub fn success(content: impl Into<String>) -> Self {
        Self {
            content: content.into(),
            is_error: false,
        }
    }

    pub fn error(content: impl Into<String>) -> Self {
        Self {
            content: content.into(),
            is_error: true,
        }
    }
}

/// The core trait for all tools.
///
/// Each tool provides its name, description, JSON Schema for parameters,
/// and an async `execute` method that performs the actual work.
#[async_trait]
pub trait Tool: Send + Sync {
    /// The unique name of this tool (used in API requests and dispatching).
    fn name(&self) -> &str;

    /// Human-readable description of what this tool does.
    fn description(&self) -> &str;

    /// JSON Schema describing the input parameters for this tool.
    fn parameters_schema(&self) -> Value;

    /// Execute the tool with the given input and return the result.
    async fn execute(&self, input: Value) -> Result<ToolOutput>;
}

// =============================================================================
// ToolRegistry — Manages all registered tools
// =============================================================================

/// Registry that holds all available tools and dispatches executions.
pub struct ToolRegistry {
    tools: HashMap<String, Box<dyn Tool>>,
}

impl ToolRegistry {
    pub fn new() -> Self {
        Self {
            tools: HashMap::new(),
        }
    }

    /// Register a tool. If a tool with the same name exists, it is replaced.
    pub fn register(&mut self, tool: Box<dyn Tool>) {
        let name = tool.name().to_string();
        self.tools.insert(name, tool);
    }

    /// Get a reference to a tool by name.
    pub fn get(&self, name: &str) -> Option<&dyn Tool> {
        self.tools.get(name).map(|t| t.as_ref())
    }

    /// Execute a tool by name with the given input.
    pub async fn execute(&self, name: &str, input: Value) -> Result<ToolOutput> {
        match self.tools.get(name) {
            Some(tool) => tool.execute(input).await,
            None => Ok(ToolOutput::error(format!("Unknown tool: {}", name))),
        }
    }

    /// Generate tool definitions for the API request.
    pub fn definitions(&self) -> Vec<ToolDefinition> {
        self.tools
            .values()
            .map(|tool| ToolDefinition {
                name: tool.name().to_string(),
                description: tool.description().to_string(),
                input_schema: tool.parameters_schema(),
            })
            .collect()
    }

    /// List all registered tool names.
    pub fn tool_names(&self) -> Vec<&str> {
        self.tools.keys().map(|k| k.as_str()).collect()
    }
}

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

// Sub-modules for concrete tool implementations
pub mod bash;
pub mod task_manager;
pub mod file_ops;
pub mod glob_search;
pub mod grep_search;
pub mod read_file;
pub mod write_file;
pub mod edit_file;
pub mod tech_query;
pub mod web_search;
pub mod web_fetch;