agentlink_domain/
registry.rs1use crate::layout::Layout;
12use crate::provider::{ManifestError, Provider};
13
14include!(concat!(env!("OUT_DIR"), "/builtin_manifests.rs"));
18
19#[derive(Debug, Clone, Default)]
21pub struct Registry {
22 providers: Vec<Provider>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
27#[error("unknown provider `{id}` (known: {})", known.join(", "))]
28pub struct UnknownProvider {
29 pub id: String,
30 pub known: Vec<String>,
31}
32
33impl Registry {
34 pub fn builtin(layout: &Layout) -> Result<Self, ManifestError> {
36 Self::load(
37 layout,
38 BUILTIN_MANIFESTS.iter().map(|(name, text)| (*name, *text)),
39 )
40 }
41
42 pub fn load<'a>(
47 layout: &Layout,
48 sources: impl IntoIterator<Item = (&'a str, &'a str)>,
49 ) -> Result<Self, ManifestError> {
50 let mut registry = Self::default();
51 for (name, text) in sources {
52 let provider =
53 crate::provider::parse(name, text, |kind| layout.canonical(kind).clone())?;
54 registry.insert(provider);
55 }
56 registry.providers.sort_by(|a, b| a.id.cmp(&b.id));
57 Ok(registry)
58 }
59
60 pub fn with_local<'a>(
66 layout: &Layout,
67 locals: impl IntoIterator<Item = (&'a str, &'a str)>,
68 ) -> Result<Self, ManifestError> {
69 let mut registry = Self::builtin(layout)?;
70 for (name, text) in locals {
71 let provider =
72 crate::provider::parse(name, text, |kind| layout.canonical(kind).clone())?;
73 registry.insert(provider);
74 }
75 registry.providers.sort_by(|a, b| a.id.cmp(&b.id));
76 Ok(registry)
77 }
78
79 fn insert(&mut self, provider: Provider) {
80 self.providers.retain(|existing| existing.id != provider.id);
81 self.providers.push(provider);
82 }
83
84 pub fn all(&self) -> &[Provider] {
85 &self.providers
86 }
87
88 pub fn get(&self, id: &str) -> Option<&Provider> {
89 self.providers.iter().find(|provider| provider.id == id)
90 }
91
92 pub fn ids(&self) -> Vec<String> {
93 self.providers
94 .iter()
95 .map(|provider| provider.id.clone())
96 .collect()
97 }
98
99 pub fn select(&self, ids: Option<&[String]>) -> Result<Vec<&Provider>, UnknownProvider> {
101 match ids {
102 None => Ok(self.providers.iter().collect()),
103 Some(ids) => ids
104 .iter()
105 .map(|id| {
106 self.get(id).ok_or_else(|| UnknownProvider {
107 id: id.clone(),
108 known: self.ids(),
109 })
110 })
111 .collect(),
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::model::{ResourceKind, Strategy};
120
121 #[test]
122 fn every_shipped_manifest_is_valid() {
123 let registry = Registry::builtin(&Layout::default()).expect("shipped manifests must parse");
126 assert!(!registry.all().is_empty(), "no providers were embedded");
127 }
128
129 #[test]
130 fn shipped_manifests_have_unique_ids() {
131 let registry = Registry::builtin(&Layout::default()).unwrap();
132 let mut ids = registry.ids();
133 let total = ids.len();
134 ids.sort();
135 ids.dedup();
136 assert_eq!(
137 ids.len(),
138 total,
139 "duplicate provider ids among shipped manifests"
140 );
141 }
142
143 #[test]
144 fn the_reference_providers_are_present_and_correct() {
145 let registry = Registry::builtin(&Layout::default()).unwrap();
146
147 let claude = registry.get("claude-code").expect("claude-code manifest");
150 let instructions = claude.capability(ResourceKind::Instructions).unwrap();
151 assert_eq!(instructions.strategy, Strategy::Link);
152 assert_eq!(instructions.path.as_str(), "CLAUDE.md");
153 assert!(
154 instructions.has_import_fallback(),
155 "CLAUDE.md cannot be symlinked on unprivileged Windows, so a fallback is required"
156 );
157
158 let agy = registry.get("antigravity").expect("antigravity manifest");
160 for &resource in ResourceKind::ALL {
161 assert_eq!(
162 agy.capability(resource).unwrap().strategy,
163 Strategy::Native,
164 "antigravity should need no work for {resource}"
165 );
166 }
167 }
168
169 #[test]
170 fn most_capabilities_need_no_writes_at_all() {
171 let registry = Registry::builtin(&Layout::default()).unwrap();
175 let (native, total) = registry.all().iter().flat_map(|p| &p.capabilities).fold(
176 (0, 0),
177 |(native, total), cap| {
178 (
179 native + usize::from(cap.strategy == Strategy::Native),
180 total + 1,
181 )
182 },
183 );
184 assert!(
185 native * 2 > total,
186 "expected a majority of native capabilities, got {native}/{total}"
187 );
188 }
189
190 #[test]
191 fn local_manifests_override_shipped_ones_by_id() {
192 let layout = Layout::default();
193 let registry = Registry::load(
194 &layout,
195 [
196 ("a.toml", "schema = 1\nid = \"dup\"\nname = \"First\"\n"),
197 ("b.toml", "schema = 1\nid = \"dup\"\nname = \"Second\"\n"),
198 ],
199 )
200 .unwrap();
201 assert_eq!(registry.all().len(), 1);
202 assert_eq!(registry.get("dup").unwrap().name, "Second");
203 }
204
205 #[test]
206 fn selecting_an_unknown_provider_lists_the_valid_ones() {
207 let registry = Registry::builtin(&Layout::default()).unwrap();
208 let err = registry.select(Some(&["nope".to_string()])).unwrap_err();
209 assert_eq!(err.id, "nope");
210 assert!(err.known.contains(&"claude-code".to_string()));
211 }
212
213 #[test]
214 fn selecting_none_yields_every_provider() {
215 let registry = Registry::builtin(&Layout::default()).unwrap();
216 assert_eq!(registry.select(None).unwrap().len(), registry.all().len());
217 }
218}