use crate::tools::handlers::PlanningWorkflowState;
use crate::tools::registry::inventory::ToolInventory;
use crate::tools::registry::registration::{ToolCatalogSource, ToolRegistration};
use crate::tools::tool_intent::builtin_tool_behavior;
#[async_trait::async_trait]
pub trait ToolPack: Send + Sync {
fn pack_id(&self) -> &'static str;
async fn register(&self, inventory: &ToolInventory, plan_state: &PlanningWorkflowState);
}
pub fn batch_register(inventory: &ToolInventory, registrations: Vec<ToolRegistration>) {
for mut registration in registrations {
let tool_name = registration.name().to_string();
if let Some(behavior) = builtin_tool_behavior(&tool_name) {
registration = registration.with_behavior(behavior);
}
registration = registration.with_catalog_source(ToolCatalogSource::Builtin);
if let Err(err) = inventory.register_tool(registration) {
tracing::warn!(tool = %tool_name, %err, "Failed to register tool from pack");
}
}
}
pub type BuiltinPackFactory = fn() -> Box<dyn ToolPack>;
#[linkme::distributed_slice]
pub static BUILTIN_PACKS: [BuiltinPackFactory] = [..];
pub async fn register_builtin_packs(inventory: &ToolInventory, plan_state: &PlanningWorkflowState) {
let mut packs: Vec<Box<dyn ToolPack>> = BUILTIN_PACKS.iter().map(|factory| factory()).collect();
packs.sort_by(|a, b| a.pack_id().cmp(b.pack_id()));
for pack in packs {
pack.register(inventory, plan_state).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::constants::tools;
use std::sync::Arc;
#[test]
fn builtin_packs_slice_is_populated() {
assert!(BUILTIN_PACKS.len() >= 8, "expected at least 8 builtin packs, found {}", BUILTIN_PACKS.len());
}
#[test]
fn pack_ids_are_unique_and_sorted() {
let mut ids: Vec<&'static str> = BUILTIN_PACKS.iter().map(|f| f().pack_id()).collect();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), BUILTIN_PACKS.len(), "pack_ids must be unique");
}
#[tokio::test]
async fn register_builtin_packs_populates_inventory() {
let temp = tempfile::tempdir().unwrap();
let inventory = ToolInventory::new(
temp.path().to_path_buf(),
Arc::new(crate::tools::edited_file_monitor::EditedFileMonitor::new()),
);
let plan_state = PlanningWorkflowState::new(temp.path().to_path_buf());
register_builtin_packs(&inventory, &plan_state).await;
assert!(inventory.has_tool(tools::CODE_SEARCH), "CODE_SEARCH should be registered");
assert!(inventory.has_tool(tools::EXEC_COMMAND), "EXEC_COMMAND should be registered");
}
}