use serde_json::Value;
pub(crate) fn render_tool_guide(list: &[Value]) -> Option<String> {
if list.is_empty() {
return None;
}
let mut lines = Vec::new();
lines.push(
"You call tools by emitting a sole ```tool_code fence (no other prose) \
with one call per line. Prefer keyword arguments. A trailing ? marks an \
optional argument."
.to_owned(),
);
lines.push("Available tools:".to_owned());
let mut listed = 0usize;
for tool in list {
let Some(function) = tool.get("function") else {
continue;
};
let Some(name) = function.get("name").and_then(Value::as_str) else {
continue;
};
let description = function
.get("description")
.and_then(Value::as_str)
.unwrap_or("");
let params = function
.get("parameters")
.and_then(|value| value.get("properties"))
.and_then(Value::as_object);
let required = function
.get("parameters")
.and_then(|value| value.get("required"))
.and_then(Value::as_array)
.map(|required| {
required
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>()
})
.unwrap_or_default();
let mut arg_bits = Vec::new();
if let Some(params) = params {
for key in params.keys() {
if required.contains(&key.as_str()) {
arg_bits.push(format!("{key}=..."));
} else {
arg_bits.push(format!("{key}=...?"));
}
}
}
let sig = if arg_bits.is_empty() {
format!("{name}()")
} else {
format!("{name}({})", arg_bits.join(", "))
};
if description.is_empty() {
lines.push(format!("- {sig}"));
} else {
lines.push(format!("- {sig}: {description}"));
}
listed += 1;
}
if listed == 0 {
return None;
}
lines.push("Example: ```tool_code\nsearch(query=\"example\")\n```".to_owned());
Some(lines.join("\n"))
}