use std::collections::HashMap;
use murmur_artifact::tool_name_of;
use crate::errors::RuntimeError;
use crate::types::StageRequest;
pub fn check_unique_tool_names<'a, I>(names: I) -> Result<(), RuntimeError>
where
I: IntoIterator<Item = &'a str>,
{
let mut seen: HashMap<&str, &str> = HashMap::new();
for name in names {
let tool = tool_name_of(name);
if let Some(first) = seen.insert(tool, name) {
return Err(RuntimeError::DuplicateToolName {
first: first.to_string(),
second: name.to_string(),
tool: tool.to_string(),
});
}
}
Ok(())
}
pub(crate) fn localize_artifact_names(request: &mut StageRequest) -> HashMap<String, String> {
let mut full_names = HashMap::with_capacity(request.artifacts.len());
for artifact in &mut request.artifacts {
let tool = tool_name_of(&artifact.name).to_string();
full_names.insert(tool.clone(), std::mem::replace(&mut artifact.name, tool));
}
request.allowlisted_tools = request
.allowlisted_tools
.iter()
.map(|name| tool_name_of(name).to_string())
.collect();
if let Some(inference) = request.inference.as_mut() {
if let Some(driver) = inference.driver.as_mut() {
driver.artifact = tool_name_of(&driver.artifact).to_string();
}
for alternate in &mut inference.alternates {
alternate.driver = tool_name_of(&alternate.driver).to_string();
}
if let Some(artifact) = inference.system_prompt_artifact.as_mut() {
*artifact = tool_name_of(artifact).to_string();
}
}
full_names
}
pub fn spawn_target(spawn_allow: &[String], capsule: &str) -> String {
spawn_allow
.iter()
.find(|entry| tool_name_of(entry) == capsule)
.cloned()
.unwrap_or_else(|| capsule.to_string())
}
pub(crate) fn full_name_of<'a>(full_names: &'a HashMap<String, String>, tool: &'a str) -> &'a str {
full_names.get(tool).map_or(tool, String::as_str)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unique_tool_names_refuses_two_scopes_with_one_name() {
let error = check_unique_tool_names(["murmur/tool-editor", "ana/tool-editor"]).unwrap_err();
let RuntimeError::DuplicateToolName {
first,
second,
tool,
} = &error
else {
panic!("expected DuplicateToolName, got {error:?}");
};
assert_eq!(first, "murmur/tool-editor");
assert_eq!(second, "ana/tool-editor");
assert_eq!(tool, "tool-editor");
let message = error.to_string();
for part in ["murmur/tool-editor", "ana/tool-editor", "'tool-editor'"] {
assert!(message.contains(part), "{message}");
}
}
#[test]
fn unique_tool_names_refuses_a_source_skill_beside_a_resolved_artifact() {
let error = check_unique_tool_names(["tool-editor", "murmur/tool-editor"]).unwrap_err();
assert!(matches!(error, RuntimeError::DuplicateToolName { .. }));
}
#[test]
fn delegate_task_spawn_target_maps_a_bare_name_to_its_entry() {
let allow = vec!["local/worker".to_string(), "ana/reviewer".to_string()];
assert_eq!(spawn_target(&allow, "worker"), "local/worker");
assert_eq!(spawn_target(&allow, "reviewer"), "ana/reviewer");
assert_eq!(spawn_target(&allow, "stranger"), "stranger");
}
#[test]
fn unique_tool_names_accepts_distinct_tool_names() {
check_unique_tool_names(["murmur/tool-editor", "ana/tool-x", "local-skill"]).unwrap();
}
}