use crate::cook::execution::errors::MapReduceError;
use crate::cook::execution::mapreduce::effects::commands::{
execute_commands_effect, CommandResult,
};
use crate::cook::execution::mapreduce::effects::merge::{merge_to_parent_effect, MergeResult};
use crate::cook::execution::mapreduce::effects::worktree::{create_worktree_effect, Worktree};
use crate::cook::execution::mapreduce::environment::MapEnv;
use serde_json::Value;
use stillwater::{from_async, par_all_limit, BoxedEffect, Effect, EffectExt};
#[derive(Debug, Clone)]
pub struct AgentExecutionResult {
pub worktree: Worktree,
pub command_result: CommandResult,
pub merge_result: MergeResult,
}
pub fn execute_agent_effect(
item: &Value,
agent_name: &str,
parent_branch: &str,
) -> BoxedEffect<AgentExecutionResult, MapReduceError, MapEnv> {
let item = item.clone();
let agent_name = agent_name.to_string();
let parent_branch = parent_branch.to_string();
create_worktree_effect(&agent_name, &parent_branch)
.and_then(move |worktree| {
let item = item.clone();
let parent_branch = parent_branch.clone();
let worktree_clone = worktree.clone();
execute_commands_effect(&item, &worktree).and_then(move |command_result| {
let worktree = worktree_clone;
let worktree_clone2 = worktree.clone();
merge_to_parent_effect(&worktree, &parent_branch).map(move |merge_result| {
AgentExecutionResult {
worktree: worktree_clone2,
command_result,
merge_result,
}
})
})
})
.boxed()
}
pub fn execute_agents_parallel(
items: &[Value],
parent_branch: &str,
max_parallel: usize,
) -> impl Effect<Output = Vec<AgentExecutionResult>, Error = Vec<MapReduceError>, Env = MapEnv> {
let parent_branch = parent_branch.to_string();
let items: Vec<Value> = items.to_vec();
from_async(move |env: &MapEnv| {
let parent_branch = parent_branch.clone();
let items = items.clone();
let env = env.clone();
async move {
let agent_effects: Vec<BoxedEffect<AgentExecutionResult, MapReduceError, MapEnv>> =
items
.iter()
.enumerate()
.map(|(index, item)| {
let agent_name = format!("agent-{}", index);
execute_agent_effect(item, &agent_name, &parent_branch)
})
.collect();
par_all_limit(agent_effects, max_parallel, &env).await
}
})
}
pub fn plan_parallel_batches(item_count: usize, max_parallel: usize) -> Vec<Vec<usize>> {
let mut batches = Vec::new();
let mut current_batch = Vec::new();
for i in 0..item_count {
current_batch.push(i);
if current_batch.len() >= max_parallel {
batches.push(current_batch.clone());
current_batch.clear();
}
}
if !current_batch.is_empty() {
batches.push(current_batch);
}
batches
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plan_parallel_batches() {
let batches = plan_parallel_batches(6, 2);
assert_eq!(batches.len(), 3);
assert_eq!(batches[0], vec![0, 1]);
assert_eq!(batches[1], vec![2, 3]);
assert_eq!(batches[2], vec![4, 5]);
let batches = plan_parallel_batches(5, 2);
assert_eq!(batches.len(), 3);
assert_eq!(batches[0], vec![0, 1]);
assert_eq!(batches[1], vec![2, 3]);
assert_eq!(batches[2], vec![4]);
let batches = plan_parallel_batches(3, 10);
assert_eq!(batches.len(), 1);
assert_eq!(batches[0], vec![0, 1, 2]);
}
#[test]
fn test_plan_parallel_batches_edge_cases() {
let batches = plan_parallel_batches(0, 2);
assert_eq!(batches.len(), 0);
let batches = plan_parallel_batches(1, 2);
assert_eq!(batches.len(), 1);
assert_eq!(batches[0], vec![0]);
let batches = plan_parallel_batches(5, 1);
assert_eq!(batches.len(), 5);
for (i, batch) in batches.iter().enumerate() {
assert_eq!(batch, &vec![i]);
}
}
}