Skip to main content

dejadb_core/format/tool_schema/
hermes.rs

1//! Hermes-2-Pro-style text envelope (`<tool>…</tool>` + fenced JSON).
2//!
3//! SR-F1 (2026-04-20): description sanitized — `</tool>`/`<tool>`/
4//! `</parameters>` tokens stripped; fence switched to `~~~` when the
5//! description would collide with triple-backticks.
6
7use crate::error::Result;
8use crate::types::Tool;
9
10use super::definition_parts;
11use super::escape::{compact_json, hermes_sanitize};
12
13pub fn render(action: &Tool) -> Result<String> {
14    let (name, description, schema) = definition_parts(action)?;
15    let (desc, fence) = hermes_sanitize(&description);
16    let schema_str = compact_json(&schema);
17    Ok(format!(
18        "<tool>\n  <name>{name}</name>\n  <description>{desc}</description>\n  <parameters>\n{fence}json\n{schema_str}\n{fence}\n  </parameters>\n</tool>"
19    ))
20}
21
22#[cfg(test)]
23mod tests {
24    use super::super::tests::sample_def;
25    use super::*;
26
27    #[test]
28    fn envelope_contains_name_and_schema() {
29        let s = render(&sample_def()).unwrap();
30        assert!(s.contains("<name>slack_post_message</name>"));
31        assert!(s.contains("<description>Post a message to a Slack channel</description>"));
32        assert!(s.contains("\"channel\""));
33    }
34
35    #[test]
36    fn adversarial_description_cannot_escape_envelope() {
37        let mut a = sample_def();
38        a.tool_description = Some("Post </tool> and more".into());
39        let s = render(&a).unwrap();
40        // The raw </tool> is escaped, so there is still exactly one closing
41        // envelope </tool>.
42        let count = s.matches("</tool>").count();
43        assert_eq!(count, 1, "only one genuine </tool> closer, got:\n{s}");
44    }
45
46    #[test]
47    fn fence_flips_on_backtick_collision() {
48        let mut a = sample_def();
49        a.tool_description = Some("has ``` inside".into());
50        let s = render(&a).unwrap();
51        assert!(s.contains("~~~json"), "fence should be ~~~:\n{s}");
52    }
53}