Skip to main content

dejadb_core/format/tool_schema/
sml.rs

1//! SML (an XML-like) tool envelope.
2//!
3//! Shape:
4//!   <tool_use>
5//!     <tool_name>…</tool_name>
6//!     <description>…</description>
7//!     <parameters>…</parameters>
8//!   </tool_use>
9//!
10//! SR-F1: name, description, and schema JSON all run through `sml_escape`
11//! so `<`, `>`, `&`, `"`, `'` become entities. A description containing
12//! `</tool_use>` cannot escape the envelope.
13
14use crate::error::Result;
15use crate::types::Tool;
16
17use super::definition_parts;
18use super::escape::{compact_json, sml_escape};
19
20pub fn render(action: &Tool) -> Result<String> {
21    let (name, description, schema) = definition_parts(action)?;
22    let name = sml_escape(&name);
23    let description = sml_escape(&description);
24    let schema = sml_escape(&compact_json(&schema));
25    Ok(format!(
26        "<tool_use>\n  <tool_name>{name}</tool_name>\n  <description>{description}</description>\n  <parameters>{schema}</parameters>\n</tool_use>"
27    ))
28}
29
30#[cfg(test)]
31mod tests {
32    use super::super::tests::sample_def;
33    use super::*;
34
35    #[test]
36    fn envelope_is_well_formed() {
37        let s = render(&sample_def()).unwrap();
38        assert!(s.starts_with("<tool_use>\n"));
39        assert!(s.ends_with("</tool_use>"));
40        assert!(s.contains("<tool_name>slack_post_message</tool_name>"));
41    }
42
43    #[test]
44    fn adversarial_description_stays_inside_envelope() {
45        let mut a = sample_def();
46        a.tool_description = Some("Post </tool_use> and <script>alert(1)</script>".into());
47        let s = render(&a).unwrap();
48        // Exactly ONE genuine closing <tool_use> (the envelope's).
49        assert_eq!(s.matches("</tool_use>").count(), 1);
50        // The description's close-bracket has been entity-escaped.
51        assert!(s.contains("&lt;/tool_use&gt;"));
52        assert!(!s.contains("<script>"));
53    }
54}