brainos_backends/
capabilities.rs1use brain::BrainConfig;
27use intent::{verbs, BackendId, ToolAnnotations, ToolDescriptor, ToolSource, ToolUsage, Verb};
28
29pub fn native(
35 ns: &str,
36 action: &str,
37 source: ToolSource,
38 annotations: ToolAnnotations,
39 mut usage: ToolUsage,
40) -> ToolDescriptor {
41 let spec = verbs::lookup(ns, action);
42 let description = spec.map(|s| s.summary.to_string()).unwrap_or_default();
43 if usage.tier.is_none() {
44 usage.tier = spec.map(|s| s.tier_hint.as_str().to_string());
45 }
46 ToolDescriptor {
47 tool_id: format!("native:{ns}.{action}"),
50 source,
51 verb: Verb::new(ns, action),
52 description,
53 input_schema: serde_json::json!({ "type": "object" }),
54 output_schema: None,
55 capabilities: vec![format!("{ns}.{action}")],
56 annotations,
57 usage,
58 embedding: None,
59 }
60}
61
62pub fn backend(name: &str) -> ToolSource {
65 ToolSource::NativeBackend {
66 backend: BackendId::new(name),
67 }
68}
69
70pub fn read_only() -> ToolAnnotations {
72 ToolAnnotations {
73 read_only_hint: true,
74 destructive_hint: false,
75 idempotent_hint: true,
76 }
77}
78
79pub fn destructive() -> ToolAnnotations {
81 ToolAnnotations {
82 read_only_hint: false,
83 destructive_hint: true,
84 idempotent_hint: false,
85 }
86}
87
88pub fn usage(
91 when_to_use: &str,
92 when_not_to: &str,
93 preconditions: &[&str],
94 cost: &str,
95 example: &str,
96) -> ToolUsage {
97 ToolUsage {
98 when_to_use: Some(when_to_use.to_string()),
99 when_not_to: Some(when_not_to.to_string()),
100 preconditions: preconditions.iter().map(|s| s.to_string()).collect(),
101 cost: Some(cost.to_string()),
102 example: Some(example.to_string()),
103 tier: None,
104 }
105}
106
107pub fn native_capabilities(config: &BrainConfig) -> Vec<ToolDescriptor> {
112 let mut out = Vec::new();
113 out.extend(crate::memory::capabilities(config));
114 out.extend(crate::search::capabilities(config));
115 out.extend(crate::scheduling::capabilities(config));
116 out.extend(crate::messaging::capabilities(config));
117 out.extend(crate::net::capabilities(config));
118 out.extend(crate::security::capabilities(config));
119 out.extend(crate::logs::capabilities(config));
120 out.extend(crate::baseline::capabilities(config));
121 out
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::sync::Arc;
128
129 fn all_enabled() -> BrainConfig {
130 let mut c = BrainConfig::default();
131 c.actions.web_search.enabled = true;
132 c.actions.scheduling.enabled = true;
133 c.actions.messaging.enabled = true;
134 c
135 }
136
137 fn has(ds: &[ToolDescriptor], ns: &str, action: &str) -> bool {
138 ds.iter().any(|d| d.verb == Verb::new(ns, action))
139 }
140
141 #[test]
145 fn every_always_on_backend_declares_its_capability() {
146 let ds = native_capabilities(&all_enabled());
147 for (ns, action) in [
148 ("memory", "store"),
149 ("memory", "delete"),
150 ("net", "check"),
151 ("net", "trace"),
152 ("net", "cert"),
153 ("security", "audit"),
154 ("logs", "analyze"),
155 ("baseline", "capture"),
156 ("baseline", "diff"),
157 ("baseline", "list"),
158 ] {
159 assert!(has(&ds, ns, action), "{ns}.{action} not declared");
160 }
161 }
162
163 #[test]
165 fn config_gates_drop_disabled_backends() {
166 let mut c = BrainConfig::default();
167 c.actions.web_search.enabled = false;
168 c.actions.scheduling.enabled = false;
169 c.actions.messaging.enabled = false;
170 let ds = native_capabilities(&c);
171 assert!(
172 !has(&ds, "net", "http"),
173 "web search disabled → no net.http"
174 );
175 assert!(!ds.iter().any(|d| d.verb.namespace == "schedule"));
176 assert!(!ds.iter().any(|d| d.verb.namespace == "notify"));
177 assert!(has(&ds, "net", "check"));
179 assert!(has(&ds, "memory", "store"));
180 }
181
182 #[test]
185 fn declared_tool_ids_are_unique() {
186 let ds = native_capabilities(&all_enabled());
187 let mut ids: Vec<&str> = ds.iter().map(|d| d.tool_id.as_str()).collect();
188 ids.sort_unstable();
189 let before = ids.len();
190 ids.dedup();
191 assert_eq!(before, ids.len(), "duplicate tool_id in native manifest");
192 }
193
194 #[tokio::test]
198 async fn declared_capabilities_register_and_are_retrievable() {
199 let ds = native_capabilities(&all_enabled());
200 let registry: Arc<dyn intent::ToolRegistry> = Arc::new(intent::InMemoryToolRegistry::new());
201 for d in &ds {
202 registry.register(d.clone()).await.unwrap();
203 }
204 assert_eq!(registry.list().await.len(), ds.len());
205 for d in &ds {
206 assert!(
207 registry.get(&d.tool_id).await.is_some(),
208 "{} not retrievable after registration",
209 d.tool_id
210 );
211 }
212 }
213}