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}