use serde_json::Value;
pub fn clean_json_string(
input: &str,
unwrap_hallucinated_tool_calls: bool,
expected_tool_name: Option<&str>,
) -> String {
let mut start_index = None;
let mut current_pos = 0;
while let Some(pos) = input[current_pos..].find('{') {
let absolute_pos = current_pos + pos;
let remainder = &input[absolute_pos + 1..];
if let Some(first_char) = remainder.chars().find(|c| !c.is_whitespace())
&& (first_char == '"' || first_char == '}')
{
start_index = Some(absolute_pos);
break;
}
current_pos = absolute_pos + 1;
}
let start = start_index.or_else(|| input.find('{'));
let end = input.rfind('}');
let candidate = match (start, end) {
(Some(start), Some(end)) if start <= end => &input[start..=end],
_ => input,
};
if unwrap_hallucinated_tool_calls
&& let Ok(val) = serde_json::from_str::<Value>(candidate)
&& let Some(args) = val.get("arguments")
&& args.is_object()
{
let name_ok = match expected_tool_name {
None => true,
Some(expected) => match val.get("name") {
None => true,
Some(Value::String(actual)) => actual == expected,
Some(_) => false,
},
};
if name_ok {
return args.to_string();
}
}
candidate.to_string()
}
pub fn split_args_respecting_brackets(args_str: &str) -> Vec<String> {
let mut parts = Vec::new();
let mut current = String::new();
let mut depth = 0;
let mut in_quote = false;
let mut quote_char = '\0';
let mut escape = false;
for c in args_str.chars() {
if escape {
current.push(c);
escape = false;
continue;
}
if c == '\\' {
current.push(c);
escape = true;
continue;
}
if in_quote {
current.push(c);
if c == quote_char {
in_quote = false;
}
} else {
match c {
'"' | '\'' => {
in_quote = true;
quote_char = c;
current.push(c);
}
'[' | '{' | '(' => {
depth += 1;
current.push(c);
}
']' | '}' | ')' => {
if depth > 0 {
depth -= 1;
}
current.push(c);
}
',' => {
if depth == 0 {
parts.push(current.trim().to_string());
current.clear();
} else {
current.push(c);
}
}
_ => current.push(c),
}
}
}
if !current.trim().is_empty() {
parts.push(current.trim().to_string());
}
parts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clean_json_string_basic() {
let input = "Here is JSON: {\"key\": \"value\"} end";
assert_eq!(
clean_json_string(input, false, None),
"{\"key\": \"value\"}"
);
}
#[test]
fn test_clean_json_string_nested() {
let input = "Start { \"a\": { \"b\": 1 } } End";
assert_eq!(
clean_json_string(input, false, None),
"{ \"a\": { \"b\": 1 } }"
);
}
#[test]
fn test_clean_json_string_truncated() {
let input = "{ \"key\": \"value\"";
assert_eq!(
clean_json_string(input, false, None),
"{ \"key\": \"value\""
);
let input = "Prefix { \"key\": \"val... (cut)";
assert_eq!(
clean_json_string(input, false, None),
"Prefix { \"key\": \"val... (cut)"
);
}
#[test]
fn test_unwrap_hallucinated_tool_call() {
let input = r#"{"name": "submit_proposal", "arguments": {"thought_process": "Thinking...", "solution_content": "42"}}"#;
let cleaned = clean_json_string(input, true, None);
let cleaned_json: serde_json::Value = serde_json::from_str(&cleaned).unwrap();
let expected_json: serde_json::Value =
serde_json::from_str(r#"{"thought_process": "Thinking...", "solution_content": "42"}"#)
.unwrap();
assert_eq!(cleaned_json, expected_json);
}
#[test]
fn test_unwrap_skips_when_tool_name_mismatches_expected() {
let input = r#"{"name": "search_deliberation", "arguments": {"filters": {"agent_ids": ["CortexA"], "phase": "proposing"}}}"#;
let cleaned = clean_json_string(input, true, Some("submit_proposal"));
let parsed: serde_json::Value = serde_json::from_str(&cleaned).unwrap();
assert_eq!(parsed["name"], "search_deliberation");
assert!(parsed.get("arguments").is_some());
}
#[test]
fn test_unwrap_happens_when_tool_name_matches_expected() {
let input = r#"{"name": "submit_proposal", "arguments": {"thought_process": "T", "solution_content": "S"}}"#;
let cleaned = clean_json_string(input, true, Some("submit_proposal"));
let parsed: serde_json::Value = serde_json::from_str(&cleaned).unwrap();
assert_eq!(parsed["thought_process"], "T");
assert_eq!(parsed["solution_content"], "S");
assert!(parsed.get("name").is_none());
assert!(parsed.get("arguments").is_none());
}
#[test]
fn test_unwrap_happens_when_envelope_has_no_name_field() {
let input = r#"{"arguments": {"thought_process": "T", "solution_content": "S"}}"#;
let cleaned = clean_json_string(input, true, Some("submit_proposal"));
let parsed: serde_json::Value = serde_json::from_str(&cleaned).unwrap();
assert_eq!(parsed["thought_process"], "T");
}
#[test]
fn test_unwrap_rejects_non_string_name_as_mismatch() {
let inputs = [
r#"{"name": 42, "arguments": {"x": 1}}"#,
r#"{"name": null, "arguments": {"x": 1}}"#,
r#"{"name": {"nested": true}, "arguments": {"x": 1}}"#,
];
for input in &inputs {
let cleaned = clean_json_string(input, true, Some("submit_proposal"));
assert!(
cleaned.contains(r#""arguments""#),
"non-string name should leave envelope intact, got: {cleaned}"
);
}
}
#[test]
fn test_clean_json_string_with_latex_noise() {
let input = r#"The answer is \boxed{7}. {"key": "value"}"#;
assert_eq!(clean_json_string(input, false, None), r#"{"key": "value"}"#);
}
#[test]
fn test_split_args_basic() {
let result = split_args_respecting_brackets("a, b, c");
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn test_split_args_nested_brackets() {
let result = split_args_respecting_brackets("a, [b, c], d");
assert_eq!(result, vec!["a", "[b, c]", "d"]);
}
#[test]
fn test_split_args_nested_braces() {
let result = split_args_respecting_brackets(r#"a, {"key": "val, next"}, b"#);
assert_eq!(result, vec!["a", r#"{"key": "val, next"}"#, "b"]);
}
#[test]
fn test_split_args_quoted_commas() {
let result = split_args_respecting_brackets(r#""arg1", "arg2, with comma", "arg3""#);
assert_eq!(
result,
vec![r#""arg1""#, r#""arg2, with comma""#, r#""arg3""#]
);
}
#[test]
fn test_split_args_escape_sequences() {
let result = split_args_respecting_brackets(r#""arg1, \"escaped, comma\"", arg3"#);
assert_eq!(result.len(), 2);
assert!(result[0].contains("escaped, comma"));
assert_eq!(result[1], "arg3");
}
#[test]
fn test_split_args_empty() {
let result = split_args_respecting_brackets("");
assert!(result.is_empty());
}
#[test]
fn test_split_args_single() {
let result = split_args_respecting_brackets("only_one");
assert_eq!(result, vec!["only_one"]);
}
#[test]
fn test_split_args_mixed_quotes() {
let result = split_args_respecting_brackets(r#"'single, quoted', "double, quoted""#);
assert_eq!(result, vec!["'single, quoted'", r#""double, quoted""#]);
}
#[test]
fn test_split_args_nested_parens() {
let result = split_args_respecting_brackets("func(a, b), c");
assert_eq!(result, vec!["func(a, b)", "c"]);
}
#[test]
fn test_clean_json_string_no_braces() {
let input = "Just plain text without any braces";
assert_eq!(clean_json_string(input, false, None), input);
}
#[test]
fn test_clean_json_string_empty_input() {
assert_eq!(clean_json_string("", false, None), "");
}
#[test]
fn test_clean_json_string_empty_object() {
let input = "prefix { } suffix";
let result = clean_json_string(input, false, None);
assert_eq!(result, "{ }");
}
#[test]
fn test_clean_json_string_start_after_end() {
let input = "} ... {\"key\": \"val\"}";
let result = clean_json_string(input, false, None);
assert_eq!(result, "{\"key\": \"val\"}");
}
#[test]
fn test_clean_json_string_unwrap_disabled_keeps_wrapper() {
let input =
r#"{"name": "submit", "arguments": {"thought_process": "T", "solution_content": "S"}}"#;
let result = clean_json_string(input, false, None);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(parsed.get("name").is_some());
assert!(parsed.get("arguments").is_some());
}
#[test]
fn test_clean_json_string_unwrap_non_object_arguments() {
let input = r#"{"name": "tool", "arguments": "just a string"}"#;
let result = clean_json_string(input, true, None);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(parsed.get("name").is_some());
}
#[test]
fn test_clean_json_string_multiple_latex_then_json() {
let input = r#"Consider \boxed{42} and \frac{1}{2}. {"result": "ok"}"#;
let result = clean_json_string(input, false, None);
assert_eq!(result, r#"{"result": "ok"}"#);
}
#[test]
fn test_split_args_trailing_comma() {
let result = split_args_respecting_brackets("a, b, ");
assert_eq!(result, vec!["a", "b"]);
}
#[test]
fn test_split_args_whitespace_only() {
let result = split_args_respecting_brackets(" ");
assert!(result.is_empty());
}
#[test]
fn test_split_args_deeply_nested() {
let result = split_args_respecting_brackets("a, [[1, 2], [3, 4]], b");
assert_eq!(result, vec!["a", "[[1, 2], [3, 4]]", "b"]);
}
#[test]
fn test_split_args_unmatched_close_bracket() {
let result = split_args_respecting_brackets("a], b");
assert_eq!(result, vec!["a]", "b"]);
}
}