1use std::path::PathBuf;
7use std::sync::Arc;
8
9use robit_ai::config::{resolve_image_provider, resolve_profile, RobitConfig};
10
11use crate::image_gen::ImageGenClient;
12use crate::skill::{load_skills, Skill, SkillRegistry};
13use crate::tool::bash::BashTool;
14use crate::tool::edit::EditTool;
15use crate::tool::find::FindTool;
16use crate::tool::generate_image::GenerateImageTool;
17use crate::tool::grep::GrepTool;
18use crate::tool::load_skill::LoadSkillTool;
19use crate::tool::ls::LsTool;
20use crate::tool::memory::{ForgetTool, ListMemoriesTool, MemorizeTool, RecallTool};
21use crate::tool::query_task::QueryTaskTool;
22use crate::tool::read::ReadTool;
23use crate::tool::search_history::SearchHistoryTool;
24use crate::tool::write::WriteTool;
25use crate::tool::ToolRegistry;
26use crate::SkillLoadError;
27
28pub struct BootstrapResult {
34 pub skill_registry: Arc<SkillRegistry>,
36 pub tool_registry: Arc<ToolRegistry>,
38 pub total_skills_loaded: usize,
40 pub skill_load_errors: Vec<SkillLoadError>,
42}
43
44pub fn bootstrap(
58 config: &RobitConfig,
59 working_dir: &PathBuf,
60 base_tool_names: &[&str],
61) -> BootstrapResult {
62 let (skills, skill_load_errors) = load_all_skills(working_dir);
63 let total_skills_loaded = skills.len();
64
65 let filtered_skills = filter_skills_by_config(skills, config);
66
67 let skill_registry = Arc::new(SkillRegistry::new(filtered_skills, base_tool_names));
68 let tool_registry = Arc::new(create_tools_from_config(config, Arc::clone(&skill_registry)));
69
70 BootstrapResult {
71 skill_registry,
72 tool_registry,
73 total_skills_loaded,
74 skill_load_errors,
75 }
76}
77
78pub fn load_all_skills(working_dir: &PathBuf) -> (Vec<Skill>, Vec<SkillLoadError>) {
82 let global_skills_dir = dirs::home_dir().map(|h| h.join(".robit/skills"));
83 let project_skills_dir = Some(working_dir.join(".robit/skills"));
84
85 load_skills(global_skills_dir, project_skills_dir)
86}
87
88pub fn filter_skills_by_config(skills: Vec<Skill>, config: &RobitConfig) -> Vec<Skill> {
90 let enabled_skills = config.app.as_ref().and_then(|a| a.enabled_skills.as_ref());
91
92 match enabled_skills {
93 Some(list) => skills
94 .into_iter()
95 .filter(|s| list.contains(&s.frontmatter.name))
96 .collect(),
97 None => skills,
98 }
99}
100
101pub fn create_tools_from_config(
107 config: &RobitConfig,
108 skill_registry: Arc<SkillRegistry>,
109) -> ToolRegistry {
110 let mut tools = ToolRegistry::new();
111 let context_config = config.app.as_ref().and_then(|a| a.context.as_ref());
112 let max_lines = context_config.and_then(|c| c.max_output_lines).unwrap_or(500);
113 let max_bytes = context_config
114 .and_then(|c| c.max_output_bytes)
115 .unwrap_or(51200);
116
117 let supports_images = resolve_profile(config, None)
121 .map(|m| m.supports_images)
122 .unwrap_or(false);
123
124 tools.register(ReadTool::new(max_lines, max_bytes, supports_images));
127 tools.register(LoadSkillTool::new(skill_registry));
128 tools.register(MemorizeTool::new());
129 tools.register(RecallTool::new());
130 tools.register(ForgetTool::new());
131 tools.register(ListMemoriesTool::new());
132 tools.register(SearchHistoryTool::new());
133 tools.register(QueryTaskTool::new());
134
135 let mut image_client = build_image_client(config);
138
139 let enabled_tools = config.app.as_ref().and_then(|a| a.enabled_tools.as_ref());
141
142 match enabled_tools {
143 Some(list) => {
144 for tool_name in list {
146 match tool_name.as_str() {
147 "read" => {} "load_skill" => {} "memorize" => {} "recall" => {} "forget" => {} "list_memories" => {} "search_history" => {} "query_task" => {} "bash" => tools.register(BashTool::new(max_bytes)),
156 "write" => tools.register(WriteTool::new()),
157 "edit" => tools.register(EditTool::new()),
158 "ls" => tools.register(LsTool::new()),
159 "find" => tools.register(FindTool::new(max_bytes)),
160 "grep" => tools.register(GrepTool::new(max_lines, max_bytes)),
161 "generate_image" => match image_client.take() {
162 Some(client) => tools.register(GenerateImageTool::new(client)),
163 None => tracing::warn!(
164 "generate_image listed in enabled_tools but no image_provider \
165 is configured, skipping"
166 ),
167 },
168 _ => tracing::warn!("Unknown tool in enabled_tools config: {}", tool_name),
169 }
170 }
171 }
172 None => {
173 tools.register(BashTool::new(max_bytes));
175 tools.register(WriteTool::new());
176 tools.register(EditTool::new());
177 tools.register(LsTool::new());
178 tools.register(FindTool::new(max_bytes));
179 tools.register(GrepTool::new(max_lines, max_bytes));
180 if let Some(client) = image_client.take() {
181 tools.register(GenerateImageTool::new(client));
182 }
183 }
184 }
185
186 tools
187}
188
189fn build_image_client(config: &RobitConfig) -> Option<ImageGenClient> {
197 if config.image_providers.is_empty() {
198 return None;
199 }
200 if config.default_image_model.is_none() {
203 return None;
204 }
205 match resolve_image_provider(config) {
206 Ok(provider) => {
207 tracing::info!(
208 "Image generation provider configured: {}/{} (protocol: {:?}, mode: {:?})",
209 provider.provider_name,
210 provider.model_id,
211 provider.protocol,
212 provider.mode
213 );
214 Some(ImageGenClient::new(provider))
215 }
216 Err(e) => {
217 tracing::warn!("Failed to resolve image generation provider: {}", e);
218 None
219 }
220 }
221}
222
223pub fn log_skill_errors(errors: &[SkillLoadError]) {
227 for err in errors {
228 tracing::warn!("Skill load error: {:?}", err);
229 }
230}