rustant_core/multi/
routing.rs1use crate::channels::ChannelType;
7use uuid::Uuid;
8
9#[derive(Debug, Clone)]
11pub struct AgentRoute {
12 pub priority: u32,
14 pub target_agent_id: Uuid,
16 pub conditions: Vec<RouteCondition>,
18}
19
20#[derive(Debug, Clone)]
22pub enum RouteCondition {
23 ChannelType(ChannelType),
25 UserId(String),
27 MessageContains(String),
29 TaskPrefix(String),
31 CapabilityName(String),
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct RouteRequest {
38 pub channel_type: Option<ChannelType>,
39 pub user_id: Option<String>,
40 pub message_text: Option<String>,
41 pub task_name: Option<String>,
42 pub capability: Option<String>,
43}
44
45impl RouteRequest {
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn with_channel(mut self, ct: ChannelType) -> Self {
51 self.channel_type = Some(ct);
52 self
53 }
54
55 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
56 self.user_id = Some(user_id.into());
57 self
58 }
59
60 pub fn with_message(mut self, text: impl Into<String>) -> Self {
61 self.message_text = Some(text.into());
62 self
63 }
64
65 pub fn with_task(mut self, name: impl Into<String>) -> Self {
66 self.task_name = Some(name.into());
67 self
68 }
69
70 pub fn with_capability(mut self, cap: impl Into<String>) -> Self {
71 self.capability = Some(cap.into());
72 self
73 }
74}
75
76pub struct AgentRouter {
78 routes: Vec<AgentRoute>,
79 default_agent_id: Option<Uuid>,
81}
82
83impl AgentRouter {
84 pub fn new() -> Self {
85 Self {
86 routes: Vec::new(),
87 default_agent_id: None,
88 }
89 }
90
91 pub fn with_default(mut self, agent_id: Uuid) -> Self {
93 self.default_agent_id = Some(agent_id);
94 self
95 }
96
97 pub fn add_route(&mut self, route: AgentRoute) {
99 self.routes.push(route);
100 self.routes.sort_by_key(|r| r.priority);
101 }
102
103 pub fn route(&self, request: &RouteRequest) -> Option<Uuid> {
105 for route in &self.routes {
106 if self.matches_all(&route.conditions, request) {
107 return Some(route.target_agent_id);
108 }
109 }
110 self.default_agent_id
111 }
112
113 pub fn route_count(&self) -> usize {
115 self.routes.len()
116 }
117
118 fn matches_all(&self, conditions: &[RouteCondition], request: &RouteRequest) -> bool {
119 conditions.iter().all(|c| self.matches(c, request))
120 }
121
122 fn matches(&self, condition: &RouteCondition, request: &RouteRequest) -> bool {
123 match condition {
124 RouteCondition::ChannelType(ct) => request.channel_type.as_ref() == Some(ct),
125 RouteCondition::UserId(uid) => request.user_id.as_deref() == Some(uid.as_str()),
126 RouteCondition::MessageContains(sub) => request
127 .message_text
128 .as_ref()
129 .is_some_and(|t| t.contains(sub.as_str())),
130 RouteCondition::TaskPrefix(prefix) => request
131 .task_name
132 .as_ref()
133 .is_some_and(|t| t.starts_with(prefix.as_str())),
134 RouteCondition::CapabilityName(cap) => {
135 request.capability.as_deref() == Some(cap.as_str())
136 }
137 }
138 }
139}
140
141impl Default for AgentRouter {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_router_no_routes_returns_default() {
153 let default_id = Uuid::new_v4();
154 let router = AgentRouter::new().with_default(default_id);
155 let req = RouteRequest::new().with_message("hello");
156 assert_eq!(router.route(&req), Some(default_id));
157 }
158
159 #[test]
160 fn test_router_no_routes_no_default_returns_none() {
161 let router = AgentRouter::new();
162 let req = RouteRequest::new().with_message("hello");
163 assert_eq!(router.route(&req), None);
164 }
165
166 #[test]
167 fn test_router_matches_channel_type() {
168 let agent_id = Uuid::new_v4();
169 let mut router = AgentRouter::new();
170 router.add_route(AgentRoute {
171 priority: 1,
172 target_agent_id: agent_id,
173 conditions: vec![RouteCondition::ChannelType(ChannelType::Telegram)],
174 });
175
176 let req = RouteRequest::new().with_channel(ChannelType::Telegram);
177 assert_eq!(router.route(&req), Some(agent_id));
178
179 let req2 = RouteRequest::new().with_channel(ChannelType::Discord);
180 assert_eq!(router.route(&req2), None);
181 }
182
183 #[test]
184 fn test_router_priority_ordering() {
185 let low_prio_agent = Uuid::new_v4();
186 let high_prio_agent = Uuid::new_v4();
187 let mut router = AgentRouter::new();
188
189 router.add_route(AgentRoute {
191 priority: 10,
192 target_agent_id: low_prio_agent,
193 conditions: vec![RouteCondition::MessageContains("help".into())],
194 });
195 router.add_route(AgentRoute {
197 priority: 1,
198 target_agent_id: high_prio_agent,
199 conditions: vec![RouteCondition::MessageContains("help".into())],
200 });
201
202 let req = RouteRequest::new().with_message("I need help");
203 assert_eq!(router.route(&req), Some(high_prio_agent));
205 }
206
207 #[test]
208 fn test_router_multiple_conditions_all_must_match() {
209 let agent_id = Uuid::new_v4();
210 let mut router = AgentRouter::new();
211 router.add_route(AgentRoute {
212 priority: 1,
213 target_agent_id: agent_id,
214 conditions: vec![
215 RouteCondition::ChannelType(ChannelType::Discord),
216 RouteCondition::UserId("user-42".into()),
217 ],
218 });
219
220 let req = RouteRequest::new()
222 .with_channel(ChannelType::Discord)
223 .with_user("user-42");
224 assert_eq!(router.route(&req), Some(agent_id));
225
226 let req2 = RouteRequest::new()
228 .with_channel(ChannelType::Discord)
229 .with_user("user-99");
230 assert_eq!(router.route(&req2), None);
231 }
232}