use crate::brain::provider::{ContentBlock, Message};
pub fn is_repetitive_tool_error(msg: &str) -> bool {
crate::brain::provider::error::is_repetitive_tool_guardrail(msg)
}
fn tool_use_signature(m: &Message) -> Option<String> {
let mut sigs = Vec::new();
for b in &m.content {
match b {
ContentBlock::ToolUse { name, input, .. } => sigs.push(format!("{name}|{input}")),
ContentBlock::Text { text } if !text.trim().is_empty() => return None,
_ => {}
}
}
(!sigs.is_empty()).then(|| sigs.join("\n"))
}
fn is_tool_result(m: &Message) -> bool {
m.content
.iter()
.any(|b| matches!(b, ContentBlock::ToolResult { .. }))
}
pub fn prune_repetitive_tool_calls(messages: &[Message]) -> (Vec<Message>, usize) {
let mut out: Vec<Message> = Vec::with_capacity(messages.len());
let mut removed = 0usize;
let mut last_sig: Option<String> = None;
let mut i = 0;
while i < messages.len() {
let m = &messages[i];
if let Some(sig) = tool_use_signature(m) {
if last_sig.as_deref() == Some(sig.as_str()) {
removed += 1;
i += 1;
if i < messages.len() && is_tool_result(&messages[i]) {
removed += 1;
i += 1;
}
continue;
}
last_sig = Some(sig);
out.push(m.clone());
} else {
if !is_tool_result(m) {
last_sig = None;
}
out.push(m.clone());
}
i += 1;
}
(out, removed)
}