use super::error::Result;
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct ToolExecutionContext {
pub session_id: Uuid,
pub working_directory: std::path::PathBuf,
pub env_vars: HashMap<String, String>,
pub auto_approve: bool,
pub timeout_secs: u64,
pub sudo_callback: Option<crate::brain::agent::SudoCallback>,
pub ssh_callback: Option<crate::brain::agent::SshPasswordCallback>,
pub shared_working_directory: Option<Arc<std::sync::RwLock<std::path::PathBuf>>>,
pub service_context: Option<crate::services::ServiceContext>,
}
impl std::fmt::Debug for ToolExecutionContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ToolExecutionContext")
.field("session_id", &self.session_id)
.field("working_directory", &self.working_directory)
.field("auto_approve", &self.auto_approve)
.field("timeout_secs", &self.timeout_secs)
.field("sudo_callback", &self.sudo_callback.is_some())
.field("ssh_callback", &self.ssh_callback.is_some())
.finish()
}
}
impl ToolExecutionContext {
pub fn new(session_id: Uuid) -> Self {
Self {
session_id,
working_directory: std::env::current_dir().unwrap_or_default(),
env_vars: HashMap::new(),
auto_approve: false,
timeout_secs: 120,
sudo_callback: None,
ssh_callback: None,
shared_working_directory: None,
service_context: None,
}
}
pub fn with_working_directory(mut self, dir: std::path::PathBuf) -> Self {
self.working_directory = dir;
self
}
pub fn with_auto_approve(mut self, auto_approve: bool) -> Self {
self.auto_approve = auto_approve;
self
}
pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
self.timeout_secs = timeout_secs;
self
}
}
#[derive(Debug, Clone)]
pub struct ToolResult {
pub success: bool,
pub output: String,
pub error: Option<String>,
pub metadata: HashMap<String, String>,
pub images: Vec<(String, String)>,
}
impl ToolResult {
pub fn success(output: String) -> Self {
Self {
success: true,
output,
error: None,
metadata: HashMap::new(),
images: Vec::new(),
}
}
pub fn error(error: String) -> Self {
Self {
success: false,
output: String::new(),
error: Some(error),
metadata: HashMap::new(),
images: Vec::new(),
}
}
pub fn with_images(mut self, images: Vec<(String, String)>) -> Self {
self.images = images;
self
}
pub fn with_metadata(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolCapability {
ReadFiles,
WriteFiles,
ExecuteShell,
Network,
SystemModification,
PlanManagement,
}
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn input_schema(&self) -> Value;
fn capabilities(&self) -> Vec<ToolCapability>;
fn requires_approval(&self) -> bool {
let dangerous_capabilities = [
ToolCapability::WriteFiles,
ToolCapability::ExecuteShell,
ToolCapability::SystemModification,
];
self.capabilities()
.iter()
.any(|cap| dangerous_capabilities.contains(cap))
}
fn requires_approval_for_input(&self, _input: &Value) -> bool {
self.requires_approval()
}
async fn execute(&self, input: Value, context: &ToolExecutionContext) -> Result<ToolResult>;
fn validate_input(&self, _input: &Value) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_context() {
let session_id = Uuid::new_v4();
let ctx = ToolExecutionContext::new(session_id)
.with_auto_approve(true)
.with_timeout(60);
assert_eq!(ctx.session_id, session_id);
assert!(ctx.auto_approve);
assert_eq!(ctx.timeout_secs, 60);
}
#[test]
fn test_tool_result_success() {
let result = ToolResult::success("Done!".to_string())
.with_metadata("duration_ms".to_string(), "123".to_string());
assert!(result.success);
assert_eq!(result.output, "Done!");
assert!(result.error.is_none());
assert_eq!(result.metadata.get("duration_ms"), Some(&"123".to_string()));
}
#[test]
fn test_tool_result_error() {
let result = ToolResult::error("Something went wrong".to_string());
assert!(!result.success);
assert_eq!(result.error, Some("Something went wrong".to_string()));
}
}