use serde_json::{Value, json};
pub mod console;
pub mod core;
pub mod project;
pub mod proxy;
pub mod session;
pub const TOOL_CATALOG: [&str; 33] = [
"session_list",
"session_status",
"agent_delegate",
"memory_protect",
"circuit_breaker_status",
"hook_event",
"list_recent_errors",
"preview_bug_report",
"report_bug",
"session_new",
"session_stop",
"session_resume",
"session_decommission",
"session_delete",
"session_activity",
"session_send",
"session_decommission_ephemeral",
"session_prune",
"session_context_catchup",
"session_context_pause",
"console_metrics",
"supervisor_status",
"auto_resume_set",
"config_read",
"config_write",
"project_list",
"project_register",
"project_get",
"project_resolve",
"session_proxy_focus",
"session_proxy_unfocus",
"session_proxy_message",
"session_proxy_summary",
];
pub fn tool_catalog() -> Vec<Value> {
let mut tools = core::core_tools();
tools.extend(session::session_tools());
tools.extend(console::console_tools());
tools.extend(project::project_tools());
tools.extend(proxy::proxy_tools());
tools
}
pub(crate) fn tool(name: &str, description: &str, input_schema: Value) -> Value {
json!({
"name": name,
"description": description,
"inputSchema": input_schema,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_has_expected_tool_count() {
assert_eq!(tool_catalog().len(), 33);
assert_eq!(TOOL_CATALOG.len(), 33);
}
#[test]
fn catalog_names_match_constant() {
let names: Vec<String> = tool_catalog()
.iter()
.map(|t| t["name"].as_str().unwrap().to_string())
.collect();
assert_eq!(names, TOOL_CATALOG);
}
#[test]
fn every_tool_has_input_schema() {
for t in tool_catalog() {
assert!(t["name"].is_string());
assert!(t["description"].is_string());
assert_eq!(t["inputSchema"]["type"], "object");
}
}
#[test]
fn bug_reporting_tools_present() {
let catalog = tool_catalog();
let names: Vec<&str> = catalog.iter().filter_map(|t| t["name"].as_str()).collect();
assert!(names.contains(&"list_recent_errors"), "{names:?}");
assert!(names.contains(&"preview_bug_report"), "{names:?}");
assert!(names.contains(&"report_bug"), "{names:?}");
}
#[test]
fn session_tools_present() {
let catalog = tool_catalog();
let names: Vec<&str> = catalog.iter().filter_map(|t| t["name"].as_str()).collect();
for expected in [
"session_new",
"session_stop",
"session_resume",
"session_decommission",
"session_delete",
"session_activity",
"session_send",
"session_context_catchup",
"session_context_pause",
] {
assert!(names.contains(&expected), "missing {expected}: {names:?}");
}
}
#[test]
fn console_tools_present() {
let catalog = tool_catalog();
let names: Vec<&str> = catalog.iter().filter_map(|t| t["name"].as_str()).collect();
for expected in [
"console_metrics",
"supervisor_status",
"auto_resume_set",
"config_read",
"config_write",
] {
assert!(names.contains(&expected), "missing {expected}: {names:?}");
}
}
#[test]
fn project_tools_present() {
let catalog = tool_catalog();
let names: Vec<&str> = catalog.iter().filter_map(|t| t["name"].as_str()).collect();
for expected in [
"project_list",
"project_register",
"project_get",
"project_resolve",
] {
assert!(names.contains(&expected), "missing {expected}: {names:?}");
}
}
#[test]
fn proxy_tools_present() {
let catalog = tool_catalog();
let names: Vec<&str> = catalog.iter().filter_map(|t| t["name"].as_str()).collect();
for expected in [
"session_proxy_focus",
"session_proxy_unfocus",
"session_proxy_message",
"session_proxy_summary",
] {
assert!(names.contains(&expected), "missing {expected}: {names:?}");
}
}
#[test]
fn agent_delegate_description_clarifies_it_does_not_execute() {
let catalog = tool_catalog();
let desc = catalog
.iter()
.find(|t| t["name"] == "agent_delegate")
.and_then(|t| t["description"].as_str())
.expect("agent_delegate tool present with a description");
assert!(
desc.contains("does not spawn"),
"agent_delegate description must state it does not spawn: {desc}"
);
assert!(
desc.contains("Agent/Task tool"),
"agent_delegate description must point to the native Agent/Task tool: {desc}"
);
assert!(
!desc.contains("before spawning"),
"agent_delegate description must not claim it spawns: {desc}"
);
}
}