agent_base/engine/
middleware.rs1use std::sync::Arc;
2
3use async_trait::async_trait;
4use serde_json::Value;
5
6use crate::types::{AgentResult, ChatMessage, FinishReason, SessionId, UserEvent};
7
8#[derive(Clone)]
9pub struct UserMessageCtx {
10 pub session_id: SessionId,
11 pub user_input: String,
12}
13
14pub struct PreLlmCtx {
15 pub session_id: SessionId,
16 pub messages: Vec<ChatMessage>,
17 pub tools: Vec<Value>,
18 pub emit_fn: Option<Box<dyn Fn(UserEvent) + Send + Sync>>,
25}
26
27impl PreLlmCtx {
28 pub fn emit(&self, event: UserEvent) {
37 if let Some(ref f) = self.emit_fn
38 && let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
39 f(event);
40 }))
41 {
42 tracing::warn!("emit failed: {:?}", e);
43 }
44 }
45}
46
47#[derive(Clone)]
48pub struct PostLlmCtx {
49 pub session_id: SessionId,
50 pub full_text: String,
51 pub is_tool_call: bool,
52 pub tool_calls: Vec<(String, String, String)>,
53 pub available_tools: Vec<String>,
54 pub turn_count: u32,
55 pub total_tool_calls: usize,
56 pub nudge_count: usize,
59 pub turn_tool_calls: usize,
62 pub skip_push: bool,
63 pub follow_up_message: Option<String>,
64 pub finish_reason: FinishReason,
68}
69
70#[async_trait]
71pub trait Middleware: Send + Sync {
72 async fn on_user_message(&self, _ctx: &mut UserMessageCtx) -> AgentResult<()> {
73 Ok(())
74 }
75
76 async fn on_pre_llm(&self, _ctx: &mut PreLlmCtx) -> AgentResult<()> {
77 Ok(())
78 }
79
80 async fn on_post_llm(&self, _ctx: &mut PostLlmCtx) -> AgentResult<()> {
81 Ok(())
82 }
83}
84
85pub(crate) type MiddlewareRef = Arc<dyn Middleware>;
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::types::SessionId;
91
92 #[test]
93 fn test_post_llm_ctx_new_fields_defaults() {
94 let ctx = PostLlmCtx {
95 session_id: SessionId {
96 id: 1,
97 external_id: None,
98 },
99 full_text: "test".to_string(),
100 is_tool_call: false,
101 tool_calls: vec![],
102 available_tools: vec![],
103 turn_count: 0,
104 total_tool_calls: 0,
105 nudge_count: 0,
106 turn_tool_calls: 0,
107 skip_push: false,
108 follow_up_message: None,
109 finish_reason: FinishReason::Stop,
110 };
111 assert!(ctx.available_tools.is_empty());
112 assert_eq!(ctx.turn_count, 0);
113 assert_eq!(ctx.total_tool_calls, 0);
114 assert!(!ctx.skip_push);
115 assert!(ctx.follow_up_message.is_none());
116 assert_eq!(ctx.finish_reason, FinishReason::Stop);
117 }
118
119 #[test]
120 fn test_post_llm_ctx_skip_push_follow_up_set() {
121 let ctx = PostLlmCtx {
122 session_id: SessionId {
123 id: 2,
124 external_id: None,
125 },
126 full_text: "I will execute...".to_string(),
127 is_tool_call: false,
128 tool_calls: vec![],
129 available_tools: vec!["echo".to_string()],
130 turn_count: 1,
131 total_tool_calls: 0,
132 nudge_count: 0,
133 turn_tool_calls: 0,
134 skip_push: true,
135 follow_up_message: Some("Please call tools now.".to_string()),
136 finish_reason: FinishReason::Stop,
137 };
138 assert!(ctx.skip_push);
139 assert_eq!(
140 ctx.follow_up_message,
141 Some("Please call tools now.".to_string())
142 );
143 assert_eq!(ctx.available_tools, vec!["echo".to_string()]);
144 assert_eq!(ctx.total_tool_calls, 0);
145 }
146
147 #[test]
148 fn test_post_llm_ctx_clone_preserves_new_fields() {
149 let ctx = PostLlmCtx {
150 session_id: SessionId {
151 id: 3,
152 external_id: None,
153 },
154 full_text: "hello".to_string(),
155 is_tool_call: false,
156 tool_calls: vec![],
157 available_tools: vec!["add".to_string(), "subtract".to_string()],
158 turn_count: 5,
159 total_tool_calls: 3,
160 nudge_count: 0,
161 turn_tool_calls: 2,
162 skip_push: true,
163 follow_up_message: Some("nudge".to_string()),
164 finish_reason: FinishReason::Truncated {
165 reason: Some("max_tokens".into()),
166 },
167 };
168 let cloned = ctx.clone();
169 assert_eq!(cloned.available_tools, vec!["add", "subtract"]);
170 assert_eq!(cloned.turn_count, 5);
171 assert_eq!(cloned.total_tool_calls, 3);
172 assert_eq!(cloned.turn_tool_calls, 2);
173 assert!(cloned.skip_push);
174 assert_eq!(cloned.follow_up_message, Some("nudge".to_string()));
175 assert!(cloned.finish_reason.is_truncated());
176 }
177}