use crate::bus::{MediaItem, MediaRef, MessageSource}; use async_trait::async_trait; #[derive(Debug, Clone)] pub struct ToolResult { pub success: bool, pub output: String, pub error: Option, } /// A tool result plus media artifacts that should be made available to a /// capable model on the next agent iteration. #[derive(Debug, Clone)] pub struct ToolResultWithMedia { pub result: ToolResult, pub media_refs: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OutboundDelivery { Delivered, AttachedToCurrentTurn, } impl From for ToolResultWithMedia { fn from(result: ToolResult) -> Self { Self { result, media_refs: Vec::new(), } } } #[async_trait] pub trait Tool: Send + Sync + 'static { fn name(&self) -> &str; fn description(&self) -> &str; fn parameters_schema(&self) -> serde_json::Value; async fn execute(&self, args: serde_json::Value) -> anyhow::Result; /// Execute the tool and return structured media artifacts when applicable. /// Most tools only return text and use this default implementation. async fn execute_with_media( &self, args: serde_json::Value, ) -> anyhow::Result { self.execute(args).await.map(Into::into) } /// Whether this tool is side-effect free and safe to parallelize. fn read_only(&self) -> bool { false } /// Whether this tool can run alongside other concurrency-safe tools. fn concurrency_safe(&self) -> bool { self.read_only() && !self.exclusive() } /// Whether this tool should run alone even if concurrency is enabled. fn exclusive(&self) -> bool { false } } #[async_trait] pub trait OutboundMessenger: Send + Sync { async fn send_message( &self, channel: &str, chat_id: &str, dialog_id: Option<&str>, content: &str, source: MessageSource, media: Vec, ) -> Result; }