1use crate::tools::{ToolCallRequest, ToolCallResponse, UnifiedToolError};
7use async_trait::async_trait;
8use std::ops::Add;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12pub type ToolRequest = ToolCallRequest;
14
15pub type ToolResponse = ToolCallResponse;
17
18pub type MiddlewareResult<T> = Result<T, UnifiedToolError>;
20
21#[async_trait]
23pub trait Middleware: Send + Sync {
24 async fn before_execute(&self, _req: &ToolRequest) -> MiddlewareResult<()> {
26 Ok(())
27 }
28
29 async fn after_execute(&self, _req: &ToolRequest, _res: &ToolResponse) -> MiddlewareResult<()> {
31 Ok(())
32 }
33
34 async fn on_error(&self, _req: &ToolRequest, _err: &UnifiedToolError) -> MiddlewareResult<()> {
36 Ok(())
37 }
38}
39
40pub struct NoopMiddleware;
42
43#[async_trait]
44impl Middleware for NoopMiddleware {}
45
46#[derive(Clone)]
48pub struct MiddlewareChain {
49 middlewares: Vec<Arc<dyn Middleware>>,
50}
51
52impl MiddlewareChain {
53 pub fn new() -> Self {
55 Self { middlewares: Vec::new() }
56 }
57
58 pub fn push(mut self, mw: Arc<dyn Middleware>) -> Self {
60 self.middlewares.push(mw);
61 self
62 }
63
64 pub async fn before_execute_opt(&self, req: &ToolRequest, fail_open: bool) -> MiddlewareResult<()> {
71 if !fail_open {
72 return self.before_execute(req).await;
73 }
74 let mut last_err = None;
75 for mw in &self.middlewares {
76 if let Err(err) = mw.before_execute(req).await {
77 tracing::warn!(
78 tool = %req.tool_name,
79 error = %err,
80 "middleware before_execute failed; ignoring (fail-open)"
81 );
82 last_err = Some(err);
83 }
84 }
85 if let Some(err) = last_err { Err(err) } else { Ok(()) }
86 }
87
88 pub async fn before_execute(&self, req: &ToolRequest) -> MiddlewareResult<()> {
90 for mw in &self.middlewares {
91 mw.before_execute(req).await?;
92 }
93 Ok(())
94 }
95
96 pub async fn after_execute(&self, req: &ToolRequest, res: &ToolResponse) -> MiddlewareResult<()> {
98 for mw in self.middlewares.iter().rev() {
100 mw.after_execute(req, res).await?;
101 }
102 Ok(())
103 }
104
105 pub async fn on_error(&self, req: &ToolRequest, err: &UnifiedToolError) -> MiddlewareResult<()> {
107 for mw in self.middlewares.iter().rev() {
108 if let Err(handler_err) = mw.on_error(req, err).await {
109 tracing::warn!(
110 error = %handler_err,
111 "Middleware error handler itself failed"
112 );
113 }
114 }
115 Ok(())
116 }
117}
118
119impl Add<Arc<dyn Middleware>> for MiddlewareChain {
120 type Output = Self;
121
122 fn add(self, rhs: Arc<dyn Middleware>) -> Self::Output {
123 self.push(rhs)
124 }
125}
126
127impl Default for MiddlewareChain {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133pub struct LoggingMiddleware {
135 name: String,
136}
137
138impl LoggingMiddleware {
139 pub fn new(name: impl Into<String>) -> Arc<Self> {
140 Arc::new(Self { name: name.into() })
141 }
142}
143
144#[async_trait]
145impl Middleware for LoggingMiddleware {
146 async fn before_execute(&self, req: &ToolRequest) -> MiddlewareResult<()> {
147 tracing::info!(
148 middleware = %self.name,
149 tool = %req.tool_name,
150 "Executing tool"
151 );
152 Ok(())
153 }
154
155 async fn after_execute(&self, req: &ToolRequest, res: &ToolResponse) -> MiddlewareResult<()> {
156 let duration_ms = res.duration_ms.unwrap_or(0);
157 let cache_hit = res.cache_hit.unwrap_or(false);
158 tracing::info!(
159 middleware = %self.name,
160 tool = %req.tool_name,
161 duration_ms,
162 cache_hit,
163 "Completed tool"
164 );
165 Ok(())
166 }
167
168 async fn on_error(&self, req: &ToolRequest, err: &UnifiedToolError) -> MiddlewareResult<()> {
169 tracing::error!(
170 middleware = %self.name,
171 tool = %req.tool_name,
172 error = %err,
173 "Tool execution failed"
174 );
175 Ok(())
176 }
177}
178
179#[derive(Clone, Copy, Debug)]
181pub struct MetricsSnapshot {
182 pub total_calls: u64,
183 pub successful_calls: u64,
184 pub failed_calls: u64,
185 pub total_duration_ms: u64,
186 pub cache_hits: u64,
187}
188
189pub struct MetricsMiddleware {
190 total_calls: Arc<AtomicU64>,
191 successful_calls: Arc<AtomicU64>,
192 failed_calls: Arc<AtomicU64>,
193 total_duration_ms: Arc<AtomicU64>,
194 cache_hits: Arc<AtomicU64>,
195}
196
197impl MetricsMiddleware {
198 fn new_inner() -> Self {
199 Self {
200 total_calls: Arc::new(AtomicU64::new(0)),
201 successful_calls: Arc::new(AtomicU64::new(0)),
202 failed_calls: Arc::new(AtomicU64::new(0)),
203 total_duration_ms: Arc::new(AtomicU64::new(0)),
204 cache_hits: Arc::new(AtomicU64::new(0)),
205 }
206 }
207
208 pub fn new() -> Arc<Self> {
209 Arc::new(Self::new_inner())
210 }
211
212 pub async fn snapshot(&self) -> MetricsSnapshot {
213 MetricsSnapshot {
214 total_calls: self.total_calls.load(Ordering::Relaxed),
215 successful_calls: self.successful_calls.load(Ordering::Relaxed),
216 failed_calls: self.failed_calls.load(Ordering::Relaxed),
217 total_duration_ms: self.total_duration_ms.load(Ordering::Relaxed),
218 cache_hits: self.cache_hits.load(Ordering::Relaxed),
219 }
220 }
221}
222
223#[async_trait]
224impl Middleware for MetricsMiddleware {
225 async fn before_execute(&self, _: &ToolRequest) -> MiddlewareResult<()> {
226 self.total_calls.fetch_add(1, Ordering::Relaxed);
227 Ok(())
228 }
229
230 async fn after_execute(&self, _: &ToolRequest, res: &ToolResponse) -> MiddlewareResult<()> {
231 self.successful_calls.fetch_add(1, Ordering::Relaxed);
232 self.total_duration_ms
233 .fetch_add(res.duration_ms.unwrap_or(0), Ordering::Relaxed);
234
235 if res.cache_hit.unwrap_or(false) {
236 self.cache_hits.fetch_add(1, Ordering::Relaxed);
237 }
238
239 Ok(())
240 }
241
242 async fn on_error(&self, _: &ToolRequest, _: &UnifiedToolError) -> MiddlewareResult<()> {
243 self.failed_calls.fetch_add(1, Ordering::Relaxed);
244 Ok(())
245 }
246}
247
248impl Default for MetricsMiddleware {
249 fn default() -> Self {
250 Self::new_inner()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use serde_json::Value;
258
259 #[tokio::test]
260 async fn test_chain_execution() {
261 let chain = MiddlewareChain::new()
262 .push(LoggingMiddleware::new("test"))
263 .push(MetricsMiddleware::new());
264
265 let req = ToolRequest {
266 id: "req-1".to_string(),
267 tool_name: "test_tool".into(),
268 args: Value::Null,
269 metadata: Some(Default::default()),
270 };
271
272 chain.before_execute(&req).await.unwrap();
273
274 let res = ToolResponse {
275 id: "req-1".to_string(),
276 success: true,
277 result: Some(Value::Null),
278 error: None,
279 duration_ms: Some(100),
280 cache_hit: Some(false),
281 };
282
283 chain.after_execute(&req, &res).await.unwrap();
284 }
285
286 #[tokio::test]
287 async fn test_metrics_tracking() {
288 let metrics = MetricsMiddleware::new();
289 let chain = MiddlewareChain::new().push(metrics.clone());
290
291 let req = ToolRequest {
292 id: "req-2".to_string(),
293 tool_name: "test".into(),
294 args: Value::Null,
295 metadata: Some(Default::default()),
296 };
297
298 for i in 0..5 {
299 chain.before_execute(&req).await.unwrap();
300 let res = ToolResponse {
301 id: format!("req-2-{i}"),
302 success: true,
303 result: Some(Value::Null),
304 error: None,
305 duration_ms: Some(10),
306 cache_hit: Some(true),
307 };
308 chain.after_execute(&req, &res).await.unwrap();
309 }
310
311 let snapshot = metrics.snapshot().await;
312 assert_eq!(snapshot.total_calls, 5);
313 assert_eq!(snapshot.successful_calls, 5);
314 assert_eq!(snapshot.cache_hits, 5);
315 }
316}