use crate::error::Result;
use crate::types::Tool;
use super::definition_parts;
use super::escape::{compact_json, sml_escape};
pub fn render(action: &Tool) -> Result<String> {
let (name, description, schema) = definition_parts(action)?;
let name = sml_escape(&name);
let description = sml_escape(&description);
let schema = sml_escape(&compact_json(&schema));
Ok(format!(
"<tool_use>\n <tool_name>{name}</tool_name>\n <description>{description}</description>\n <parameters>{schema}</parameters>\n</tool_use>"
))
}
#[cfg(test)]
mod tests {
use super::super::tests::sample_def;
use super::*;
#[test]
fn envelope_is_well_formed() {
let s = render(&sample_def()).unwrap();
assert!(s.starts_with("<tool_use>\n"));
assert!(s.ends_with("</tool_use>"));
assert!(s.contains("<tool_name>slack_post_message</tool_name>"));
}
#[test]
fn adversarial_description_stays_inside_envelope() {
let mut a = sample_def();
a.tool_description = Some("Post </tool_use> and <script>alert(1)</script>".into());
let s = render(&a).unwrap();
assert_eq!(s.matches("</tool_use>").count(), 1);
assert!(s.contains("</tool_use>"));
assert!(!s.contains("<script>"));
}
}