use super::*;
use rig_core::streaming::{Item, StreamEvent};
pub(super) fn validation_len(stream: &BusStreamed) -> usize {
stream
.errors
.first()
.map_or(stream.events.len(), |(position, _)| {
(*position).min(stream.events.len())
})
}
pub fn discover_streamed_invalid_calls(
mut commands: Commands,
effects: Query<(&ChildOf, &BusStreamed), NotRetrieval>,
mut turns: Query<(&ChildOf, &mut Outputs), Unread>,
runs: Query<(&OutputToolName, &RunPhase), LiveRun>,
children: Query<&Children>,
adverts: Query<&Advert>,
bound: Query<&Bound>,
access: Query<&ToolAccess>,
invalid: Query<(&ChildOf, &InvalidCall, Option<&Resolution>)>,
) {
for (parent, stream) in &effects {
let turn = parent.parent();
let Ok((run_of, mut outputs)) = turns.get_mut(turn) else {
continue;
};
let Ok((minted, &RunPhase::AwaitingModel)) = runs.get(run_of.parent()) else {
continue;
};
let pending: Vec<_> = invalid
.iter()
.filter(|(parent, _, _)| parent.parent() == turn)
.collect();
if pending.iter().any(|(_, _, resolution)| {
!matches!(
resolution,
Some(Resolution::Repair { .. } | Resolution::Ignore)
)
}) {
continue;
}
let mut allowed: Vec<String> = links_in_order(turn, &children, &adverts)
.into_iter()
.filter_map(|Advert(entity)| bound.get(*entity).ok())
.filter_map(|bound| crate::policy::tool_name(&bound.descriptor).map(str::to_owned))
.collect();
if let Some(names) = access
.get(turn)
.ok()
.and_then(|access| access.allowed.as_ref())
{
allowed = names.iter().cloned().collect();
}
allowed.extend(minted.0.iter().cloned());
let items = stream.events.items();
for (index, item) in items
.iter()
.enumerate()
.take(validation_len(stream))
.skip(outputs.stream_validated)
{
outputs.stream_validated = index + 1;
let Item::Event(StreamEvent::End {
content: AssistantContent::ToolCall(call),
..
}) = item
else {
continue;
};
let name = call.function.name.as_str();
if allowed.iter().any(|allowed| allowed == name) {
continue;
}
if pending.iter().any(|(_, pending, _)| pending.id == call.id) {
continue;
}
let mut prefix =
rig_core::streaming::delivered(items.get(..=index).unwrap_or_default());
for (_, pending, resolution) in &pending {
match resolution {
Some(Resolution::Repair { to }) => {
for part in &mut prefix {
if let AssistantContent::ToolCall(call) = part
&& call.id == pending.id
&& let Ok(to) = rig_core::message::ToolName::new(to.clone())
{
call.function.name = to;
}
}
}
Some(Resolution::Ignore) => prefix.retain(|part| {
!matches!(part, AssistantContent::ToolCall(call) if call.id == pending.id)
}),
_ => {}
}
}
commands.spawn((
InvalidCall {
id: call.id.clone(),
name: name.to_owned(),
arguments: call.function.arguments_value(),
prefix,
stream_offset: Some(index),
origin: stream.origin.clone(),
},
ChildOf(turn),
));
break;
}
}
}