use async_trait::async_trait;
use salvor_core::Effect;
use serde_json::Value;
use crate::context::ToolCtx;
use crate::error::ToolError;
use crate::handler::ToolHandler;
use crate::outcome::ToolOutcome;
#[derive(Clone, Debug, PartialEq)]
pub struct ToolDescriptor {
pub name: String,
pub description: String,
pub effect: Effect,
pub input_schema: Value,
}
#[async_trait]
pub trait DynTool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn effect(&self) -> Effect;
fn input_schema(&self) -> Value;
async fn call_json(&self, ctx: &ToolCtx, input: Value)
-> Result<ToolOutcome<Value>, ToolError>;
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor {
name: self.name().to_owned(),
description: self.description().to_owned(),
effect: self.effect(),
input_schema: self.input_schema(),
}
}
}
pub struct TypedTool<H>(pub H);
impl<H> TypedTool<H> {
pub fn new(handler: H) -> Self {
Self(handler)
}
}
#[async_trait]
impl<H: ToolHandler> DynTool for TypedTool<H> {
fn name(&self) -> &str {
H::NAME
}
fn description(&self) -> &str {
H::DESCRIPTION
}
fn effect(&self) -> Effect {
H::EFFECT
}
fn input_schema(&self) -> Value {
H::input_schema()
}
async fn call_json(
&self,
ctx: &ToolCtx,
input: Value,
) -> Result<ToolOutcome<Value>, ToolError> {
let typed: H::Input =
serde_json::from_value(input).map_err(|source| ToolError::InvalidInput {
tool: H::NAME.to_owned(),
source,
})?;
let outcome = self
.0
.call(ctx, typed)
.await
.map_err(|source| ToolError::Handler {
tool: H::NAME.to_owned(),
source,
})?;
match outcome {
ToolOutcome::Output(output) => {
let value = serde_json::to_value(output).map_err(|source| {
ToolError::OutputSerialization {
tool: H::NAME.to_owned(),
source,
}
})?;
Ok(ToolOutcome::Output(value))
}
ToolOutcome::Suspend(suspension) => Ok(ToolOutcome::Suspend(suspension)),
}
}
}