use std::sync::Arc;
use tinyagents::harness::testkit::{FakeTool, ScriptedModel};
use tinyagents::language::compiler::{compile, compile_source};
use tinyagents::language::parser::parse_str;
use tinyagents::language::types::Routing;
use tinyagents::{CapabilityRegistry, ComponentKind, TinyAgentsError};
const RETRIEVAL: &str = r#"
graph retrieval {
start search
node search {
kind model
model "default"
next END
}
}
"#;
const SUPPORT_FLOW: &str = r#"
graph support_flow {
start retrieve
channel messages append
channel summary last_write
node retrieve {
kind subgraph
model "retrieval"
next agent
}
node agent {
kind agent
model "default"
tools ["lookup_user", "create_ticket"]
routes {
tool_call -> tools
final -> END
}
}
node tools {
kind tool_executor
next agent
}
}
"#;
fn populated_registry() -> CapabilityRegistry<()> {
let mut registry: CapabilityRegistry<()> = CapabilityRegistry::new();
registry
.register_model("default", Arc::new(ScriptedModel::new(vec![])))
.expect("model registers");
registry
.register_tool(Arc::new(FakeTool::returning("lookup_user", "Ada Lovelace")))
.expect("lookup_user registers")
.register_tool(Arc::new(FakeTool::returning("create_ticket", "TICKET-1")))
.expect("create_ticket registers");
let retrieval = compile(&parse_str(RETRIEVAL).expect("retrieval parses"))
.expect("retrieval compiles")
.remove(0);
registry
.register_graph_blueprint("retrieval", retrieval)
.expect("subgraph registers");
registry
.register_reducer("append")
.expect("append reducer registers")
.register_reducer("last_write")
.expect("last_write reducer registers");
registry
}
#[test]
fn compiles_rag_source_through_a_populated_registry() {
let registry = populated_registry();
assert!(registry.has(ComponentKind::Model, "default"));
assert!(registry.has(ComponentKind::Tool, "lookup_user"));
assert!(registry.has(ComponentKind::Graph, "retrieval"));
assert!(registry.has(ComponentKind::Reducer, "append"));
assert!(registry.has(ComponentKind::Reducer, "last_write"));
let mut blueprints = compile_source(SUPPORT_FLOW, ®istry).expect("source compiles + binds");
assert_eq!(blueprints.len(), 1);
let blueprint = blueprints.remove(0);
assert_eq!(blueprint.graph_id, "support_flow");
assert_eq!(blueprint.start, "retrieve");
assert_eq!(blueprint.nodes.len(), 3);
let channels: Vec<(&str, &str)> = blueprint
.channels
.iter()
.map(|c| (c.name.as_str(), c.reducer.as_str()))
.collect();
assert_eq!(
channels,
vec![("messages", "append"), ("summary", "last_write")]
);
let retrieve = &blueprint.nodes[0];
assert_eq!(retrieve.name, "retrieve");
assert_eq!(retrieve.kind, "subgraph");
assert_eq!(retrieve.model.as_deref(), Some("retrieval"));
assert_eq!(retrieve.routing, Routing::Next("agent".to_string()));
let agent = &blueprint.nodes[1];
assert_eq!(agent.name, "agent");
assert_eq!(agent.kind, "agent");
assert_eq!(agent.model.as_deref(), Some("default"));
assert_eq!(agent.tools, vec!["lookup_user", "create_ticket"]);
match &agent.routing {
Routing::Conditional(routes) => {
assert_eq!(routes.len(), 2);
assert_eq!(routes[0], ("tool_call".to_string(), "tools".to_string()));
assert_eq!(routes[1], ("final".to_string(), "END".to_string()));
}
other => panic!("expected conditional routing, got {other:?}"),
}
let tools = &blueprint.nodes[2];
assert_eq!(tools.name, "tools");
assert_eq!(tools.kind, "tool_executor");
assert_eq!(tools.routing, Routing::Next("agent".to_string()));
}
#[test]
fn unregistered_capability_is_rejected() {
let registry = populated_registry();
const UNREGISTERED_TOOL: &str = r#"
graph bad_flow {
start agent
node agent {
kind agent
model "default"
tools ["delete_database"]
next END
}
}
"#;
let err = compile_source(UNREGISTERED_TOOL, ®istry)
.expect_err("unregistered tool must be rejected");
match err {
TinyAgentsError::Capability(msg) => {
assert!(
msg.contains("delete_database"),
"error should name the missing capability: {msg}"
);
}
other => panic!("expected Capability error, got {other:?}"),
}
const UNREGISTERED_MODEL: &str = r#"
graph bad_model {
start agent
node agent {
kind agent
model "ghost_model"
next END
}
}
"#;
let err = compile_source(UNREGISTERED_MODEL, ®istry)
.expect_err("unregistered model must be rejected");
assert!(matches!(err, TinyAgentsError::Capability(_)));
}