use std::path::PathBuf;
use std::sync::Arc;
use robit_ai::config::{resolve_image_provider, resolve_profile, RobitConfig};
use crate::image_gen::ImageGenClient;
use crate::skill::{load_skills, Skill, SkillRegistry};
use crate::tool::bash::BashTool;
use crate::tool::edit::EditTool;
use crate::tool::find::FindTool;
use crate::tool::generate_image::GenerateImageTool;
use crate::tool::grep::GrepTool;
use crate::tool::load_skill::LoadSkillTool;
use crate::tool::ls::LsTool;
use crate::tool::memory::{ForgetTool, ListMemoriesTool, MemorizeTool, RecallTool};
use crate::tool::query_task::QueryTaskTool;
use crate::tool::read::ReadTool;
use crate::tool::search_history::SearchHistoryTool;
use crate::tool::write::WriteTool;
use crate::tool::ToolRegistry;
use crate::SkillLoadError;
pub struct BootstrapResult {
pub skill_registry: Arc<SkillRegistry>,
pub tool_registry: Arc<ToolRegistry>,
pub total_skills_loaded: usize,
pub skill_load_errors: Vec<SkillLoadError>,
}
pub fn bootstrap(
config: &RobitConfig,
working_dir: &PathBuf,
base_tool_names: &[&str],
) -> BootstrapResult {
let (skills, skill_load_errors) = load_all_skills(working_dir);
let total_skills_loaded = skills.len();
let filtered_skills = filter_skills_by_config(skills, config);
let skill_registry = Arc::new(SkillRegistry::new(filtered_skills, base_tool_names));
let tool_registry = Arc::new(create_tools_from_config(config, Arc::clone(&skill_registry)));
BootstrapResult {
skill_registry,
tool_registry,
total_skills_loaded,
skill_load_errors,
}
}
pub fn load_all_skills(working_dir: &PathBuf) -> (Vec<Skill>, Vec<SkillLoadError>) {
let global_skills_dir = dirs::home_dir().map(|h| h.join(".robit/skills"));
let project_skills_dir = Some(working_dir.join(".robit/skills"));
load_skills(global_skills_dir, project_skills_dir)
}
pub fn filter_skills_by_config(skills: Vec<Skill>, config: &RobitConfig) -> Vec<Skill> {
let enabled_skills = config.app.as_ref().and_then(|a| a.enabled_skills.as_ref());
match enabled_skills {
Some(list) => skills
.into_iter()
.filter(|s| list.contains(&s.frontmatter.name))
.collect(),
None => skills,
}
}
pub fn create_tools_from_config(
config: &RobitConfig,
skill_registry: Arc<SkillRegistry>,
) -> ToolRegistry {
let mut tools = ToolRegistry::new();
let context_config = config.app.as_ref().and_then(|a| a.context.as_ref());
let max_lines = context_config.and_then(|c| c.max_output_lines).unwrap_or(500);
let max_bytes = context_config
.and_then(|c| c.max_output_bytes)
.unwrap_or(51200);
let supports_images = resolve_profile(config, None)
.map(|m| m.supports_images)
.unwrap_or(false);
tools.register(ReadTool::new(max_lines, max_bytes, supports_images));
tools.register(LoadSkillTool::new(skill_registry));
tools.register(MemorizeTool::new());
tools.register(RecallTool::new());
tools.register(ForgetTool::new());
tools.register(ListMemoriesTool::new());
tools.register(SearchHistoryTool::new());
tools.register(QueryTaskTool::new());
let mut image_client = build_image_client(config);
let enabled_tools = config.app.as_ref().and_then(|a| a.enabled_tools.as_ref());
match enabled_tools {
Some(list) => {
for tool_name in list {
match tool_name.as_str() {
"read" => {} "load_skill" => {} "memorize" => {} "recall" => {} "forget" => {} "list_memories" => {} "search_history" => {} "query_task" => {} "bash" => tools.register(BashTool::new(max_bytes)),
"write" => tools.register(WriteTool::new()),
"edit" => tools.register(EditTool::new()),
"ls" => tools.register(LsTool::new()),
"find" => tools.register(FindTool::new(max_bytes)),
"grep" => tools.register(GrepTool::new(max_lines, max_bytes)),
"generate_image" => match image_client.take() {
Some(client) => tools.register(GenerateImageTool::new(client)),
None => tracing::warn!(
"generate_image listed in enabled_tools but no image_provider \
is configured, skipping"
),
},
_ => tracing::warn!("Unknown tool in enabled_tools config: {}", tool_name),
}
}
}
None => {
tools.register(BashTool::new(max_bytes));
tools.register(WriteTool::new());
tools.register(EditTool::new());
tools.register(LsTool::new());
tools.register(FindTool::new(max_bytes));
tools.register(GrepTool::new(max_lines, max_bytes));
if let Some(client) = image_client.take() {
tools.register(GenerateImageTool::new(client));
}
}
}
tools
}
fn build_image_client(config: &RobitConfig) -> Option<ImageGenClient> {
if config.image_providers.is_empty() {
return None;
}
if config.default_image_model.is_none() {
return None;
}
match resolve_image_provider(config) {
Ok(provider) => {
tracing::info!(
"Image generation provider configured: {}/{} (protocol: {:?}, mode: {:?})",
provider.provider_name,
provider.model_id,
provider.protocol,
provider.mode
);
Some(ImageGenClient::new(provider))
}
Err(e) => {
tracing::warn!("Failed to resolve image generation provider: {}", e);
None
}
}
}
pub fn log_skill_errors(errors: &[SkillLoadError]) {
for err in errors {
tracing::warn!("Skill load error: {:?}", err);
}
}