use super::*;
#[derive(Resource)]
pub struct ToolResults(pub UnboundedReceiver<ToolOutcome>);
pub(crate) fn apply_tool_results(
window: &mut ContextWindow,
response_content: &str,
tool_calls: &[crate::components::ToolCall],
tool_results: &[(String, String)],
routing: Option<&leviath_core::blueprint::ToolResultRouting>,
sensitivities: Option<&std::collections::HashMap<String, leviath_core::TaintLevel>>,
) {
let response_tokens = leviath_core::estimate_tokens(response_content);
let serialized: Vec<leviath_core::SerializedToolCall> = tool_calls
.iter()
.map(|tc| leviath_core::SerializedToolCall {
id: tc.tool_id.clone(),
name: tc.name.clone(),
arguments: tc.arguments.clone(),
thought_signature: tc.thought_signature.clone(),
})
.collect();
let _ = window.add_typed_entry(
"conversation",
leviath_core::EntryKind::AssistantTurn {
tool_calls: serialized,
},
response_content.to_string(),
response_tokens,
);
for (tool_call_id, result) in tool_results {
let mut result_text = result.clone();
let tool_name = tool_calls
.iter()
.find(|tc| tc.tool_id == *tool_call_id)
.map(|tc| tc.name.clone())
.unwrap_or_default();
if let Some(routing) = routing
&& let Some(max_tokens) = routing.max_result_tokens
{
let max_chars = max_tokens * 4;
if result_text.len() > max_chars {
result_text = truncate_on_char_boundary(&result_text, max_chars);
result_text.push_str("\n[...truncated]");
}
}
let result_tokens = leviath_core::estimate_tokens(&result_text);
let base_region = match routing {
Some(r) => {
let canon = leviath_tools::canonical_tool_name(&tool_name);
r.tool_overrides
.iter()
.find(|(k, _)| leviath_tools::canonical_tool_name(k) == canon)
.map(|(_, v)| v.as_str())
.unwrap_or(r.default_region.as_str())
}
None => "conversation",
};
let target_region = match routing {
Some(r) if !r.persist && window.get_region("scratch").is_some() => "scratch",
_ => base_region,
};
let taint_level = sensitivities.map(|s| {
s.get(&tool_name)
.copied()
.unwrap_or(leviath_core::TaintLevel::Public)
});
let add_kind = |window: &mut ContextWindow,
region: &str,
kind: leviath_core::EntryKind,
content: String,
tokens: usize| {
let put = |w: &mut ContextWindow, c: String, t: usize| match taint_level {
Some(level) => w.add_typed_tainted_to_region(region, kind.clone(), c, t, level),
None => w.add_typed_entry(region, kind.clone(), c, t),
};
if put(window, content.clone(), tokens).is_err() {
let available = window
.get_region(region)
.map(|r| r.max_tokens.saturating_sub(r.current_tokens))
.unwrap_or(0);
let truncated = if available > 100 {
let char_budget = (available - 10) * 4;
let prefix = truncate_on_char_boundary(&content, char_budget);
let omitted = content.len().saturating_sub(prefix.len());
format!("{}... [truncated, {} chars omitted]", prefix, omitted)
} else {
"[tool result truncated - context window full]".to_string()
};
let trunc_tokens = leviath_core::estimate_tokens(&truncated);
if put(window, truncated, trunc_tokens).is_err() {
let _ = put(window, "[result omitted]".to_string(), 5);
}
}
};
let result_kind = || leviath_core::EntryKind::ToolResult {
tool_call_id: tool_call_id.clone(),
tool_name: tool_name.clone(),
is_error: false,
};
if target_region == "conversation" {
add_kind(
window,
"conversation",
result_kind(),
result_text,
result_tokens,
);
} else {
let preview: String = result_text.chars().take(160).collect();
let ellipsis = if result_text.len() > preview.len() {
"…"
} else {
""
};
let pointer = format!(
"[output stored in context region '{target_region}' ({result_tokens} tokens) - read that region for the full result. Preview: {preview}{ellipsis}]"
);
let pointer_tokens = leviath_core::estimate_tokens(&pointer);
add_kind(
window,
"conversation",
result_kind(),
pointer,
pointer_tokens,
);
add_kind(
window,
target_region,
leviath_core::EntryKind::Text,
result_text,
result_tokens,
);
}
}
}
pub(crate) fn truncate_file(content: String, max_tokens: Option<usize>) -> String {
match max_tokens {
Some(max) => {
let approx_chars = max * 4;
if content.len() > approx_chars {
let head: String = content.chars().take(approx_chars).collect();
format!("{head}\n\n[... truncated at {max} tokens ...]")
} else {
content
}
}
None => content,
}
}
pub(crate) fn apply_file_tracking(
window: &mut ContextWindow,
ft: &leviath_core::blueprint::FileTrackingConfig,
tool_calls: &[crate::components::ToolCall],
merged: &mut [(String, String)],
) {
let is_hashmap = window
.get_region(&ft.region)
.is_some_and(|r| matches!(r.kind, leviath_core::RegionKind::HashMap { .. }));
if !is_hashmap {
return;
}
for (call, (_id, result)) in tool_calls.iter().zip(merged.iter_mut()) {
if call_had_no_effect(result) {
continue;
}
let Some(path) = call.arguments.get("path").and_then(|v| v.as_str()) else {
continue;
};
let (body, verb) = match call.name.as_str() {
"read_file" if ft.track_reads => (result.clone(), "stored"),
"write_file" if ft.track_writes => {
match call.arguments.get("content").and_then(|v| v.as_str()) {
Some(c) => (c.to_string(), "written"),
None => continue,
}
}
_ => continue,
};
let body = truncate_file(body, ft.max_file_tokens);
let tokens = leviath_core::estimate_tokens(&body);
window
.get_region_mut(&ft.region)
.expect("region presence checked above")
.upsert_by_key(path, body, tokens)
.ok();
*result = format!(
"File {verb} in [{}] → ### [{}] ({} tokens). Reference it there; do not re-read this path.",
ft.region, path, tokens
);
}
}
pub(crate) fn stage_modifying_tools(
blueprint: Option<&AgentBlueprint>,
cursor: Option<&StageCursor>,
) -> Vec<String> {
let mut names: Vec<String> = leviath_core::blueprint::MODIFYING_TOOLS
.iter()
.map(|t| (*t).to_string())
.collect();
let (Some(bp), Some(cursor)) = (blueprint, cursor) else {
return names;
};
let Some(stage) = bp.0.stages.get(cursor.index) else {
return names;
};
let Some(transitions) = &stage.transitions else {
return names;
};
for edge in transitions.values() {
let Some(gate) = &edge.gate else { continue };
for tool in &gate.tools {
let canonical = leviath_tools::canonical_tool_name(tool).to_string();
if !names.contains(&canonical) {
names.push(canonical);
}
}
}
names
}
pub(crate) fn record_modifications(
tool_calls: &[crate::components::ToolCall],
merged: &[(String, String)],
modifying: &[String],
progress: Option<bevy_ecs::prelude::Mut<'_, StageProgress>>,
flags: Option<bevy_ecs::prelude::Mut<'_, crate::persistence::RunOutcomeFlags>>,
) {
let mut progress = progress;
let mut flags = flags;
for (call, (_id, result)) in tool_calls.iter().zip(merged.iter()) {
let canonical = leviath_tools::canonical_tool_name(&call.name);
if !modifying.iter().any(|t| t == canonical) {
continue;
}
if result.starts_with("[denied]") {
if let Some(progress) = progress.as_mut() {
progress.blocked_modification_calls += 1;
}
continue;
}
if call_had_no_effect(result) {
continue;
}
if let Some(progress) = progress.as_mut() {
progress.modifying_tool_calls += 1;
}
if let Some(flags) = flags.as_mut() {
let path = call
.arguments
.get("path")
.and_then(|v| v.as_str())
.unwrap_or("<unknown>");
flags.0.record_modification(path);
}
}
}
#[allow(clippy::type_complexity)]
pub fn collect_tools(
mut results: ResMut<ToolResults>,
mut agents: Query<
(
&mut ContextWindow,
&crate::components::InferenceResult,
Option<&crate::components::ToolResultRoutingComponent>,
Option<&ToolSensitivities>,
Option<&ContextToolResults>,
Option<&StageCursor>,
Option<&mut StageIoBuffer>,
Option<&AgentBlueprint>,
Option<&mut crate::repetition::RepetitionDetector>,
Option<&mut StageProgress>,
Option<&mut crate::persistence::RunOutcomeFlags>,
Option<&mut crate::telemetry::StageActivity>,
),
With<AwaitingTools>,
>,
mut commands: Commands,
) {
crate::tick_scope::clear();
while let Ok(outcome) = results.0.try_recv() {
let Ok((
mut window,
infer,
routing,
sensitivities,
context_results,
cursor,
buffer,
blueprint,
repetition,
progress,
flags,
activity,
)) = agents.get_mut(outcome.entity)
else {
continue; };
crate::tick_scope::enter(outcome.entity);
let mut parts = outcome.results;
if let Some(ctx) = context_results {
parts.extend(ctx.0.iter().cloned());
}
let mut merged = merge_in_call_order(&infer.tool_calls, &parts);
record_modifications(
&infer.tool_calls,
&merged,
&stage_modifying_tools(blueprint, cursor),
progress,
flags,
);
if let Some(mut activity) = activity {
let batch_latency_ms = u64::try_from(outcome.elapsed.as_millis()).unwrap_or(u64::MAX);
for (call, (_id, result)) in infer.tool_calls.iter().zip(merged.iter()) {
activity.0.push(crate::telemetry::ActivityRecord::ToolCall {
tool_name: call.name.clone(),
batch_latency_ms,
success: !result.starts_with("[error]"),
});
}
}
if let Some(ft) = blueprint.and_then(|bp| bp.0.file_tracking.as_ref()) {
apply_file_tracking(&mut window, ft, &infer.tool_calls, &mut merged);
}
if let Some(mut buffer) = buffer {
let idx = cursor.map_or(0, |c| c.index);
for (call, (_id, result)) in infer.tool_calls.iter().zip(merged.iter()) {
buffer.logs.push((
idx,
format!("[tool] {}: {}", call.name, one_line(result, 200)),
));
}
}
apply_tool_results(
&mut window,
&infer.response,
&infer.tool_calls,
&merged,
routing.map(|c| &c.routing),
sensitivities.map(|s| &s.0),
);
if let Some(mut detector) = repetition {
let nudges: Vec<String> = infer
.tool_calls
.iter()
.filter_map(|call| detector.record_call(&call.name, &call.arguments.to_string()))
.collect();
for nudge in nudges {
let content = format!("[System] {nudge}");
let tokens = leviath_core::estimate_tokens(&content);
let _ = window.add_to_region("conversation", content, tokens);
}
}
commands
.entity(outcome.entity)
.remove::<AwaitingTools>()
.remove::<ContextToolResults>()
.remove::<InFlightWork>()
.insert(ReadyToInfer);
}
}