use brain::BrainConfig;
use intent::{verbs, BackendId, ToolAnnotations, ToolDescriptor, ToolSource, ToolUsage, Verb};
pub fn native(
ns: &str,
action: &str,
source: ToolSource,
annotations: ToolAnnotations,
mut usage: ToolUsage,
) -> ToolDescriptor {
let spec = verbs::lookup(ns, action);
let description = spec.map(|s| s.summary.to_string()).unwrap_or_default();
if usage.tier.is_none() {
usage.tier = spec.map(|s| s.tier_hint.as_str().to_string());
}
ToolDescriptor {
tool_id: format!("native:{ns}.{action}"),
source,
verb: Verb::new(ns, action),
description,
input_schema: serde_json::json!({ "type": "object" }),
output_schema: None,
capabilities: vec![format!("{ns}.{action}")],
annotations,
usage,
embedding: None,
}
}
pub fn backend(name: &str) -> ToolSource {
ToolSource::NativeBackend {
backend: BackendId::new(name),
}
}
pub fn read_only() -> ToolAnnotations {
ToolAnnotations {
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
}
}
pub fn destructive() -> ToolAnnotations {
ToolAnnotations {
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
}
}
pub fn usage(
when_to_use: &str,
when_not_to: &str,
preconditions: &[&str],
cost: &str,
example: &str,
) -> ToolUsage {
ToolUsage {
when_to_use: Some(when_to_use.to_string()),
when_not_to: Some(when_not_to.to_string()),
preconditions: preconditions.iter().map(|s| s.to_string()).collect(),
cost: Some(cost.to_string()),
example: Some(example.to_string()),
tier: None,
}
}
pub fn native_capabilities(config: &BrainConfig) -> Vec<ToolDescriptor> {
let mut out = Vec::new();
out.extend(crate::memory::capabilities(config));
out.extend(crate::search::capabilities(config));
out.extend(crate::scheduling::capabilities(config));
out.extend(crate::messaging::capabilities(config));
out.extend(crate::net::capabilities(config));
out.extend(crate::security::capabilities(config));
out.extend(crate::logs::capabilities(config));
out.extend(crate::baseline::capabilities(config));
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn all_enabled() -> BrainConfig {
let mut c = BrainConfig::default();
c.actions.web_search.enabled = true;
c.actions.scheduling.enabled = true;
c.actions.messaging.enabled = true;
c
}
fn has(ds: &[ToolDescriptor], ns: &str, action: &str) -> bool {
ds.iter().any(|d| d.verb == Verb::new(ns, action))
}
#[test]
fn every_always_on_backend_declares_its_capability() {
let ds = native_capabilities(&all_enabled());
for (ns, action) in [
("memory", "store"),
("memory", "delete"),
("net", "check"),
("net", "trace"),
("net", "cert"),
("security", "audit"),
("logs", "analyze"),
("baseline", "capture"),
("baseline", "diff"),
("baseline", "list"),
] {
assert!(has(&ds, ns, action), "{ns}.{action} not declared");
}
}
#[test]
fn config_gates_drop_disabled_backends() {
let mut c = BrainConfig::default();
c.actions.web_search.enabled = false;
c.actions.scheduling.enabled = false;
c.actions.messaging.enabled = false;
let ds = native_capabilities(&c);
assert!(
!has(&ds, "net", "http"),
"web search disabled → no net.http"
);
assert!(!ds.iter().any(|d| d.verb.namespace == "schedule"));
assert!(!ds.iter().any(|d| d.verb.namespace == "notify"));
assert!(has(&ds, "net", "check"));
assert!(has(&ds, "memory", "store"));
}
#[test]
fn declared_tool_ids_are_unique() {
let ds = native_capabilities(&all_enabled());
let mut ids: Vec<&str> = ds.iter().map(|d| d.tool_id.as_str()).collect();
ids.sort_unstable();
let before = ids.len();
ids.dedup();
assert_eq!(before, ids.len(), "duplicate tool_id in native manifest");
}
#[tokio::test]
async fn declared_capabilities_register_and_are_retrievable() {
let ds = native_capabilities(&all_enabled());
let registry: Arc<dyn intent::ToolRegistry> = Arc::new(intent::InMemoryToolRegistry::new());
for d in &ds {
registry.register(d.clone()).await.unwrap();
}
assert_eq!(registry.list().await.len(), ds.len());
for d in &ds {
assert!(
registry.get(&d.tool_id).await.is_some(),
"{} not retrievable after registration",
d.tool_id
);
}
}
}