use std::collections::BTreeSet;
use super::syntax::preview_str;
pub(crate) fn parse_native_json_tool_calls(
text: &str,
known_tools: &BTreeSet<String>,
) -> (Vec<serde_json::Value>, Vec<String>) {
let mut results = Vec::new();
let mut errors = Vec::new();
let Some(items) = find_native_json_items(text) else {
return (results, errors);
};
for item in items {
let func = item
.get("function")
.and_then(|function| function.as_object());
let Some(func) = func else { continue };
let name = func
.get("name")
.and_then(|name| name.as_str())
.unwrap_or("");
if name.is_empty() {
continue;
}
if !known_tools.contains(name) {
let available: Vec<_> = known_tools.iter().take(20).cloned().collect();
errors.push(format!(
"Unknown tool '{}'. Available tools: [{}]",
name,
available.join(", ")
));
continue;
}
let arguments = match func.get("arguments") {
Some(serde_json::Value::String(raw)) => match serde_json::from_str(raw) {
Ok(value) => value,
Err(error) => {
errors.push(format!(
"Could not parse arguments for tool '{}': {}. Raw: {}",
name,
error,
preview_str(raw, 200)
));
continue;
}
},
Some(obj @ serde_json::Value::Object(_)) => obj.clone(),
_ => serde_json::Value::Object(Default::default()),
};
let call_id = item
.get("id")
.and_then(|id| id.as_str())
.unwrap_or("native_fallback");
results.push(serde_json::json!({
"id": call_id,
"name": name,
"arguments": arguments,
}));
}
(results, errors)
}
fn find_native_json_items(text: &str) -> Option<Vec<serde_json::Value>> {
let bytes = text.as_bytes();
for (offset, &byte) in bytes.iter().enumerate() {
if byte != b'[' && byte != b'{' {
continue;
}
let parsed = serde_json::Deserializer::from_str(&text[offset..])
.into_iter::<serde_json::Value>()
.next()
.and_then(|result| result.ok())
.map(|value| match value {
serde_json::Value::Array(items) => items,
other => vec![other],
});
let Some(items) = parsed else {
continue;
};
if items.iter().any(|item| {
item.get("function")
.is_some_and(serde_json::Value::is_object)
}) {
return Some(items);
}
}
None
}