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 let max_image_dimension = context_config
127 .and_then(|c| c.max_image_dimension)
128 .unwrap_or(1024);
129
130 tools.register(ReadTool::new(
133 max_lines,
134 max_bytes,
135 supports_images,
136 max_image_dimension,
137 ));
138 tools.register(LoadSkillTool::new(skill_registry));
139 tools.register(MemorizeTool::new());
140 tools.register(RecallTool::new());
141 tools.register(ForgetTool::new());
142 tools.register(ListMemoriesTool::new());
143 tools.register(SearchHistoryTool::new());
144 tools.register(QueryTaskTool::new());
145
146 let mut image_client = build_image_client(config);
149
150 let enabled_tools = config.app.as_ref().and_then(|a| a.enabled_tools.as_ref());
152
153 match enabled_tools {
154 Some(list) => {
155 for tool_name in list {
157 match tool_name.as_str() {
158 "read" => {} "load_skill" => {} "memorize" => {} "recall" => {} "forget" => {} "list_memories" => {} "search_history" => {} "query_task" => {} "bash" => tools.register(BashTool::new(max_bytes)),
167 "write" => tools.register(WriteTool::new()),
168 "edit" => tools.register(EditTool::new()),
169 "ls" => tools.register(LsTool::new()),
170 "find" => tools.register(FindTool::new(max_bytes)),
171 "grep" => tools.register(GrepTool::new(max_lines, max_bytes)),
172 "generate_image" => match image_client.take() {
173 Some(client) => tools.register(GenerateImageTool::new(client)),
174 None => tracing::warn!(
175 "generate_image listed in enabled_tools but no image_provider \
176 is configured, skipping"
177 ),
178 },
179 _ => tracing::warn!("Unknown tool in enabled_tools config: {}", tool_name),
180 }
181 }
182 }
183 None => {
184 tools.register(BashTool::new(max_bytes));
186 tools.register(WriteTool::new());
187 tools.register(EditTool::new());
188 tools.register(LsTool::new());
189 tools.register(FindTool::new(max_bytes));
190 tools.register(GrepTool::new(max_lines, max_bytes));
191 if let Some(client) = image_client.take() {
192 tools.register(GenerateImageTool::new(client));
193 }
194 }
195 }
196
197 tools
198}
199
200fn build_image_client(config: &RobitConfig) -> Option<ImageGenClient> {
208 if config.image_providers.is_empty() {
209 return None;
210 }
211 if config.default_image_model.is_none() {
214 return None;
215 }
216 match resolve_image_provider(config) {
217 Ok(provider) => {
218 tracing::info!(
219 "Image generation provider configured: {}/{} (protocol: {:?}, mode: {:?})",
220 provider.provider_name,
221 provider.model_id,
222 provider.protocol,
223 provider.mode
224 );
225 Some(ImageGenClient::new(provider))
226 }
227 Err(e) => {
228 tracing::warn!("Failed to resolve image generation provider: {}", e);
229 None
230 }
231 }
232}
233
234pub fn log_skill_errors(errors: &[SkillLoadError]) {
238 for err in errors {
239 tracing::warn!("Skill load error: {:?}", err);
240 }
241}