use std::sync::Arc;
use async_trait::async_trait;
use pulsedb::{CollectiveId, SubstrateProvider};
use serde_json::Value;
use crate::error::Result;
use crate::event::EventEmitter;
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> Value;
async fn execute(&self, params: Value, context: &ToolContext) -> Result<ToolResult>;
fn requires_approval(&self) -> bool {
false
}
}
pub struct ToolContext {
pub agent_id: String,
pub collective_id: CollectiveId,
pub substrate: Arc<dyn SubstrateProvider>,
pub event_emitter: EventEmitter,
}
#[derive(Debug, Clone)]
pub enum ToolResult {
Text(String),
Json(Value),
Error(String),
}
impl ToolResult {
pub fn text(s: impl Into<String>) -> Self {
Self::Text(s.into())
}
pub fn json(v: Value) -> Self {
Self::Json(v)
}
pub fn error(s: impl Into<String>) -> Self {
Self::Error(s.into())
}
pub fn to_content(&self) -> String {
match self {
Self::Text(s) => s.clone(),
Self::Json(v) => v.to_string(),
Self::Error(s) => format!("Error: {s}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::PulseHiveError;
#[test]
fn test_tool_is_object_safe() {
fn _assert_object_safe(_: &dyn Tool) {}
fn _assert_boxable(_: Box<dyn Tool>) {}
fn _assert_arcable(_: Arc<dyn Tool>) {}
}
struct EchoTool;
#[async_trait]
impl Tool for EchoTool {
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"Echoes input back"
}
fn parameters(&self) -> Value {
serde_json::json!({"type": "object", "properties": {"text": {"type": "string"}}})
}
async fn execute(&self, params: Value, _ctx: &ToolContext) -> Result<ToolResult> {
let text = params["text"]
.as_str()
.ok_or_else(|| PulseHiveError::validation("text required"))?;
Ok(ToolResult::text(text))
}
}
#[test]
fn test_mock_tool_metadata() {
let tool = EchoTool;
assert_eq!(tool.name(), "echo");
assert_eq!(tool.description(), "Echoes input back");
assert!(!tool.requires_approval());
}
#[test]
fn test_tool_result_constructors() {
let text = ToolResult::text("hello");
assert!(matches!(text, ToolResult::Text(s) if s == "hello"));
let json = ToolResult::json(serde_json::json!({"key": "value"}));
assert!(matches!(json, ToolResult::Json(_)));
let err = ToolResult::error("not found");
assert!(matches!(err, ToolResult::Error(s) if s == "not found"));
}
#[test]
fn test_tool_result_to_content() {
assert_eq!(ToolResult::text("hello").to_content(), "hello");
assert_eq!(
ToolResult::json(serde_json::json!({"a": 1})).to_content(),
r#"{"a":1}"#
);
assert_eq!(ToolResult::error("oops").to_content(), "Error: oops");
}
}