eval_magic/adapters/opencode/
mod.rs1fn is_valid_opencode_name(name: &str) -> bool {
13 if name.is_empty() || name.len() > 64 {
14 return false;
15 }
16 let mut prev = '-';
17 for ch in name.chars() {
18 if ch == '-' {
19 if prev == '-' {
20 return false;
21 }
22 } else if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() {
23 return false;
24 }
25 prev = ch;
26 }
27 !name.starts_with('-') && !name.ends_with('-')
28}
29
30fn sanitize_opencode_name(name: &str) -> String {
32 let mut out = String::new();
33 let mut prev_hyphen = false;
34 for ch in name.to_ascii_lowercase().chars() {
35 if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
36 out.push(ch);
37 prev_hyphen = false;
38 } else if !prev_hyphen {
39 out.push('-');
40 prev_hyphen = true;
41 }
42 }
43 while out.ends_with('-') {
44 out.pop();
45 }
46 while out.starts_with('-') {
47 out.remove(0);
48 }
49 if out.is_empty() {
50 out.push_str("skill");
51 }
52 if out.len() > 64 {
53 out.truncate(64);
54 while out.ends_with('-') {
55 out.pop();
56 }
57 }
58 out
59}
60
61pub(crate) fn opencode_slug(
66 prefix: &str,
67 iteration: u32,
68 condition: &str,
69 skill_name: &str,
70) -> String {
71 let condition = sanitize_opencode_name(condition);
72 let skill = sanitize_opencode_name(skill_name);
73 let base = format!("{prefix}{iteration}-{condition}-{skill}");
74 if base.len() <= 64 && is_valid_opencode_name(&base) {
75 return base;
76 }
77 let prefix = format!("{prefix}{iteration}-{condition}-");
79 let budget = 64usize.saturating_sub(prefix.len());
80 let mut truncated = skill.clone();
81 truncated.truncate(budget);
82 while truncated.ends_with('-') {
83 truncated.pop();
84 }
85 if truncated.is_empty() {
86 truncated.push_str("skill");
87 }
88 format!("{prefix}{truncated}")
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 const PREFIX: &str = "slow-powers-eval-";
96
97 #[test]
98 fn opencode_slug_sanitizes_underscores_and_special_characters() {
99 assert_eq!(
100 opencode_slug(PREFIX, 1, "with_skill", "My_Skill!"),
101 "slow-powers-eval-1-with-skill-my-skill"
102 );
103 assert_eq!(
104 opencode_slug(PREFIX, 2, "without_skill", "snake_case"),
105 "slow-powers-eval-2-without-skill-snake-case"
106 );
107 }
108
109 #[test]
110 fn opencode_slug_truncates_to_valid_max_length() {
111 let very_long = "a".repeat(200);
112 let slug = opencode_slug(PREFIX, 1, "with_skill", &very_long);
113 assert!(slug.len() <= 64);
114 assert!(is_valid_opencode_name(&slug));
115 assert!(slug.starts_with("slow-powers-eval-1-with-skill-"));
116 }
117}