use std::{collections::HashMap, sync::Arc};
use async_trait::async_trait;
use crate::value::Value;
#[derive(Clone)]
pub struct ToolDefinition {
pub handler: Arc<dyn ToolHandler>,
pub parallelizable: bool,
}
impl ToolDefinition {
pub fn new(handler: impl ToolHandler + 'static) -> Self {
Self { handler: Arc::new(handler), parallelizable: false }
}
pub fn parallel(handler: impl ToolHandler + 'static) -> Self {
Self { handler: Arc::new(handler), parallelizable: true }
}
pub fn from_fn<F, Fut>(f: F) -> Self
where
F: Fn(HashMap<String, Value>) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<Value, ToolError>> + Send + 'static,
{
Self { handler: Arc::new(FnTool(f)), parallelizable: false }
}
pub fn from_fn_parallel<F, Fut>(f: F) -> Self
where
F: Fn(HashMap<String, Value>) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<Value, ToolError>> + Send + 'static,
{
Self { handler: Arc::new(FnTool(f)), parallelizable: true }
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("{message}")]
pub struct ToolError {
pub message: String,
}
impl ToolError {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into() }
}
pub fn from_err(err: impl std::fmt::Display) -> Self {
Self { message: err.to_string() }
}
}
#[async_trait]
pub trait ToolHandler: Send + Sync {
async fn call(&self, kwargs: HashMap<String, Value>) -> Result<Value, ToolError>;
}
#[derive(Clone)]
pub struct Tools(HashMap<String, ToolDefinition>);
impl Tools {
#[must_use]
pub fn new() -> Self {
Self(HashMap::new())
}
#[must_use]
pub fn with(mut self, name: &str, config: ToolDefinition) -> Self {
self.insert(name, config);
self
}
pub fn insert(&mut self, name: &str, config: ToolDefinition) {
assert!(
crate::security::validator::is_name_allowed(name),
"tool name '{name}' is a dangerous builtin and cannot be registered"
);
self.0.insert(name.to_string(), config);
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&ToolDefinition> {
self.0.get(name)
}
#[must_use]
pub fn contains_key(&self, name: &str) -> bool {
self.0.contains_key(name)
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.0.keys()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn merged_with(&self, other: &Self) -> Self {
let mut merged = self.0.clone();
for (name, config) in &other.0 {
merged.insert(name.clone(), config.clone());
}
Self(merged)
}
}
impl Default for Tools {
fn default() -> Self {
Self::new()
}
}
pub trait KwargsExt {
fn require_str(&self, key: &str) -> Result<&str, ToolError>;
fn require_int(&self, key: &str) -> Result<i64, ToolError>;
fn require_float(&self, key: &str) -> Result<f64, ToolError>;
fn require_bool(&self, key: &str) -> Result<bool, ToolError>;
fn get_str(&self, key: &str) -> Option<&str>;
fn get_int(&self, key: &str) -> Option<i64>;
fn get_float(&self, key: &str) -> Option<f64>;
fn get_bool(&self, key: &str) -> Option<bool>;
fn get_or(&self, key: &str, default: Value) -> Value;
}
impl<S: std::hash::BuildHasher> KwargsExt for HashMap<String, Value, S> {
fn require_str(&self, key: &str) -> Result<&str, ToolError> {
self.get(key)
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::new(format!("missing required string argument '{key}'")))
}
fn require_int(&self, key: &str) -> Result<i64, ToolError> {
self.get(key)
.and_then(super::value::Value::as_int)
.ok_or_else(|| ToolError::new(format!("missing required integer argument '{key}'")))
}
fn require_float(&self, key: &str) -> Result<f64, ToolError> {
self.get(key)
.and_then(super::value::Value::as_float)
.ok_or_else(|| ToolError::new(format!("missing required float argument '{key}'")))
}
fn require_bool(&self, key: &str) -> Result<bool, ToolError> {
self.get(key)
.and_then(super::value::Value::as_bool)
.ok_or_else(|| ToolError::new(format!("missing required bool argument '{key}'")))
}
fn get_str(&self, key: &str) -> Option<&str> {
self.get(key).and_then(|v| v.as_str())
}
fn get_int(&self, key: &str) -> Option<i64> {
self.get(key).and_then(super::value::Value::as_int)
}
fn get_float(&self, key: &str) -> Option<f64> {
self.get(key).and_then(super::value::Value::as_float)
}
fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key).and_then(super::value::Value::as_bool)
}
fn get_or(&self, key: &str, default: Value) -> Value {
self.get(key).cloned().unwrap_or(default)
}
}
struct FnTool<F>(F);
#[async_trait]
impl<F, Fut> ToolHandler for FnTool<F>
where
F: Fn(HashMap<String, Value>) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<Value, ToolError>> + Send + 'static,
{
async fn call(&self, kwargs: HashMap<String, Value>) -> Result<Value, ToolError> {
(self.0)(kwargs).await
}
}
pub(crate) mod lazy_proxy;
pub(crate) mod resolver;