use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use crate::context::ToolCallContext;
use crate::error::ToolError;
use crate::metadata::ToolMetadata;
use crate::stream::{ToolStream, terminal_only};
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[allow(
clippy::derive_partial_eq_without_eq,
reason = "JSON Schema Value is not Eq"
)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: Value,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[allow(
clippy::derive_partial_eq_without_eq,
reason = "optional JSON Value is not Eq"
)]
pub struct ToolResult {
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub structured: Option<Value>,
#[serde(default)]
pub is_error: bool,
}
impl ToolResult {
#[must_use]
pub fn text(content: impl Into<String>) -> Self {
Self {
content: content.into(),
structured: None,
is_error: false,
}
}
#[must_use]
pub fn error(content: impl Into<String>) -> Self {
Self {
content: content.into(),
structured: None,
is_error: true,
}
}
}
#[async_trait]
pub trait DynTool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> Value;
fn metadata(&self) -> ToolMetadata {
ToolMetadata::default()
}
fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: self.name().to_owned(),
description: self.description().to_owned(),
parameters: self.parameters(),
}
}
async fn call(&self, ctx: ToolCallContext, arguments: Value) -> Result<ToolResult, ToolError>;
async fn execute(&self, ctx: ToolCallContext, arguments: Value) -> ToolStream {
let result = self.call(ctx, arguments).await;
terminal_only(result)
}
}
pub type SharedTool = Arc<dyn DynTool>;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;