use sim_kernel::Symbol;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExternalNamePolicy {
McpTool,
OpenAiTool,
FileSafe,
HumanReadable,
}
pub fn external_name(symbol: &Symbol, policy: ExternalNamePolicy) -> String {
let qualified = symbol.as_qualified_str();
match policy {
ExternalNamePolicy::OpenAiTool => openai_tool(&qualified),
ExternalNamePolicy::FileSafe => map_chars(&qualified, |ch| matches!(ch, '-' | '_' | '.')),
ExternalNamePolicy::McpTool => map_chars(&qualified, |ch| matches!(ch, '-' | '_')),
ExternalNamePolicy::HumanReadable => qualified,
}
}
fn openai_tool(qualified: &str) -> String {
let mut out = String::new();
let mut last_was_separator = false;
for ch in qualified.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
last_was_separator = false;
} else if !last_was_separator {
out.push('_');
last_was_separator = true;
}
}
out.trim_matches('_').to_owned()
}
fn map_chars(qualified: &str, keep_extra: impl Fn(char) -> bool) -> String {
qualified
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || keep_extra(ch) {
ch
} else {
'_'
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn policies_on_qualified_symbol() {
let symbol = Symbol::qualified("skill", "do_thing/v2");
assert_eq!(
external_name(&symbol, ExternalNamePolicy::OpenAiTool),
"skill_do_thing_v2"
);
assert_eq!(
external_name(&symbol, ExternalNamePolicy::FileSafe),
"skill_do_thing_v2"
);
assert_eq!(
external_name(&symbol, ExternalNamePolicy::McpTool),
"skill_do_thing_v2"
);
assert_eq!(
external_name(&symbol, ExternalNamePolicy::HumanReadable),
"skill/do_thing/v2"
);
}
#[test]
fn openai_tool_empty_case() {
let symbol = Symbol::qualified("--", "//");
assert_eq!(external_name(&symbol, ExternalNamePolicy::OpenAiTool), "");
}
}