sim_lib_surface_card/
name.rs1use sim_kernel::Symbol;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ExternalNamePolicy {
12 McpTool,
15 OpenAiTool,
19 FileSafe,
22 HumanReadable,
24}
25
26pub fn external_name(symbol: &Symbol, policy: ExternalNamePolicy) -> String {
28 let qualified = symbol.as_qualified_str();
29 match policy {
30 ExternalNamePolicy::OpenAiTool => openai_tool(&qualified),
31 ExternalNamePolicy::FileSafe => map_chars(&qualified, |ch| matches!(ch, '-' | '_' | '.')),
32 ExternalNamePolicy::McpTool => map_chars(&qualified, |ch| matches!(ch, '-' | '_')),
33 ExternalNamePolicy::HumanReadable => qualified,
34 }
35}
36
37fn openai_tool(qualified: &str) -> String {
40 let mut out = String::new();
41 let mut last_was_separator = false;
42 for ch in qualified.chars() {
43 if ch.is_ascii_alphanumeric() {
44 out.push(ch);
45 last_was_separator = false;
46 } else if !last_was_separator {
47 out.push('_');
48 last_was_separator = true;
49 }
50 }
51 out.trim_matches('_').to_owned()
52}
53
54fn map_chars(qualified: &str, keep_extra: impl Fn(char) -> bool) -> String {
57 qualified
58 .chars()
59 .map(|ch| {
60 if ch.is_ascii_alphanumeric() || keep_extra(ch) {
61 ch
62 } else {
63 '_'
64 }
65 })
66 .collect()
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn policies_on_qualified_symbol() {
75 let symbol = Symbol::qualified("skill", "do_thing/v2");
76 assert_eq!(
78 external_name(&symbol, ExternalNamePolicy::OpenAiTool),
79 "skill_do_thing_v2"
80 );
81 assert_eq!(
82 external_name(&symbol, ExternalNamePolicy::FileSafe),
83 "skill_do_thing_v2"
84 );
85 assert_eq!(
86 external_name(&symbol, ExternalNamePolicy::McpTool),
87 "skill_do_thing_v2"
88 );
89 assert_eq!(
90 external_name(&symbol, ExternalNamePolicy::HumanReadable),
91 "skill/do_thing/v2"
92 );
93 }
94
95 #[test]
96 fn openai_tool_empty_case() {
97 let symbol = Symbol::qualified("--", "//");
100 assert_eq!(external_name(&symbol, ExternalNamePolicy::OpenAiTool), "");
101 }
102}