use std::path::Path;
const XML_BREAK: &[&str] = &["tool_call", "tool_response", "function", "parameter"];
pub fn neutralize_xml(s: &str) -> String {
let mut out = s.to_string();
for t in XML_BREAK {
out = out
.replace(&format!("<{t}"), &format!("<\u{200b}{t}"))
.replace(&format!("</{t}"), &format!("</\u{200b}{t}"));
}
out
}
fn line_has_leak(line: &str) -> bool {
let hit = |needle: &str| line.contains(needle);
if hit("<tool_call") || hit("</tool_call") || hit("<tool_response") || hit("</tool_response")
{
return true;
}
if hit("<parameter") || hit("</parameter") {
return true;
}
for pat in ["<function", "</function"] {
if let Some(idx) = line.find(pat) {
match line[idx + pat.len()..].chars().next() {
None | Some('=') | Some('>') | Some('/') => return true,
Some(c) if c.is_whitespace() => return true,
_ => {}
}
}
}
false
}
pub fn has_leak(text: &str) -> bool {
text.lines().any(line_has_leak)
}
fn strip_leak_lines(text: &str) -> String {
text.lines()
.filter(|l| !line_has_leak(l))
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_string()
}
pub fn extract_final(content: &str) -> Option<String> {
if let Some(inner) = extract_final_answer_block(content) {
return Some(inner);
}
let c = strip_leak_lines(content);
if c.is_empty() {
None
} else {
Some(c)
}
}
fn extract_final_answer_block(text: &str) -> Option<String> {
let start_tag = "<final_answer>";
let end_tag = "</final_answer>";
let start = text.find(start_tag)? + start_tag.len();
let rest = &text[start..];
let end = rest.find(end_tag)?;
Some(rest[..end].trim().to_string())
}
fn location_path(line: &str) -> Option<&str> {
let line = line.trim();
if line.is_empty() {
return None;
}
if let Some(hash) = line.find('#') {
let before = &line[..hash];
let colon = before.find(':')?;
let path = before[colon + 1..].trim();
return (!path.is_empty()).then_some(path);
}
let colon = line.rfind(':')?;
let (path, num) = (line[..colon].trim(), &line[colon + 1..]);
if !num.is_empty() && num.bytes().all(|b| b.is_ascii_digit()) && !path.is_empty() {
return Some(path);
}
None
}
fn path_resolves(path: &str, root: &Path) -> bool {
if Path::new(path).is_file() {
return true;
}
let joined = if Path::new(path).is_absolute() {
root.join(path.trim_start_matches('/'))
} else {
root.join(path)
};
joined.is_file()
}
pub fn get_final_answer(text: &str, root: &Path) -> String {
let inner = match extract_final(text) {
Some(s) => s,
None => return String::new(),
};
let kept: Vec<&str> = inner
.lines()
.filter(|line| match location_path(line) {
Some(p) => path_resolves(p, root),
None => !line.trim().is_empty(), })
.collect();
kept.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn neutralize_breaks_tags_but_preserves_reading() {
let n = neutralize_xml("a <tool_call> and </parameter> b");
assert!(!n.contains("<tool_call>"));
assert!(n.contains('\u{200b}'));
assert_eq!(n.replace('\u{200b}', ""), "a <tool_call> and </parameter> b");
}
#[test]
fn leak_lines_are_detected_both_ways() {
assert!(line_has_leak("<tool_call>{...}"));
assert!(line_has_leak("#watch@143</parameter></function></tool_call>"));
assert!(line_has_leak("<function=foo>"));
assert!(!line_has_leak("go:hugolib/gitinfo.go#forPage@57"));
assert!(!line_has_leak("the function lives here")); }
#[test]
fn salvages_answer_lines_from_trailing_tag_junk() {
let dir = std::env::temp_dir().join(format!("grove-ground-salv-{}", std::process::id()));
fs::create_dir_all(dir.join("src")).unwrap();
fs::write(dir.join("src/sort.c"), "int main(){}\n").unwrap();
let text = "c:src/sort.c#sortCommand@1\n</parameter></function></tool_call>";
let out = get_final_answer(text, &dir);
assert_eq!(out, "c:src/sort.c#sortCommand@1");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn drops_location_lines_whose_path_is_hallucinated() {
let dir = std::env::temp_dir().join(format!("grove-ground-val-{}", std::process::id()));
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("real.rs"), "fn x(){}\n").unwrap();
let text = "rust:real.rs#x@1\nrust:ghost.rs#y@9\nreal.rs:1\nghost.rs:9";
let out = get_final_answer(text, &dir);
assert!(out.contains("rust:real.rs#x@1"), "kept resolved id: {out}");
assert!(out.contains("real.rs:1"), "kept resolved path:line: {out}");
assert!(!out.contains("ghost.rs"), "dropped hallucinated paths: {out}");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn optional_final_answer_wrapper_is_unwrapped() {
let dir = std::env::temp_dir().join(format!("grove-ground-wrap-{}", std::process::id()));
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("a.rs"), "fn a(){}\n").unwrap();
let text = "prose\n<final_answer>\nrust:a.rs#a@1\n</final_answer>";
let out = get_final_answer(text, &dir);
assert_eq!(out, "rust:a.rs#a@1");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn pure_leak_grounds_to_empty() {
assert_eq!(get_final_answer("<tool_call>{}", Path::new(".")), "");
}
}