use serde_json::{Value, json};
use crate::config::constants::tools;
use crate::tools::tool_intent;
use super::{ToolRegistry, normalize_tool_output};
pub(super) struct ExecutionOutput {
pub(super) normalized_value: Value,
pub(super) structured_error: Option<String>,
}
impl ToolRegistry {
pub(super) async fn prepare_execution_output(
&self,
tool_name: &str,
args: &Value,
value: Value,
is_mcp: bool,
max_output_tokens: usize,
) -> ExecutionOutput {
if !is_mcp && crate::core::agent::completion::tracker_adoption_succeeded(tool_name, args, &value) {
self.harness_context
.tracker_adopted
.store(true, std::sync::atomic::Ordering::Relaxed);
}
if !is_mcp && tool_name == tools::TASK_TRACKER && self.tracker_adopted_for_request() {
self.observe_required_completion_verifiers(&value);
}
self.observe_completion_mutation(tool_name, args, &value);
let mut value = value;
if tool_intent::is_spool_file_read_command(tool_name, args) {
if let Some(output) = value.as_object_mut() {
output.insert("no_spool".to_string(), json!(true));
} else {
value = json!({"output": value, "no_spool": true});
}
}
let processed_value = self.process_tool_output(tool_name, value, is_mcp, max_output_tokens).await;
let mut normalized_value = normalize_tool_output(processed_value);
if tool_name == tools::CODE_SEARCH
&& let Some(output) = normalized_value.as_object_mut()
{
output.remove("success");
}
let structured_error = structured_tool_output_error(&normalized_value);
if structured_error.is_none() && !is_mcp {
self.observe_source_output(tool_name, args, &normalized_value).await;
}
ExecutionOutput { normalized_value, structured_error }
}
pub(super) fn invalidate_mutated_reads(&self, tool_name: &str, args: &Value) {
let targets = mutated_target_paths(tool_name, args);
if targets.is_empty() && is_pathless_mutating_command(tool_name) {
self.execution_history.invalidate_all_reads();
} else {
for target in targets {
self.execution_history.invalidate_for_path(&target);
}
}
}
}
fn mutated_target_paths(tool_name: &str, args: &Value) -> Vec<String> {
crate::tools::apply_patch::mutation_target_paths(tool_name, args)
.into_iter()
.map(|path| path.to_string_lossy().into_owned())
.collect()
}
fn is_pathless_mutating_command(tool_name: &str) -> bool {
matches!(tool_name, tools::UNIFIED_EXEC | tools::EXEC_COMMAND | tools::EXEC_PTY_CMD | tools::WRITE_STDIN)
}
fn structured_tool_output_error(value: &Value) -> Option<String> {
let obj = value.as_object()?;
if obj.get("success").and_then(Value::as_bool) == Some(false) {
return obj
.get("error")
.map(tool_error_value_to_string)
.or_else(|| Some("tool reported success=false".to_string()));
}
obj.get("error").map(tool_error_value_to_string)
}
fn tool_error_value_to_string(value: &Value) -> String {
if let Some(message) = value.as_str() {
return message.to_string();
}
if let Some(message) = value.get("message").and_then(Value::as_str) {
return message.to_string();
}
value.to_string()
}
#[cfg(test)]
mod tests;