aether_core/core/
agent_registry.rs1use crate::agent_spec::AgentSpec;
2use std::sync::Arc;
3use thiserror::Error;
4
5#[derive(Clone, Debug, Default)]
7pub struct AgentRegistry {
8 specs: Arc<[AgentSpec]>,
9}
10
11impl AgentRegistry {
12 pub fn new(specs: Vec<AgentSpec>) -> Self {
13 Self { specs: specs.into() }
14 }
15
16 pub fn all(&self) -> &[AgentSpec] {
17 &self.specs
18 }
19
20 pub fn get(&self, name: &str) -> Option<&AgentSpec> {
21 self.specs.iter().find(|spec| spec.name == name)
22 }
23
24 pub fn agent_invocable(&self) -> impl Iterator<Item = &AgentSpec> {
25 self.specs.iter().filter(|spec| spec.exposure.agent_invocable)
26 }
27
28 pub fn resolve_agent_invocable(&self, name: &str) -> Result<AgentSpec, AgentRegistryError> {
30 let spec = self.get(name).ok_or_else(|| AgentRegistryError::NotFound { name: name.to_string() })?;
31 if !spec.exposure.agent_invocable {
32 return Err(AgentRegistryError::NotAgentInvocable { name: name.to_string() });
33 }
34 Ok(spec.clone())
35 }
36}
37
38#[derive(Clone, Debug, Error, Eq, PartialEq)]
39pub enum AgentRegistryError {
40 #[error("Agent '{name}' not found")]
41 NotFound { name: String },
42 #[error("Agent '{name}' is not agent-invocable")]
43 NotAgentInvocable { name: String },
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::agent_spec::AgentSpecExposure;
50
51 #[test]
52 fn delegation_resolution_distinguishes_missing_and_unexposed_agents() {
53 let model = "anthropic:claude-sonnet-4-5".parse().unwrap();
54 let mut delegate = AgentSpec::bare(&model, None, Vec::new());
55 delegate.name = "delegate".to_string();
56 delegate.exposure = AgentSpecExposure::agent_only();
57 let mut user_only = AgentSpec::bare(&model, None, Vec::new());
58 user_only.name = "user-only".to_string();
59 user_only.exposure = AgentSpecExposure::user_only();
60 let registry = AgentRegistry::new(vec![delegate, user_only]);
61
62 assert_eq!(registry.resolve_agent_invocable("delegate").unwrap().name, "delegate");
63 assert!(matches!(
64 registry.resolve_agent_invocable("user-only"),
65 Err(AgentRegistryError::NotAgentInvocable { name }) if name == "user-only"
66 ));
67 assert!(matches!(
68 registry.resolve_agent_invocable("missing"),
69 Err(AgentRegistryError::NotFound { name }) if name == "missing"
70 ));
71 }
72}