use crate::error::RuntimeResult;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type ToolFuture = Pin<Box<dyn Future<Output = RuntimeResult<Value>> + Send>>;
pub trait Tool: Send + Sync {
fn definition(&self) -> ToolDefinition;
fn execute(&self, call_id: &str, arguments: Value) -> ToolFuture;
fn requires_approval(&self) -> bool;
}
impl std::fmt::Debug for dyn Tool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let def = self.definition();
f.debug_struct("Tool")
.field("name", &def.name)
.field("requires_approval", &def.requires_approval)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub input_schema: Value,
pub requires_approval: bool,
}
impl ToolDefinition {
pub fn new<S1: Into<String>, S2: Into<String>>(
name: S1,
description: S2,
input_schema: Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
input_schema,
requires_approval: false,
}
}
pub fn with_approval(mut self, requires: bool) -> Self {
self.requires_approval = requires;
self
}
pub fn to_provider_definition(&self) -> iron_providers::ToolDefinition {
iron_providers::ToolDefinition::new(
self.name.clone(),
self.description.clone(),
self.input_schema.clone(),
)
}
}
#[derive(Default)]
pub struct ToolRegistry {
tools: HashMap<String, Arc<dyn Tool>>,
version: u64,
}
impl std::fmt::Debug for ToolRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ToolRegistry")
.field("tool_count", &self.tools.len())
.field("tool_names", &self.tools.keys().collect::<Vec<_>>())
.finish()
}
}
impl Clone for ToolRegistry {
fn clone(&self) -> Self {
Self {
tools: self.tools.clone(),
version: self.version,
}
}
}
impl ToolRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register<T: Tool + 'static>(&mut self, tool: T) {
let definition = tool.definition();
self.tools.insert(definition.name, Arc::new(tool));
self.version += 1;
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|t| t.as_ref())
}
pub fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
pub fn definitions(&self) -> Vec<ToolDefinition> {
self.tools.values().map(|t| t.definition()).collect()
}
pub fn provider_definitions(&self) -> Vec<iron_providers::ToolDefinition> {
self.definitions()
.into_iter()
.map(|d| d.to_provider_definition())
.collect()
}
pub fn len(&self) -> usize {
self.tools.len()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
pub fn clear(&mut self) {
self.tools.clear();
self.version += 1;
}
pub fn version(&self) -> u64 {
self.version
}
}
pub struct FunctionTool {
definition: ToolDefinition,
handler: Arc<dyn Fn(Value) -> RuntimeResult<Value> + Send + Sync>,
}
impl std::fmt::Debug for FunctionTool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionTool")
.field("definition", &self.definition)
.finish()
}
}
impl FunctionTool {
pub fn new<F>(definition: ToolDefinition, handler: F) -> Self
where
F: Fn(Value) -> RuntimeResult<Value> + Send + Sync + 'static,
{
Self {
definition,
handler: Arc::new(handler),
}
}
pub fn simple<S1, S2, F>(name: S1, description: S2, handler: F) -> Self
where
S1: Into<String>,
S2: Into<String>,
F: Fn(Value) -> RuntimeResult<Value> + Send + Sync + 'static,
{
let schema = serde_json::json!({
"type": "object",
"properties": {},
});
let definition = ToolDefinition::new(name, description, schema);
Self::new(definition, handler)
}
}
impl Tool for FunctionTool {
fn definition(&self) -> ToolDefinition {
self.definition.clone()
}
fn execute(&self, _call_id: &str, arguments: Value) -> ToolFuture {
let handler = self.handler.clone();
Box::pin(async move {
match tokio::task::spawn_blocking(move || handler(arguments)).await {
Ok(result) => result,
Err(e) => Err(crate::error::RuntimeError::tool_execution(e.to_string())),
}
})
}
fn requires_approval(&self) -> bool {
self.definition.requires_approval
}
}