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