eval_magic/adapters/
opencode_session.rs1use crate::core::AvailableSkill;
9
10pub fn render_opencode_available_skills_block(skills: &[AvailableSkill]) -> String {
15 if skills.is_empty() {
16 return String::new();
17 }
18 let mut sorted: Vec<&AvailableSkill> = skills.iter().collect();
19 sorted.sort_by(|a, b| a.name.cmp(&b.name));
20 let mut out = String::from("<available_skills>");
21 for s in sorted {
22 out.push_str(&format!(
23 "\n <skill>\n <name>{}</name>\n <description>{}</description>\n </skill>",
24 s.name, s.description
25 ));
26 }
27 out.push_str("\n</available_skills>");
28 out
29}
30
31pub fn render_opencode_plan_mode_context(profile_text: &str) -> String {
35 let trimmed = profile_text.trim();
36 if trimmed.is_empty() {
37 return String::new();
38 }
39 format!("<system-reminder>\n{trimmed}\n</system-reminder>")
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::core::AvailableSkill;
46
47 fn skill(name: &str, description: &str) -> AvailableSkill {
48 AvailableSkill {
49 name: name.into(),
50 path: format!("/x/{name}/SKILL.md"),
51 description: description.into(),
52 }
53 }
54
55 #[test]
56 fn renders_opencode_xml_with_name_and_description() {
57 let block =
58 render_opencode_available_skills_block(&[skill("git-release", "Create releases")]);
59 assert!(block.contains("<available_skills>"));
60 assert!(block.contains("</available_skills>"));
61 assert!(block.contains("<name>git-release</name>"));
62 assert!(block.contains("<description>Create releases</description>"));
63 assert!(block.contains("<skill>"));
64 assert!(block.contains("</skill>"));
65 }
66
67 #[test]
68 fn sorts_skills_by_name() {
69 let block =
70 render_opencode_available_skills_block(&[skill("zebra", "z"), skill("alpha", "a")]);
71 assert!(
72 block.find("<name>alpha</name>").unwrap() < block.find("<name>zebra</name>").unwrap()
73 );
74 }
75
76 #[test]
77 fn empty_list_renders_empty_string() {
78 assert_eq!(render_opencode_available_skills_block(&[]), "");
79 }
80
81 #[test]
82 fn plan_mode_wraps_in_system_reminder() {
83 let block = render_opencode_plan_mode_context("OpenCode plan mode is active.");
84 assert_eq!(
85 block,
86 "<system-reminder>\nOpenCode plan mode is active.\n</system-reminder>"
87 );
88 }
89
90 #[test]
91 fn plan_mode_trims_surrounding_whitespace() {
92 let block = render_opencode_plan_mode_context("\n\n PROFILE-BODY \n\n");
93 assert_eq!(block, "<system-reminder>\nPROFILE-BODY\n</system-reminder>");
94 }
95
96 #[test]
97 fn plan_mode_empty_or_whitespace_renders_empty_string() {
98 assert_eq!(render_opencode_plan_mode_context(""), "");
99 assert_eq!(render_opencode_plan_mode_context(" \n "), "");
100 }
101}