1use std::collections::BTreeSet;
10use std::sync::Arc;
11
12use tokio::sync::{mpsc, RwLock};
13use tokio_util::sync::CancellationToken;
14
15use crate::metrics::MetricsCollector;
16use crate::skills::SkillManager;
17use bamboo_agent_core::storage::{AttachmentReader, Storage};
18use bamboo_agent_core::tools::ToolExecutor;
19use bamboo_agent_core::{AgentEvent, Role, Session};
20use bamboo_domain::ReasoningEffort;
21use bamboo_infrastructure::config::PermissionMode;
22use bamboo_infrastructure::Config;
23use bamboo_infrastructure::LLMProvider;
24
25use crate::runtime::config::{AgentLoopConfig, ImageFallbackConfig, PromptMemoryFlags};
26use crate::runtime::hooks::HookRunner;
27use crate::runtime::managers::{
28 LifecycleManager, LlmManager, MemoryManager, PromptManager, ToolManager,
29};
30use crate::runtime::runner::run_agent_loop_with_config;
31use bamboo_domain::RuntimeSessionPersistence;
32
33#[derive(Clone)]
42pub struct AgentRuntime {
43 pub storage: Arc<dyn Storage>,
44 pub persistence: Arc<dyn RuntimeSessionPersistence>,
45 pub attachment_reader: Arc<dyn AttachmentReader>,
46 pub skill_manager: Arc<SkillManager>,
47 pub metrics_collector: MetricsCollector,
48 pub config: Arc<RwLock<Config>>,
49
50 pub provider: Arc<dyn LLMProvider>,
52
53 pub default_tools: Arc<dyn ToolExecutor>,
57
58 pub prompt_manager: Option<Arc<dyn PromptManager>>,
61 pub memory_manager: Option<Arc<dyn MemoryManager>>,
63 pub tool_manager: Option<Arc<dyn ToolManager>>,
65 pub llm_manager: Option<Arc<dyn LlmManager>>,
67 pub lifecycle_manager: Option<Arc<dyn LifecycleManager>>,
69 pub hook_runner: Option<HookRunner>,
71}
72
73pub struct AgentRuntimeBuilder {
88 storage: Option<Arc<dyn Storage>>,
89 persistence: Option<Arc<dyn RuntimeSessionPersistence>>,
90 attachment_reader: Option<Arc<dyn AttachmentReader>>,
91 skill_manager: Option<Arc<SkillManager>>,
92 metrics_collector: Option<MetricsCollector>,
93 config: Option<Arc<RwLock<Config>>>,
94 provider: Option<Arc<dyn LLMProvider>>,
95 default_tools: Option<Arc<dyn ToolExecutor>>,
96 prompt_manager: Option<Arc<dyn PromptManager>>,
97 memory_manager: Option<Arc<dyn MemoryManager>>,
98 tool_manager: Option<Arc<dyn ToolManager>>,
99 llm_manager: Option<Arc<dyn LlmManager>>,
100 lifecycle_manager: Option<Arc<dyn LifecycleManager>>,
101 hook_runner: Option<HookRunner>,
102}
103
104impl AgentRuntimeBuilder {
105 pub fn new() -> Self {
106 Self {
107 storage: None,
108 persistence: None,
109 attachment_reader: None,
110 skill_manager: None,
111 metrics_collector: None,
112 config: None,
113 provider: None,
114 default_tools: None,
115 prompt_manager: None,
116 memory_manager: None,
117 tool_manager: None,
118 llm_manager: None,
119 lifecycle_manager: None,
120 hook_runner: None,
121 }
122 }
123
124 pub fn storage(mut self, v: Arc<dyn Storage>) -> Self {
125 self.storage = Some(v);
126 self
127 }
128
129 pub fn persistence(mut self, v: Arc<dyn RuntimeSessionPersistence>) -> Self {
130 self.persistence = Some(v);
131 self
132 }
133
134 pub fn attachment_reader(mut self, v: Arc<dyn AttachmentReader>) -> Self {
135 self.attachment_reader = Some(v);
136 self
137 }
138
139 pub fn skill_manager(mut self, v: Arc<SkillManager>) -> Self {
140 self.skill_manager = Some(v);
141 self
142 }
143
144 pub fn metrics_collector(mut self, v: MetricsCollector) -> Self {
145 self.metrics_collector = Some(v);
146 self
147 }
148
149 pub fn config(mut self, v: Arc<RwLock<Config>>) -> Self {
150 self.config = Some(v);
151 self
152 }
153
154 pub fn provider(mut self, v: Arc<dyn LLMProvider>) -> Self {
155 self.provider = Some(v);
156 self
157 }
158
159 pub fn default_tools(mut self, v: Arc<dyn ToolExecutor>) -> Self {
160 self.default_tools = Some(v);
161 self
162 }
163
164 pub fn prompt_manager(mut self, v: Arc<dyn PromptManager>) -> Self {
165 self.prompt_manager = Some(v);
166 self
167 }
168
169 pub fn memory_manager(mut self, v: Arc<dyn MemoryManager>) -> Self {
170 self.memory_manager = Some(v);
171 self
172 }
173
174 pub fn tool_manager(mut self, v: Arc<dyn ToolManager>) -> Self {
175 self.tool_manager = Some(v);
176 self
177 }
178
179 pub fn llm_manager(mut self, v: Arc<dyn LlmManager>) -> Self {
180 self.llm_manager = Some(v);
181 self
182 }
183
184 pub fn lifecycle_manager(mut self, v: Arc<dyn LifecycleManager>) -> Self {
185 self.lifecycle_manager = Some(v);
186 self
187 }
188
189 pub fn hook_runner(mut self, v: HookRunner) -> Self {
190 self.hook_runner = Some(v);
191 self
192 }
193
194 pub fn build(self) -> Result<AgentRuntime, &'static str> {
195 Ok(AgentRuntime {
196 storage: self.storage.ok_or_else(|| format_missing("storage"))?,
197 persistence: self
198 .persistence
199 .ok_or_else(|| format_missing("persistence"))?,
200 attachment_reader: self
201 .attachment_reader
202 .ok_or_else(|| format_missing("attachment_reader"))?,
203 skill_manager: self
204 .skill_manager
205 .ok_or_else(|| format_missing("skill_manager"))?,
206 metrics_collector: self
207 .metrics_collector
208 .ok_or_else(|| format_missing("metrics_collector"))?,
209 config: self.config.ok_or_else(|| format_missing("config"))?,
210 provider: self.provider.ok_or_else(|| format_missing("provider"))?,
211 default_tools: self
212 .default_tools
213 .ok_or_else(|| format_missing("default_tools"))?,
214 prompt_manager: None,
215 memory_manager: None,
216 tool_manager: None,
217 llm_manager: None,
218 lifecycle_manager: None,
219 hook_runner: None,
220 })
221 }
222}
223
224fn format_missing(field: &str) -> &'static str {
225 match field {
228 "storage" => "AgentRuntimeBuilder: missing storage",
229 "persistence" => "AgentRuntimeBuilder: missing persistence",
230 "attachment_reader" => "AgentRuntimeBuilder: missing attachment_reader",
231 "skill_manager" => "AgentRuntimeBuilder: missing skill_manager",
232 "metrics_collector" => "AgentRuntimeBuilder: missing metrics_collector",
233 "config" => "AgentRuntimeBuilder: missing config",
234 "provider" => "AgentRuntimeBuilder: missing provider",
235 "default_tools" => "AgentRuntimeBuilder: missing default_tools",
236 _ => "AgentRuntimeBuilder: missing required field",
237 }
238}
239
240impl Default for AgentRuntimeBuilder {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246pub struct ExecuteRequest {
256 pub initial_message: String,
258 pub event_tx: mpsc::Sender<AgentEvent>,
259 pub cancel_token: CancellationToken,
260
261 pub tools: Option<Arc<dyn ToolExecutor>>,
265 pub provider_override: Option<Arc<dyn LLMProvider>>,
268
269 pub model: Option<String>,
271 pub provider_name: Option<String>,
272 pub provider_type: Option<String>,
273 pub fast_model: Option<String>,
275 pub fast_model_provider: Option<Arc<dyn LLMProvider>>,
277 pub background_model: Option<String>,
279 pub background_model_provider: Option<Arc<dyn LLMProvider>>,
281 pub summarization_model: Option<String>,
283 pub summarization_model_provider: Option<Arc<dyn LLMProvider>>,
285 pub reasoning_effort: Option<ReasoningEffort>,
286 pub disabled_tools: Option<BTreeSet<String>>,
288 pub disabled_skill_ids: Option<BTreeSet<String>>,
290 pub selected_skill_ids: Option<Vec<String>>,
291 pub selected_skill_mode: Option<String>,
292 pub image_fallback: Option<ImageFallbackConfig>,
293 pub app_data_dir: Option<std::path::PathBuf>,
295}
296
297fn extract_system_prompt(session: &Session) -> Option<String> {
303 session
304 .messages
305 .iter()
306 .find(|m| matches!(m.role, Role::System))
307 .map(|m| m.content.clone())
308}
309
310impl AgentRuntime {
315 pub async fn execute(
320 &self,
321 session: &mut Session,
322 req: ExecuteRequest,
323 ) -> crate::runtime::runner::Result<()> {
324 let system_prompt = extract_system_prompt(session);
325 let config = self.config.read().await;
326 let ExecuteRequest {
327 initial_message,
328 event_tx,
329 cancel_token,
330 tools,
331 provider_override,
332 model,
333 provider_name,
334 provider_type,
335 fast_model,
336 fast_model_provider,
337 background_model,
338 background_model_provider,
339 summarization_model,
340 summarization_model_provider,
341 reasoning_effort,
342 disabled_tools,
343 disabled_skill_ids,
344 selected_skill_ids,
345 selected_skill_mode,
346 image_fallback,
347 app_data_dir,
348 } = req;
349 let tools = tools.unwrap_or_else(|| self.default_tools.clone());
350 let llm = provider_override.unwrap_or_else(|| self.provider.clone());
351
352 let loop_config = AgentLoopConfig {
353 max_rounds: 200,
354 system_prompt,
355 disabled_skill_ids: disabled_skill_ids.unwrap_or_else(|| config.disabled_skill_ids()),
356 selected_skill_ids,
357 selected_skill_mode,
358 skill_manager: Some(self.skill_manager.clone()),
359 skip_initial_user_message: true,
360 storage: Some(self.storage.clone()),
361 persistence: Some(self.persistence.clone()),
362 attachment_reader: Some(self.attachment_reader.clone()),
363 metrics_collector: Some(self.metrics_collector.clone()),
364 model_name: model,
365 fast_model_name: fast_model.or_else(|| config.get_fast_model()),
366 fast_model_provider,
367 background_model_name: background_model
368 .or_else(|| config.get_memory_background_model()),
369 planning_model_name: config
370 .defaults
371 .as_ref()
372 .and_then(|d| d.planning.as_ref())
373 .map(|r| r.model.clone()),
374 search_model_name: config
375 .defaults
376 .as_ref()
377 .and_then(|d| d.search.as_ref().or(d.fast.as_ref()))
378 .map(|r| r.model.clone()),
379 compression_instructions: None,
380 summarization_model_name: summarization_model
381 .or_else(|| config.get_task_summary_model()),
382 background_model_provider,
383 summarization_model_provider,
384 provider_name: Some(provider_name.unwrap_or_else(|| config.provider.clone())),
385 provider_type,
386 reasoning_effort,
387 disabled_tools: {
388 let mut merged = config.disabled_tool_names();
389 if let Some(dt) = disabled_tools {
390 merged.extend(dt);
391 }
392 merged
393 },
394 image_fallback,
395 app_data_dir,
396 prompt_memory_flags: config
397 .memory
398 .as_ref()
399 .map(PromptMemoryFlags::from)
400 .unwrap_or_default(),
401 features_dynamic_model_routing: config.features.dynamic_model_routing,
402 permission_mode: session
403 .agent_runtime_state
404 .as_ref()
405 .and_then(|state| state.plan_mode.as_ref())
406 .map(|_| PermissionMode::Plan),
407 ..Default::default()
408 };
409
410 drop(config);
411
412 run_agent_loop_with_config(
413 session,
414 initial_message,
415 event_tx,
416 llm,
417 tools,
418 cancel_token,
419 loop_config,
420 )
421 .await
422 }
423}