use std::fmt;
use std::future::Future;
use std::pin::Pin;
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct ToolOutput {
pub content: String,
pub is_error: bool,
}
impl ToolOutput {
pub fn success(content: impl Into<String>) -> Self {
Self {
content: content.into(),
is_error: false,
}
}
pub fn error(content: impl Into<String>) -> Self {
Self {
content: content.into(),
is_error: true,
}
}
}
#[derive(Debug)]
pub struct ToolError {
pub message: String,
}
impl fmt::Display for ToolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "tool execution failed: {}", self.message)
}
}
impl std::error::Error for ToolError {}
impl ToolError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> Value;
fn execute(
&self,
input: Value,
) -> Pin<Box<dyn Future<Output = Result<ToolOutput, ToolError>> + Send + '_>>;
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
struct FakeTool;
impl Tool for FakeTool {
fn name(&self) -> &str {
"fake"
}
fn description(&self) -> &str {
"A fake tool for testing"
}
fn parameters_schema(&self) -> Value {
json!({"type": "object", "properties": {}})
}
fn execute(
&self,
_input: Value,
) -> Pin<Box<dyn Future<Output = Result<ToolOutput, ToolError>> + Send + '_>> {
Box::pin(async { Ok(ToolOutput::success("done")) })
}
}
#[test]
fn tool_output_success() {
let output = ToolOutput::success("hello");
assert_eq!(output.content, "hello");
assert!(!output.is_error);
}
#[test]
fn tool_output_error() {
let output = ToolOutput::error("file not found");
assert_eq!(output.content, "file not found");
assert!(output.is_error);
}
#[test]
fn tool_error_display() {
let err = ToolError::new("timeout");
assert_eq!(err.to_string(), "tool execution failed: timeout");
}
#[test]
fn fake_tool_implements_trait() {
let tool = FakeTool;
assert_eq!(tool.name(), "fake");
assert_eq!(tool.description(), "A fake tool for testing");
assert_eq!(
tool.parameters_schema(),
json!({"type": "object", "properties": {}})
);
}
#[tokio::test]
async fn fake_tool_execute() {
let tool = FakeTool;
let result = tool
.execute(json!({}))
.await
.expect("tool execution should succeed");
assert_eq!(result.content, "done");
assert!(!result.is_error);
}
}