dejadb_core/format/tool_schema/
hermes.rs1use 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 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}