use super::super::Resolved;
use super::inner::{Ctx, Gated, Inner, finish, gate};
use super::{Entry, Invocation};
use crate::prompt::Error;
use crate::prompt::tool::ToolCall;
use std::path::Path;
pub(super) struct Fan<'a> {
pub(super) outer_id: &'a str,
pub(super) invocations: &'a [Invocation],
pub(super) step_dir_abs: &'a Path,
pub(super) conv_repo: &'a Path,
pub(super) conv_id: &'a str,
}
pub(super) fn run(
fan: &Fan<'_>,
resolved: &Resolved<'_>,
ctx: Ctx<'_>,
) -> Result<Option<Vec<Entry>>, Error> {
let mut gated = Vec::with_capacity(fan.invocations.len());
for (idx, inv) in fan.invocations.iter().enumerate() {
let inner = Inner {
outer_id: fan.outer_id,
k: idx + 1,
inv,
step_dir_abs: fan.step_dir_abs,
conv_repo: fan.conv_repo,
conv_id: fan.conv_id,
};
match gate(&inner, resolved, ctx)? {
Some(decision) => gated.push(decision),
None => return Ok(None),
}
}
let results = {
let calls: Vec<ToolCall<'_>> = gated
.iter()
.zip(fan.invocations)
.filter_map(|(decision, inv)| match decision {
Gated::Ready(id) => Some(ToolCall {
id,
name: &inv.name,
input: &inv.input,
}),
Gated::Declined(_) => None,
})
.collect();
ctx.executor.execute_all(
&calls,
fan.step_dir_abs,
ctx.stop,
resolved.workflow.tool_output,
)
};
let mut results = results.into_iter();
let mut entries = Vec::with_capacity(gated.len());
for (decision, inv) in gated.into_iter().zip(fan.invocations) {
let entry = match decision {
Gated::Declined(entry) => entry,
Gated::Ready(_) => {
let result = results
.next()
.expect("one result per tool call handed over");
match finish(&inv.name, result, ctx.stop)? {
Some(entry) => entry,
None => return Ok(None),
}
}
};
entries.push(entry);
}
Ok(Some(entries))
}