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 delegate = AgentSpec {
54 name: "delegate".to_string(),
55 exposure: AgentSpecExposure::agent_only(),
56 ..AgentSpec::default()
57 };
58 let user_only = AgentSpec {
59 name: "user-only".to_string(),
60 exposure: AgentSpecExposure::user_only(),
61 ..AgentSpec::default()
62 };
63 let registry = AgentRegistry::new(vec![delegate, user_only]);
64
65 assert_eq!(registry.resolve_agent_invocable("delegate").unwrap().name, "delegate");
66 assert!(matches!(
67 registry.resolve_agent_invocable("user-only"),
68 Err(AgentRegistryError::NotAgentInvocable { name }) if name == "user-only"
69 ));
70 assert!(matches!(
71 registry.resolve_agent_invocable("missing"),
72 Err(AgentRegistryError::NotFound { name }) if name == "missing"
73 ));
74 }
75}