#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Role {
Assistant,
Planner,
Router,
Coder,
TestCoder,
Verifier,
SurgicalCoder,
Cto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProviderKind {
Ollama,
AnthropicClaude,
}
#[derive(Debug, Clone, Default)]
pub struct ModelMap {
entries: Vec<(Role, ProviderKind, String)>,
}
impl ModelMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, role: Role, provider: ProviderKind, model: impl Into<String>) {
self.entries.retain(|(r, _, _)| *r != role);
self.entries.push((role, provider, model.into()));
}
#[must_use]
pub fn resolve(&self, role: Role) -> Option<(ProviderKind, &str)> {
self.entries
.iter()
.rev() .find(|(r, _, _)| *r == role)
.map(|(_, p, m)| (*p, m.as_str()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_map_resolves_nothing() {
let map = ModelMap::new();
assert!(map.resolve(Role::Coder).is_none());
assert!(map.resolve(Role::Planner).is_none());
}
#[test]
fn set_then_resolve_round_trips() {
let mut map = ModelMap::new();
map.set(Role::Coder, ProviderKind::Ollama, "qwen3-coder:30b");
let (kind, name) = map.resolve(Role::Coder).expect("coder should be set");
assert_eq!(kind, ProviderKind::Ollama);
assert_eq!(name, "qwen3-coder:30b");
}
#[test]
fn set_twice_same_role_last_wins() {
let mut map = ModelMap::new();
map.set(Role::Planner, ProviderKind::Ollama, "qwen3.5:9b");
map.set(
Role::Planner,
ProviderKind::AnthropicClaude,
"claude-opus-4-7",
);
let (kind, name) = map.resolve(Role::Planner).unwrap();
assert_eq!(kind, ProviderKind::AnthropicClaude);
assert_eq!(name, "claude-opus-4-7");
}
#[test]
fn multiple_roles_coexist() {
let mut map = ModelMap::new();
map.set(Role::Coder, ProviderKind::Ollama, "qwen3-coder:30b");
map.set(Role::Planner, ProviderKind::Ollama, "qwen3.5:9b");
map.set(Role::Cto, ProviderKind::AnthropicClaude, "claude-opus-4-7");
assert_eq!(map.resolve(Role::Coder).unwrap().1, "qwen3-coder:30b");
assert_eq!(map.resolve(Role::Planner).unwrap().1, "qwen3.5:9b");
assert_eq!(map.resolve(Role::Cto).unwrap().1, "claude-opus-4-7");
assert!(map.resolve(Role::Verifier).is_none());
}
}