1#![deny(missing_docs)]
22#![allow(clippy::redundant_closure)]
23
24pub mod conditional_router;
25pub mod execution;
26pub mod group_chat;
27pub mod router;
28pub mod team_context;
29
30#[cfg(test)]
31pub(crate) mod tests_common;
32
33use serde::{Deserialize, Serialize};
34use std::sync::Arc;
35use traitclaw_core::traits::provider::Provider;
36
37pub use conditional_router::ConditionalRouter;
38pub use execution::{run_verification_chain, TeamRunner};
39pub use team_context::TeamContext;
40
41pub fn pool_from_team(
67 team: &Team,
68 provider: impl Provider,
69) -> traitclaw_core::Result<traitclaw_core::pool::AgentPool> {
70 let missing: Vec<&str> = team
72 .roles()
73 .iter()
74 .filter(|r| r.system_prompt.is_none())
75 .map(|r| r.name.as_str())
76 .collect();
77
78 if !missing.is_empty() {
79 return Err(traitclaw_core::Error::Config(format!(
80 "Cannot create AgentPool from team '{}': roles missing system_prompt: [{}]",
81 team.name(),
82 missing.join(", ")
83 )));
84 }
85
86 let factory = traitclaw_core::factory::AgentFactory::new(provider);
87 let agents: Vec<traitclaw_core::Agent> = team
88 .roles()
89 .iter()
90 .map(|role| factory.spawn(role.system_prompt.as_ref().expect("checked above")))
91 .collect();
92
93 Ok(traitclaw_core::pool::AgentPool::new(agents))
94}
95
96pub fn pool_from_team_arc(
101 team: &Team,
102 provider: Arc<dyn Provider>,
103) -> traitclaw_core::Result<traitclaw_core::pool::AgentPool> {
104 let missing: Vec<&str> = team
105 .roles()
106 .iter()
107 .filter(|r| r.system_prompt.is_none())
108 .map(|r| r.name.as_str())
109 .collect();
110
111 if !missing.is_empty() {
112 return Err(traitclaw_core::Error::Config(format!(
113 "Cannot create AgentPool from team '{}': roles missing system_prompt: [{}]",
114 team.name(),
115 missing.join(", ")
116 )));
117 }
118
119 let factory = traitclaw_core::factory::AgentFactory::from_arc(provider);
120 let agents: Vec<traitclaw_core::Agent> = team
121 .roles()
122 .iter()
123 .map(|role| factory.spawn(role.system_prompt.as_ref().expect("checked above")))
124 .collect();
125
126 Ok(traitclaw_core::pool::AgentPool::new(agents))
127}
128
129pub struct Team {
131 name: String,
132 roles: Vec<AgentRole>,
133 router: Box<dyn router::Router>,
134}
135
136impl Team {
137 #[must_use]
139 pub fn new(name: impl Into<String>) -> Self {
140 Self {
141 name: name.into(),
142 roles: Vec::new(),
143 router: Box::new(router::SequentialRouter::new()),
144 }
145 }
146
147 #[must_use]
149 pub fn add_role(mut self, role: AgentRole) -> Self {
150 self.roles.push(role);
151 self
152 }
153
154 #[must_use]
156 pub fn name(&self) -> &str {
157 &self.name
158 }
159
160 #[must_use]
162 pub fn roles(&self) -> &[AgentRole] {
163 &self.roles
164 }
165
166 #[must_use]
168 pub fn find_role(&self, name: &str) -> Option<&AgentRole> {
169 self.roles.iter().find(|r| r.name == name)
170 }
171
172 #[must_use]
176 pub fn with_router(mut self, router: impl router::Router) -> Self {
177 self.router = Box::new(router);
178 self
179 }
180
181 #[must_use]
183 pub fn router(&self) -> &dyn router::Router {
184 &*self.router
185 }
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct AgentRole {
191 pub name: String,
193 pub description: String,
195 pub system_prompt: Option<String>,
197}
198
199impl AgentRole {
200 #[must_use]
202 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
203 Self {
204 name: name.into(),
205 description: description.into(),
206 system_prompt: None,
207 }
208 }
209
210 #[must_use]
212 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
213 self.system_prompt = Some(prompt.into());
214 self
215 }
216}
217
218pub struct VerificationChain {
222 pub max_retries: usize,
224}
225
226impl VerificationChain {
227 #[must_use]
229 pub fn new() -> Self {
230 Self { max_retries: 3 }
231 }
232
233 #[must_use]
235 pub fn with_max_retries(mut self, n: usize) -> Self {
236 self.max_retries = n;
237 self
238 }
239}
240
241impl Default for VerificationChain {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247#[derive(Debug, Clone)]
249pub enum VerifyResult {
250 Accepted(String),
252 Rejected(String),
254}
255
256impl VerifyResult {
257 #[must_use]
259 pub fn is_accepted(&self) -> bool {
260 matches!(self, Self::Accepted(_))
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_team_builder() {
270 let team = Team::new("test_team")
271 .add_role(AgentRole::new("role1", "First role"))
272 .add_role(AgentRole::new("role2", "Second role"));
273
274 assert_eq!(team.name(), "test_team");
275 assert_eq!(team.roles().len(), 2);
276 }
277
278 #[test]
279 fn test_find_role() {
280 let team = Team::new("team").add_role(AgentRole::new("researcher", "Research"));
281
282 assert!(team.find_role("researcher").is_some());
283 assert!(team.find_role("unknown").is_none());
284 }
285
286 #[test]
287 fn test_agent_role_with_prompt() {
288 let role =
289 AgentRole::new("writer", "Write docs").with_system_prompt("You are a technical writer");
290 assert_eq!(
291 role.system_prompt,
292 Some("You are a technical writer".into())
293 );
294 }
295
296 #[test]
297 fn test_verification_chain_default() {
298 let chain = VerificationChain::new();
299 assert_eq!(chain.max_retries, 3);
300 }
301
302 #[test]
303 fn test_verification_chain_custom_retries() {
304 let chain = VerificationChain::new().with_max_retries(5);
305 assert_eq!(chain.max_retries, 5);
306 }
307
308 #[test]
309 fn test_verify_result() {
310 assert!(VerifyResult::Accepted("ok".into()).is_accepted());
311 assert!(!VerifyResult::Rejected("bad".into()).is_accepted());
312 }
313}