use crate::traits::composite::AgentFunctions;
use crate::types::{Scope, Task};
use anyhow::{Result, anyhow};
use futures::future::join_all;
use std::borrow::Cow;
use std::sync::Arc;
use tokio::{sync::Mutex, task};
use tracing::{debug, error};
use uuid::Uuid;
#[macro_export]
macro_rules! agents {
( $($agent:expr),* $(,)? ) => {
vec![
$(
::std::sync::Arc::new(
::tokio::sync::Mutex::new(
Box::new($agent) as Box<dyn $crate::traits::composite::AgentFunctions>
)
)
),*
]
};
}
pub type BoxedAgent = Arc<Mutex<Box<dyn AgentFunctions>>>;
pub struct AutoAgent {
pub id: Uuid,
pub agents: Vec<BoxedAgent>,
pub execute: bool,
pub browse: bool,
pub max_tries: u64,
pub crud: bool,
pub auth: bool,
pub external: bool,
}
impl Default for AutoAgent {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
agents: vec![],
execute: true,
browse: false,
max_tries: 1,
crud: true,
auth: false,
external: true,
}
}
}
impl AutoAgent {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id: Uuid) -> Self {
self.id = id;
self
}
pub fn with<A>(mut self, agents: A) -> Self
where
A: Into<Vec<BoxedAgent>>,
{
self.agents = agents.into();
self
}
pub fn execute(mut self, execute: bool) -> Self {
self.execute = execute;
self
}
pub fn browse(mut self, browse: bool) -> Self {
self.browse = browse;
self
}
pub fn max_tries(mut self, max_tries: u64) -> Self {
self.max_tries = max_tries;
self
}
pub fn crud(mut self, enabled: bool) -> Self {
self.crud = enabled;
self
}
pub fn auth(mut self, enabled: bool) -> Self {
self.auth = enabled;
self
}
pub fn external(mut self, enabled: bool) -> Self {
self.external = enabled;
self
}
pub fn build(self) -> Result<Self> {
if self.agents.is_empty() {
return Err(anyhow!(
"No agents registered. Call `.with(agents![...])` first."
));
}
Ok(self)
}
pub async fn run(&self) -> Result<String> {
if self.agents.is_empty() {
return Err(anyhow!("No agents to run."));
}
let execute = self.execute;
let browse = self.browse;
let max_tries = self.max_tries;
let crud = self.crud;
let auth = self.auth;
let external = self.external;
let mut handles = Vec::with_capacity(self.agents.len());
for (i, agent_arc) in self.agents.iter().cloned().enumerate() {
let agent_clone = Arc::clone(&agent_arc);
let agent_persona = agent_arc.lock().await.get_agent().persona.clone();
let task_payload = Arc::new(Mutex::new(Task {
description: Cow::Owned(agent_persona.clone()),
scope: Some(Scope {
crud,
auth,
external,
}),
urls: None,
frontend_code: None,
backend_code: None,
api_schema: None,
}));
let handle = task::spawn(async move {
let mut locked_task = task_payload.lock().await;
let mut agent = agent_clone.lock().await;
match agent
.execute(&mut locked_task, execute, browse, max_tries)
.await
{
Ok(_) => {
debug!("Agent {i} ({agent_persona}) completed successfully.");
Ok::<(), anyhow::Error>(())
}
Err(err) => {
error!("Agent {i} ({agent_persona}) failed: {err}");
Err(anyhow!("Agent {i} failed: {err}"))
}
}
});
handles.push(handle);
}
let results = join_all(handles).await;
let failures: Vec<String> = results
.into_iter()
.enumerate()
.filter_map(|(i, res)| match res {
Ok(Err(e)) => Some(format!("Agent {i}: {e}")),
Err(join_err) => Some(format!("Agent {i} panicked: {join_err}")),
_ => None,
})
.collect();
if !failures.is_empty() {
return Err(anyhow!("Some agents failed:\n{}", failures.join("\n")));
}
Ok("All agents executed successfully.".to_string())
}
}