codei-tools 0.0.10

终端优先的 AI 编程 Agent,用自然语言在本地仓库中读代码、改代码、跑命令、调试问题。
Documentation
use std::sync::Arc;

use async_trait::async_trait;
use codei_config::ResolvedConfig;
use serde_json::Value;

use crate::approval::{ApprovalHandler, ApprovalRequest};
use crate::ToolError;

#[derive(Debug, Clone)]
pub struct ToolResult {
    pub content: String,
    pub is_error: bool,
}

#[derive(Clone)]
pub struct ToolContext {
    pub cwd: std::path::PathBuf,
    pub config: Arc<ResolvedConfig>,
    pub approval: Arc<dyn ApprovalHandler>,
}

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> Value;
    fn requires_approval(&self) -> bool {
        false
    }

    async fn execute(&self, ctx: &ToolContext, args: Value) -> Result<ToolResult, ToolError>;
}

pub struct ToolRegistry {
    tools: Vec<Box<dyn Tool>>,
}

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

    pub fn register(&mut self, tool: Box<dyn Tool>) {
        self.tools.push(tool);
    }

    pub fn definitions(&self) -> Vec<codei_llm::ToolDefinition> {
        self.tools
            .iter()
            .map(|tool| codei_llm::ToolDefinition {
                name: tool.name().to_string(),
                description: tool.description().to_string(),
                parameters: tool.parameters_schema(),
            })
            .collect()
    }

    pub fn get(&self, name: &str) -> Option<&dyn Tool> {
        self.tools
            .iter()
            .find(|t| t.name() == name)
            .map(|t| t.as_ref())
    }

    pub async fn execute(
        &self,
        ctx: &ToolContext,
        name: &str,
        args: Value,
    ) -> Result<ToolResult, ToolError> {
        let tool = self.get(name).ok_or_else(|| ToolError::Failed {
            name: name.to_string(),
            message: "unknown tool".into(),
        })?;

        if tool.requires_approval() {
            let response = ctx
                .approval
                .approve(ApprovalRequest {
                    tool_name: name.to_string(),
                    arguments: args.clone(),
                })
                .await;
            if !response.approved {
                return Err(ToolError::Denied);
            }
        }

        tool.execute(ctx, args).await
    }
}

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