atman_runtime/
tool_naming.rs1use std::collections::HashMap;
2
3pub fn to_wire(flow_name: &str) -> String {
7 flow_name.replace('.', "_")
8}
9
10pub fn from_wire(wire_name: &str, tools: &[crate::tool::ToolSpec]) -> String {
13 for t in tools {
14 if to_wire(&t.name) == wire_name {
15 return t.name.clone();
16 }
17 }
18 wire_name.to_string()
19}
20
21#[derive(Debug, Default, Clone)]
22pub struct ToolNaming {
23 per_provider: HashMap<String, HashMap<String, String>>,
24 reverse: HashMap<String, HashMap<String, String>>,
25}
26
27impl ToolNaming {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn map(
33 &mut self,
34 provider: impl Into<String>,
35 flow_name: impl Into<String>,
36 provider_native: impl Into<String>,
37 ) {
38 let provider = provider.into();
39 let flow_name = flow_name.into();
40 let native = provider_native.into();
41 self.per_provider
42 .entry(provider.clone())
43 .or_default()
44 .insert(flow_name.clone(), native.clone());
45 self.reverse
46 .entry(provider)
47 .or_default()
48 .insert(native, flow_name);
49 }
50
51 pub fn to_provider<'a>(&'a self, provider: &str, flow_name: &'a str) -> &'a str {
52 self.per_provider
53 .get(provider)
54 .and_then(|m| m.get(flow_name))
55 .map(|s| s.as_str())
56 .unwrap_or(flow_name)
57 }
58
59 pub fn from_provider<'a>(&'a self, provider: &str, native_name: &'a str) -> &'a str {
60 self.reverse
61 .get(provider)
62 .and_then(|m| m.get(native_name))
63 .map(|s| s.as_str())
64 .unwrap_or(native_name)
65 }
66
67 pub fn known_flow_names(&self, provider: &str) -> impl Iterator<Item = &str> {
68 self.per_provider
69 .get(provider)
70 .into_iter()
71 .flat_map(|m| m.keys().map(|s| s.as_str()))
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn to_provider_returns_mapped_name() {
81 let mut n = ToolNaming::new();
82 n.map("anthropic", "fs.read", "str_replace_based_edit_tool");
83 assert_eq!(
84 n.to_provider("anthropic", "fs.read"),
85 "str_replace_based_edit_tool"
86 );
87 }
88
89 #[test]
90 fn to_provider_falls_back_to_flow_name_when_unmapped() {
91 let n = ToolNaming::new();
92 assert_eq!(n.to_provider("anthropic", "fs.read"), "fs.read");
93 }
94
95 #[test]
96 fn from_provider_is_reverse_of_to_provider() {
97 let mut n = ToolNaming::new();
98 n.map("openai", "bash.exec", "run_bash");
99 assert_eq!(n.to_provider("openai", "bash.exec"), "run_bash");
100 assert_eq!(n.from_provider("openai", "run_bash"), "bash.exec");
101 }
102
103 #[test]
104 fn from_provider_falls_back_to_native_name_when_unmapped() {
105 let n = ToolNaming::new();
106 assert_eq!(n.from_provider("openai", "unknown_tool"), "unknown_tool");
107 }
108
109 #[test]
110 fn maps_are_per_provider() {
111 let mut n = ToolNaming::new();
112 n.map("anthropic", "fs.read", "str_replace_based_edit_tool");
113 n.map("openai", "fs.read", "read_file");
114 assert_eq!(
115 n.to_provider("anthropic", "fs.read"),
116 "str_replace_based_edit_tool"
117 );
118 assert_eq!(n.to_provider("openai", "fs.read"), "read_file");
119 }
120
121 #[test]
122 fn known_flow_names_lists_flow_side_only() {
123 let mut n = ToolNaming::new();
124 n.map("anthropic", "fs.read", "str_replace_based_edit_tool");
125 n.map("anthropic", "bash.exec", "bash");
126 let mut names: Vec<_> = n.known_flow_names("anthropic").collect();
127 names.sort();
128 assert_eq!(names, vec!["bash.exec", "fs.read"]);
129 }
130}