const A2A_TOOL_PREFIX: &str = "a2a__";
pub fn a2a_tool_name(agent_name: &str) -> String {
format!("{A2A_TOOL_PREFIX}{agent_name}")
}
pub fn parse_a2a_tool_name(name: &str) -> Option<&str> {
let agent = name.strip_prefix(A2A_TOOL_PREFIX)?;
validate_a2a_agent_name(agent).ok()?;
Some(agent)
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum A2aAgentNameError {
#[error("A2A agent name must not be empty")]
Empty,
#[error(
"A2A agent name {0:?} must not contain \"__\", which separates the parts of a \
bridged tool name"
)]
ContainsDoubleUnderscore(String),
#[error(
"A2A agent name {0:?} must not end with \"_\", which would merge with a \"__\" \
separator placed after it"
)]
EndsWithUnderscore(String),
}
pub fn validate_a2a_agent_name(agent_name: &str) -> Result<(), A2aAgentNameError> {
if agent_name.is_empty() {
return Err(A2aAgentNameError::Empty);
}
if agent_name.contains("__") {
return Err(A2aAgentNameError::ContainsDoubleUnderscore(
agent_name.to_string(),
));
}
if agent_name.ends_with('_') {
return Err(A2aAgentNameError::EndsWithUnderscore(
agent_name.to_string(),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mcp::validate_mcp_server_name;
#[test]
fn tool_name_is_prefixed_and_round_trips() {
assert_eq!(a2a_tool_name("nous"), "a2a__nous");
assert_eq!(parse_a2a_tool_name("a2a__nous"), Some("nous"));
assert_eq!(parse_a2a_tool_name("a2a__"), None);
assert_eq!(parse_a2a_tool_name("mcp__nous__ask"), None);
assert_eq!(parse_a2a_tool_name("a2a__evil__foo"), None);
}
#[test]
fn names_follow_the_mcp_server_name_rules_exactly() {
for name in ["", "evil__foo", "evil_", "nous", "a-b", "a_b", "_lead"] {
assert_eq!(
validate_a2a_agent_name(name).is_ok(),
validate_mcp_server_name(name).is_ok(),
"rules disagree on {name:?}"
);
}
assert_eq!(validate_a2a_agent_name(""), Err(A2aAgentNameError::Empty));
assert_eq!(
validate_a2a_agent_name("evil_"),
Err(A2aAgentNameError::EndsWithUnderscore("evil_".to_string()))
);
}
}