use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::tools::{BoxFuture, ToolDefinition};
use crate::types::{AgentResponse, ChatOptions, ChatResponse, Message};
pub type Terminal<C> = Box<dyn FnOnce(C) -> BoxFuture<Result<C>> + Send>;
#[async_trait]
pub trait Middleware<C: Send + 'static>: Send + Sync {
async fn process(&self, ctx: C, next: Next<C>) -> Result<C>;
}
pub struct Next<C: Send + 'static> {
middlewares: Arc<Vec<Arc<dyn Middleware<C>>>>,
index: usize,
terminal: Option<Terminal<C>>,
}
impl<C: Send + 'static> Next<C> {
pub async fn run(mut self, ctx: C) -> Result<C> {
if self.index < self.middlewares.len() {
let mw = self.middlewares[self.index].clone();
let next = Next {
middlewares: self.middlewares.clone(),
index: self.index + 1,
terminal: self.terminal.take(),
};
mw.process(ctx, next).await
} else if let Some(term) = self.terminal.take() {
term(ctx).await
} else {
Ok(ctx)
}
}
}
pub struct MiddlewarePipeline<C: Send + 'static> {
middlewares: Arc<Vec<Arc<dyn Middleware<C>>>>,
}
impl<C: Send + 'static> Default for MiddlewarePipeline<C> {
fn default() -> Self {
Self {
middlewares: Arc::new(Vec::new()),
}
}
}
impl<C: Send + 'static> Clone for MiddlewarePipeline<C> {
fn clone(&self) -> Self {
Self {
middlewares: self.middlewares.clone(),
}
}
}
impl<C: Send + 'static> MiddlewarePipeline<C> {
pub fn new(middlewares: Vec<Arc<dyn Middleware<C>>>) -> Self {
Self {
middlewares: Arc::new(middlewares),
}
}
pub fn is_empty(&self) -> bool {
self.middlewares.is_empty()
}
pub async fn execute(&self, ctx: C, terminal: Terminal<C>) -> Result<C> {
let next = Next {
middlewares: self.middlewares.clone(),
index: 0,
terminal: Some(terminal),
};
next.run(ctx).await
}
}
pub struct AgentContext {
pub messages: Vec<Message>,
pub is_streaming: bool,
pub metadata: HashMap<String, serde_json::Value>,
pub result: Option<AgentResponse>,
pub terminate: bool,
}
impl AgentContext {
pub fn new(messages: Vec<Message>, is_streaming: bool) -> Self {
Self {
messages,
is_streaming,
metadata: HashMap::new(),
result: None,
terminate: false,
}
}
}
pub struct ChatContext {
pub messages: Vec<Message>,
pub chat_options: ChatOptions,
pub is_streaming: bool,
pub metadata: HashMap<String, serde_json::Value>,
pub result: Option<ChatResponse>,
pub terminate: bool,
}
impl ChatContext {
pub fn new(messages: Vec<Message>, chat_options: ChatOptions, is_streaming: bool) -> Self {
Self {
messages,
chat_options,
is_streaming,
metadata: HashMap::new(),
result: None,
terminate: false,
}
}
}
#[derive(Clone, Default)]
pub struct LiveToolList {
inner: Arc<std::sync::Mutex<Vec<ToolDefinition>>>,
}
impl LiveToolList {
pub fn new(tools: Vec<ToolDefinition>) -> Self {
Self {
inner: Arc::new(std::sync::Mutex::new(tools)),
}
}
pub fn add_tools(&self, tools: impl IntoIterator<Item = ToolDefinition>) -> Result<()> {
let batch: Vec<ToolDefinition> = tools.into_iter().collect();
let mut list = self.inner.lock().unwrap();
for tool in &batch {
if list.iter().any(|t| t.name == tool.name)
|| batch.iter().filter(|t| t.name == tool.name).count() > 1
{
return Err(Error::Configuration(format!(
"cannot add tool '{}': a tool with that name already exists in this run",
tool.name
)));
}
}
list.extend(batch);
Ok(())
}
pub fn remove_tools<'a>(&self, names: impl IntoIterator<Item = &'a str>) {
let to_remove: std::collections::HashSet<&str> = names.into_iter().collect();
self.inner
.lock()
.unwrap()
.retain(|t| !to_remove.contains(t.name.as_str()));
}
pub fn contains(&self, name: &str) -> bool {
self.inner.lock().unwrap().iter().any(|t| t.name == name)
}
pub fn snapshot(&self) -> Vec<ToolDefinition> {
self.inner.lock().unwrap().clone()
}
}
pub struct FunctionInvocationContext {
pub function_name: String,
pub arguments: serde_json::Value,
pub session: Option<crate::session::AgentSession>,
pub tools: Option<LiveToolList>,
pub metadata: HashMap<String, serde_json::Value>,
pub result: Option<serde_json::Value>,
pub terminate: bool,
}
impl FunctionInvocationContext {
pub fn new(function_name: impl Into<String>, arguments: serde_json::Value) -> Self {
Self {
function_name: function_name.into(),
arguments,
session: None,
tools: None,
metadata: HashMap::new(),
result: None,
terminate: false,
}
}
pub fn with_session(mut self, session: Option<crate::session::AgentSession>) -> Self {
self.session = session;
self
}
pub fn with_tools(mut self, tools: Option<LiveToolList>) -> Self {
self.tools = tools;
self
}
pub fn add_tools(&self, tools: impl IntoIterator<Item = ToolDefinition>) -> Result<()> {
self.tools
.as_ref()
.ok_or_else(|| {
Error::Configuration(
"cannot add tools: this FunctionInvocationContext is not bound to a \
live agent run"
.into(),
)
})?
.add_tools(tools)
}
pub fn remove_tools<'a>(&self, names: impl IntoIterator<Item = &'a str>) -> Result<()> {
self.tools
.as_ref()
.ok_or_else(|| {
Error::Configuration(
"cannot remove tools: this FunctionInvocationContext is not bound to a \
live agent run"
.into(),
)
})
.map(|t| t.remove_tools(names))
}
}
pub type AgentMiddleware = dyn Middleware<AgentContext>;
pub type ChatMiddleware = dyn Middleware<ChatContext>;
pub type FunctionMiddleware = dyn Middleware<FunctionInvocationContext>;
pub struct FnMiddleware<C, F> {
f: F,
_marker: std::marker::PhantomData<fn(C)>,
}
impl<C, F> FnMiddleware<C, F> {
pub fn new(f: F) -> Self {
Self {
f,
_marker: std::marker::PhantomData,
}
}
}
#[async_trait]
impl<C, F, Fut> Middleware<C> for FnMiddleware<C, F>
where
C: Send + 'static,
F: Fn(C, Next<C>) -> Fut + Send + Sync,
Fut: std::future::Future<Output = Result<C>> + Send,
{
async fn process(&self, ctx: C, next: Next<C>) -> Result<C> {
(self.f)(ctx, next).await
}
}