pub fn normalize_tool_call_id(id: &str, target_provider: &crate::types::Provider) -> String {
match target_provider {
crate::types::Provider::Anthropic => {
let id = if id.contains('|') {
id.split('|').next().unwrap_or(id)
} else {
id
};
let sanitized: String = id
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
c
} else {
'_'
}
})
.take(64)
.collect();
sanitized
}
crate::types::Provider::OpenAI | crate::types::Provider::Groq => {
if id.len() > 40 {
id[..40].to_string()
} else {
id.to_string()
}
}
_ => id.to_string(),
}
}
pub struct ToolCallIdMapper {
to_normalized: std::collections::HashMap<String, String>,
from_normalized: std::collections::HashMap<String, String>,
target_provider: crate::types::Provider,
}
impl ToolCallIdMapper {
pub fn new(target_provider: crate::types::Provider) -> Self {
Self {
to_normalized: std::collections::HashMap::new(),
from_normalized: std::collections::HashMap::new(),
target_provider,
}
}
pub fn normalize(&mut self, id: &str) -> String {
if let Some(normalized) = self.to_normalized.get(id) {
return normalized.clone();
}
let normalized = normalize_tool_call_id(id, &self.target_provider);
let mut final_normalized = normalized.clone();
let mut counter = 1;
while self.from_normalized.contains_key(&final_normalized) {
final_normalized = format!("{}_{}", normalized, counter);
counter += 1;
}
self.to_normalized
.insert(id.to_string(), final_normalized.clone());
self.from_normalized
.insert(final_normalized.clone(), id.to_string());
final_normalized
}
pub fn denormalize(&self, normalized: &str) -> Option<&String> {
self.from_normalized.get(normalized)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_for_anthropic() {
let id = "call_abc123+def/ghi=";
let normalized = normalize_tool_call_id(id, &crate::types::Provider::Anthropic);
assert!(normalized.len() <= 64);
assert!(normalized
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'));
}
#[test]
fn test_normalize_pipe_separated() {
let id = "call_123|very_long_suffix_here";
let normalized = normalize_tool_call_id(id, &crate::types::Provider::Anthropic);
assert!(!normalized.contains('|'));
assert_eq!(normalized, "call_123");
}
#[test]
fn test_normalize_for_openai() {
let id = "a".repeat(50);
let normalized = normalize_tool_call_id(&id, &crate::types::Provider::OpenAI);
assert_eq!(normalized.len(), 40);
}
}