use crate::abort::AbortSignal;
use crate::adapter::ToolDefinition;
use crate::types::JsonObject;
use serde_json::Value;
use std::collections::BTreeMap;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct ToolContext {
pub tool_call_id: String,
pub abort_signal: AbortSignal,
}
pub type ToolFuture = Pin<Box<dyn Future<Output = Result<Value, String>>>>;
type ExecuteFn = dyn Fn(JsonObject, ToolContext) -> ToolFuture;
type PromptFn = dyn Fn(&str, &JsonObject) -> String;
#[derive(Clone)]
pub struct Tool {
pub(crate) description: Option<String>,
pub(crate) parameters: Value,
pub(crate) requires_approval: bool,
pub(crate) execute: Option<Rc<ExecuteFn>>,
pub(crate) prompt: Option<Rc<PromptFn>>,
}
impl Tool {
pub fn new(parameters: Value) -> Self {
Self {
description: None,
parameters,
requires_approval: false,
execute: None,
prompt: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn requires_approval(mut self) -> Self {
self.requires_approval = true;
self
}
pub fn approval_prompt(
mut self,
prompt: impl Fn(&str, &JsonObject) -> String + 'static,
) -> Self {
self.prompt = Some(Rc::new(prompt));
self
}
pub fn execute<F, Fut>(mut self, execute: F) -> Self
where
F: Fn(JsonObject, ToolContext) -> Fut + 'static,
Fut: Future<Output = Result<Value, String>> + 'static,
{
self.execute = Some(Rc::new(move |args, context| {
Box::pin(execute(args, context))
}));
self
}
fn definition(&self) -> ToolDefinition {
ToolDefinition {
description: self.description.clone(),
parameters: self.parameters.clone(),
}
}
}
#[derive(Clone, Default)]
pub struct ToolRegistry {
tools: BTreeMap<String, Tool>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, name: impl Into<String>, tool: Tool) -> Self {
self.tools.insert(name.into(), tool);
self
}
pub fn get(&self, name: &str) -> Option<&Tool> {
self.tools.get(name)
}
pub fn definitions(&self) -> BTreeMap<String, ToolDefinition> {
self.tools
.iter()
.map(|(n, t)| (n.clone(), t.definition()))
.collect()
}
}
pub(crate) fn approval_prompt(tool: &Tool, name: &str, args: &JsonObject) -> String {
match &tool.prompt {
Some(prompt) => prompt(name, args),
None => format!("Allow the assistant to use {name}?"),
}
}