tiny_agent/middleware/
mod.rs1use crate::{
14 providers::LLMRequest,
15 shared::{ContentBlock, Message, ToolCall},
16 state::AgentState,
17};
18use async_trait::async_trait;
19use std::sync::Arc;
20
21pub struct MiddlewareCtx<'a> {
23 pub session_id: &'a str,
24 pub state: &'a AgentState,
25}
26
27pub enum ToolDecision {
29 Proceed,
31 ShortCircuit {
33 output: Vec<ContentBlock>,
34 is_error: bool,
35 },
36}
37
38#[async_trait]
42pub trait Middleware: Send + Sync {
43 fn name(&self) -> &str;
44
45 async fn before_model(
47 &self,
48 _ctx: &MiddlewareCtx<'_>,
49 _req: &mut LLMRequest,
50 ) -> Result<(), String> {
51 Ok(())
52 }
53
54 async fn after_model(
56 &self,
57 _ctx: &MiddlewareCtx<'_>,
58 _msg: &mut Message,
59 ) -> Result<(), String> {
60 Ok(())
61 }
62
63 async fn before_tool(
65 &self,
66 _ctx: &MiddlewareCtx<'_>,
67 _call: &ToolCall,
68 ) -> Result<ToolDecision, String> {
69 Ok(ToolDecision::Proceed)
70 }
71
72 async fn after_tool(
74 &self,
75 _ctx: &MiddlewareCtx<'_>,
76 _call: &ToolCall,
77 _result: &mut ContentBlock,
78 ) -> Result<(), String> {
79 Ok(())
80 }
81}
82
83#[derive(Clone, Default)]
85pub struct MiddlewareChain {
86 middlewares: Vec<Arc<dyn Middleware>>,
87}
88
89impl MiddlewareChain {
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn push(&mut self, middleware: Arc<dyn Middleware>) {
95 self.middlewares.push(middleware);
96 }
97
98 pub fn with(mut self, middleware: Arc<dyn Middleware>) -> Self {
99 self.middlewares.push(middleware);
100 self
101 }
102
103 pub fn is_empty(&self) -> bool {
104 self.middlewares.is_empty()
105 }
106
107 pub async fn before_model(
109 &self,
110 ctx: &MiddlewareCtx<'_>,
111 req: &mut LLMRequest,
112 ) -> Result<(), String> {
113 for m in &self.middlewares {
114 m.before_model(ctx, req)
115 .await
116 .map_err(|e| format!("[{}] {e}", m.name()))?;
117 }
118 Ok(())
119 }
120
121 pub async fn after_model(
123 &self,
124 ctx: &MiddlewareCtx<'_>,
125 msg: &mut Message,
126 ) -> Result<(), String> {
127 for m in self.middlewares.iter().rev() {
128 m.after_model(ctx, msg)
129 .await
130 .map_err(|e| format!("[{}] {e}", m.name()))?;
131 }
132 Ok(())
133 }
134
135 pub async fn before_tool(
138 &self,
139 ctx: &MiddlewareCtx<'_>,
140 call: &ToolCall,
141 ) -> Result<ToolDecision, String> {
142 for m in &self.middlewares {
143 match m
144 .before_tool(ctx, call)
145 .await
146 .map_err(|e| format!("[{}] {e}", m.name()))?
147 {
148 ToolDecision::Proceed => {}
149 short_circuit => return Ok(short_circuit),
150 }
151 }
152 Ok(ToolDecision::Proceed)
153 }
154
155 pub async fn after_tool(
157 &self,
158 ctx: &MiddlewareCtx<'_>,
159 call: &ToolCall,
160 result: &mut ContentBlock,
161 ) -> Result<(), String> {
162 for m in self.middlewares.iter().rev() {
163 m.after_tool(ctx, call, result)
164 .await
165 .map_err(|e| format!("[{}] {e}", m.name()))?;
166 }
167 Ok(())
168 }
169}