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 background_model: Option<String>,
274 pub background_model_provider: Option<Arc<dyn LLMProvider>>,
277 pub reasoning_effort: Option<ReasoningEffort>,
278 pub disabled_tools: Option<BTreeSet<String>>,
280 pub disabled_skill_ids: Option<BTreeSet<String>>,
282 pub selected_skill_ids: Option<Vec<String>>,
283 pub selected_skill_mode: Option<String>,
284 pub image_fallback: Option<ImageFallbackConfig>,
285}
286
287fn extract_system_prompt(session: &Session) -> Option<String> {
293 session
294 .messages
295 .iter()
296 .find(|m| matches!(m.role, Role::System))
297 .map(|m| m.content.clone())
298}
299
300impl AgentRuntime {
305 pub async fn execute(
310 &self,
311 session: &mut Session,
312 req: ExecuteRequest,
313 ) -> crate::runtime::runner::Result<()> {
314 let system_prompt = extract_system_prompt(session);
315 let config = self.config.read().await;
316 let ExecuteRequest {
317 initial_message,
318 event_tx,
319 cancel_token,
320 tools,
321 provider_override,
322 model,
323 provider_name,
324 background_model,
325 background_model_provider,
326 reasoning_effort,
327 disabled_tools,
328 disabled_skill_ids,
329 selected_skill_ids,
330 selected_skill_mode,
331 image_fallback,
332 } = req;
333 let tools = tools.unwrap_or_else(|| self.default_tools.clone());
334 let llm = provider_override.unwrap_or_else(|| self.provider.clone());
335
336 let loop_config = AgentLoopConfig {
337 max_rounds: 200,
338 system_prompt,
339 disabled_skill_ids: disabled_skill_ids.unwrap_or_else(|| config.disabled_skill_ids()),
340 selected_skill_ids,
341 selected_skill_mode,
342 skill_manager: Some(self.skill_manager.clone()),
343 skip_initial_user_message: true,
344 storage: Some(self.storage.clone()),
345 persistence: Some(self.persistence.clone()),
346 attachment_reader: Some(self.attachment_reader.clone()),
347 metrics_collector: Some(self.metrics_collector.clone()),
348 model_name: model,
349 fast_model_name: background_model.clone().or_else(|| config.get_fast_model()),
350 background_model_name: background_model
351 .or_else(|| config.get_memory_background_model()),
352 background_model_provider,
353 planning_model_name: config
354 .defaults
355 .as_ref()
356 .and_then(|d| d.planning.as_ref())
357 .map(|r| r.model.clone()),
358 search_model_name: config
359 .defaults
360 .as_ref()
361 .and_then(|d| d.search.as_ref().or(d.fast.as_ref()))
362 .map(|r| r.model.clone()),
363 provider_name: Some(provider_name.unwrap_or_else(|| config.provider.clone())),
364 reasoning_effort,
365 disabled_tools: disabled_tools.unwrap_or_else(|| config.disabled_tool_names()),
366 image_fallback,
367 prompt_memory_flags: config
368 .memory
369 .as_ref()
370 .map(PromptMemoryFlags::from)
371 .unwrap_or_default(),
372 features_dynamic_model_routing: config.features.dynamic_model_routing,
373 permission_mode: session
374 .agent_runtime_state
375 .as_ref()
376 .and_then(|state| state.plan_mode.as_ref())
377 .map(|_| PermissionMode::Plan),
378 ..Default::default()
379 };
380
381 drop(config);
382
383 run_agent_loop_with_config(
384 session,
385 initial_message,
386 event_tx,
387 llm,
388 tools,
389 cancel_token,
390 loop_config,
391 )
392 .await
393 }
394}