Skip to main content

exeora_cli/tools/
mod.rs

1mod files;
2mod path;
3mod processes;
4
5pub use processes::ProcessRegistry;
6
7use crate::{
8    error::{ErrorCode, ExeoraError},
9    protocol::ToolName,
10};
11use anyhow::{Result, anyhow};
12use jsonschema::Validator;
13use serde_json::Value;
14use std::{collections::HashMap, path::Path, sync::Arc};
15use tokio_util::sync::CancellationToken;
16
17pub struct ToolEngine {
18    validators: HashMap<ToolName, Validator>,
19    processes: Arc<ProcessRegistry>,
20}
21
22impl ToolEngine {
23    pub fn new() -> Result<Self> {
24        let contract: Value = serde_json::from_str(include_str!("../../protocol/contract.json"))?;
25        let tools = contract
26            .pointer("/schemas/tools")
27            .and_then(Value::as_object)
28            .ok_or_else(|| anyhow!("generated tool schemas are missing"))?;
29        let mut validators = HashMap::new();
30        for tool in ToolName::ALL {
31            let schema = tools
32                .get(tool.as_str())
33                .and_then(|value| value.get("input"))
34                .ok_or_else(|| anyhow!("schema missing for {tool}"))?;
35            validators.insert(tool, jsonschema::validator_for(schema)?);
36        }
37        Ok(Self {
38            validators,
39            processes: Arc::new(ProcessRegistry::new()),
40        })
41    }
42
43    pub async fn execute(
44        &self,
45        root: &Path,
46        tool: ToolName,
47        arguments: Value,
48        cancel: CancellationToken,
49    ) -> Result<Value, ExeoraError> {
50        let validator = self
51            .validators
52            .get(&tool)
53            .ok_or_else(|| ExeoraError::new(ErrorCode::UnknownTool, "Unsupported tool."))?;
54        if let Err(error) = validator.validate(&arguments) {
55            return Err(ExeoraError::new(
56                ErrorCode::InvalidArguments,
57                error.to_string(),
58            ));
59        }
60        if cancel.is_cancelled() {
61            return Err(ExeoraError::new(
62                ErrorCode::Cancelled,
63                "The call was cancelled before it started.",
64            ));
65        }
66
67        match tool {
68            ToolName::ReadFile => files::read_file(root, arguments).await,
69            ToolName::ListFiles => files::list_files(root, arguments).await,
70            ToolName::Grep => files::grep(root, arguments).await,
71            ToolName::EditFile => files::edit_file(root, arguments).await,
72            ToolName::WriteFile => files::write_file(root, arguments).await,
73            ToolName::RunCommand => self.processes.run_command(root, arguments, cancel).await,
74            ToolName::StartCommand => self.processes.start_command(root, arguments).await,
75            ToolName::GetCommandOutput => self.processes.get_output(root, arguments).await,
76            ToolName::SendCommandInput => self.processes.send_input(root, arguments).await,
77            ToolName::KillCommand => self.processes.kill_command(root, arguments).await,
78        }
79    }
80
81    pub async fn kill_all(&self) {
82        self.processes.kill_all().await;
83    }
84}