1use std::sync::Arc;
7
8use bamboo_agent_core::Session;
9
10use crate::runtime::{AgentRuntime, AgentRuntimeBuilder, ExecuteRequest};
11use bamboo_domain::RuntimeSessionPersistence;
12
13#[derive(Clone)]
25pub struct Agent {
26 runtime: Arc<AgentRuntime>,
27}
28
29pub struct DirectExecutionLease {
35 target_session_id: String,
36 router: Option<Arc<crate::session_activation::SessionActivationRouter>>,
37 registration: Option<crate::session_activation::SessionRunRegistration>,
38}
39
40impl DirectExecutionLease {
41 pub async fn abandon(mut self) {
44 if let Some(registration) = self.registration.take() {
45 registration.abandon().await;
46 }
47 }
48}
49
50impl Agent {
51 pub fn from_runtime(runtime: Arc<AgentRuntime>) -> Self {
53 Agent { runtime }
54 }
55
56 pub fn builder() -> AgentBuilder {
58 AgentBuilder::new()
59 }
60
61 pub async fn execute(
63 &self,
64 session: &mut Session,
65 req: ExecuteRequest,
66 ) -> crate::runtime::runner::Result<()> {
67 self.runtime.execute(session, req).await
68 }
69
70 pub async fn execute_direct(
81 &self,
82 session: &mut Session,
83 req: ExecuteRequest,
84 ) -> crate::runtime::runner::Result<()> {
85 let lease = self.begin_direct_execution(&session.id).await?;
86 self.prepare_external_session_for_execution(session).await?;
87 self.execute_direct_registered(session, req, lease).await
88 }
89
90 pub async fn prepare_external_session_for_execution(
98 &self,
99 session: &mut Session,
100 ) -> crate::runtime::runner::Result<()> {
101 crate::session_app::execution_prep::prepare_external_session_for_execution(
102 session,
103 self.runtime.project_context_resolver.as_deref(),
104 )
105 .await
106 }
107
108 pub async fn prepare_external_project_assignment_read_only(
112 &self,
113 session: &mut Session,
114 ) -> crate::runtime::runner::Result<()> {
115 let resolver = self
116 .runtime
117 .project_context_resolver
118 .as_deref()
119 .ok_or_else(|| {
120 bamboo_agent_core::AgentError::ProjectContext(
121 "Project assignment requires a ProjectContextResolver".to_string(),
122 )
123 })?;
124 resolver
125 .refresh_session_prompt_read_only(session)
126 .await
127 .map(|_| ())
128 .map_err(|error| bamboo_agent_core::AgentError::ProjectContext(error.to_string()))
129 }
130
131 pub async fn begin_direct_execution(
134 &self,
135 target_session_id: &str,
136 ) -> crate::runtime::runner::Result<DirectExecutionLease> {
137 let Some(router) = self.activation_router().cloned() else {
138 return Ok(DirectExecutionLease {
139 target_session_id: target_session_id.to_string(),
140 router: None,
141 registration: None,
142 });
143 };
144 let run_id = format!("sdk-direct-{}", uuid::Uuid::new_v4());
145 let registration = router
146 .register_run(target_session_id, &run_id)
147 .await
148 .map_err(|error| bamboo_agent_core::AgentError::LLM(error.to_string()))?;
149 Ok(DirectExecutionLease {
150 target_session_id: target_session_id.to_string(),
151 router: Some(router),
152 registration: Some(registration),
153 })
154 }
155
156 pub async fn execute_direct_registered(
159 &self,
160 session: &mut Session,
161 req: ExecuteRequest,
162 mut lease: DirectExecutionLease,
163 ) -> crate::runtime::runner::Result<()> {
164 if lease.target_session_id != session.id {
165 return Err(bamboo_agent_core::AgentError::LLM(format!(
166 "direct execution lease target {} does not match session {}",
167 lease.target_session_id, session.id
168 )));
169 }
170 let Some(router) = lease.router.take() else {
171 return self.execute(session, req).await;
172 };
173 let mut registration = lease.registration.take().ok_or_else(|| {
174 bamboo_agent_core::AgentError::LLM(
175 "direct execution lease is missing its router registration".to_string(),
176 )
177 })?;
178 let result = self.execute(session, req).await;
179
180 let executed_admitted_generation = session
183 .session_inbox_admission()
184 .map_or(0, |state| state.last_admitted_sequence);
185 registration.begin_finalization().await;
186
187 let legacy_migration = crate::runtime::runner::state_bridge::migrate_legacy_pending_only(
188 session,
189 Some(self.storage()),
190 Some(self.persistence()),
191 self.session_inbox(),
192 )
193 .await;
194 if let Some(generation) = legacy_migration.highest_generation {
195 session.session_inbox_admission_mut().observe(generation);
196 }
197 let pending_generation = session
198 .session_inbox_admission()
199 .and_then(|state| state.pending_activation_generation());
200 if let Some(generation) = pending_generation {
201 let activation_ready = if let Some(inbox) = self.session_inbox() {
202 match inbox
203 .mark_activation_eligible(
204 &session.id,
205 generation,
206 bamboo_domain::SessionActivationPolicy::InterruptSpecificWait,
207 )
208 .await
209 {
210 Ok(()) => true,
211 Err(error) => {
212 tracing::error!(
213 session_id = %session.id,
214 %error,
215 "failed to persist direct SDK SessionInbox activation watermark"
216 );
217 false
218 }
219 }
220 } else {
221 false
222 };
223 if activation_ready {
224 if let Err(error) = bamboo_domain::SessionActivationPort::request_activation(
225 router.as_ref(),
226 &session.id,
227 generation,
228 )
229 .await
230 {
231 tracing::error!(
232 session_id = %session.id,
233 %error,
234 "failed to hand direct SDK SessionInbox generation to activation router"
235 );
236 }
237 }
238 }
239
240 if let Err(error) = self.persistence().checkpoint_runtime_session(session).await {
243 tracing::warn!(
244 session_id = %session.id,
245 %error,
246 "failed to checkpoint direct SDK terminal SessionInbox state"
247 );
248 }
249 if let Err(error) = registration.finish(executed_admitted_generation).await {
250 tracing::error!(
251 session_id = %session.id,
252 %error,
253 "direct SDK SessionInbox finalization failed"
254 );
255 }
256
257 result
258 }
259
260 pub fn storage(&self) -> &Arc<dyn bamboo_agent_core::storage::Storage> {
262 &self.runtime.storage
263 }
264
265 pub fn persistence(&self) -> &Arc<dyn RuntimeSessionPersistence> {
267 &self.runtime.persistence
268 }
269
270 pub fn session_inbox(&self) -> Option<&Arc<dyn bamboo_domain::SessionInboxPort>> {
271 self.runtime.session_inbox.as_ref()
272 }
273
274 pub async fn admit_session_inbox_at_safe_boundary(
279 &self,
280 session: &mut bamboo_agent_core::Session,
281 ) -> usize {
282 crate::runtime::runner::state_bridge::refresh_turn_boundary_with_inbox(
283 session,
284 Some(self.storage()),
285 Some(self.persistence()),
286 self.session_inbox(),
287 )
288 .await
289 .merged
290 }
291
292 pub fn activation_router(
293 &self,
294 ) -> Option<&Arc<crate::session_activation::SessionActivationRouter>> {
295 self.runtime.activation_router.as_ref()
296 }
297
298 pub fn session_messenger(&self) -> Option<&Arc<crate::SessionMessenger>> {
299 self.runtime.session_messenger.as_ref()
300 }
301
302 pub fn default_tools(&self) -> &Arc<dyn bamboo_agent_core::tools::ToolExecutor> {
312 &self.runtime.default_tools
313 }
314}
315
316pub struct AgentBuilder {
324 inner: AgentRuntimeBuilder,
325}
326
327impl AgentBuilder {
328 pub fn new() -> Self {
329 Self {
330 inner: AgentRuntimeBuilder::new(),
331 }
332 }
333
334 pub fn storage(mut self, v: Arc<dyn bamboo_agent_core::storage::Storage>) -> Self {
335 self.inner = self.inner.storage(v);
336 self
337 }
338
339 pub fn persistence(mut self, v: Arc<dyn RuntimeSessionPersistence>) -> Self {
340 self.inner = self.inner.persistence(v);
341 self
342 }
343
344 pub fn session_inbox(mut self, v: Arc<dyn bamboo_domain::SessionInboxPort>) -> Self {
345 self.inner = self.inner.session_inbox(v);
346 self
347 }
348
349 pub fn activation_router(
350 mut self,
351 v: Arc<crate::session_activation::SessionActivationRouter>,
352 ) -> Self {
353 self.inner = self.inner.activation_router(v);
354 self
355 }
356
357 pub fn session_messenger(mut self, v: Arc<crate::SessionMessenger>) -> Self {
358 self.inner = self.inner.session_messenger(v);
359 self
360 }
361
362 pub fn attachment_reader(
363 mut self,
364 v: Arc<dyn bamboo_agent_core::storage::AttachmentReader>,
365 ) -> Self {
366 self.inner = self.inner.attachment_reader(v);
367 self
368 }
369
370 pub fn skill_manager(mut self, v: Arc<bamboo_skills::SkillManager>) -> Self {
371 self.inner = self.inner.skill_manager(v);
372 self
373 }
374
375 pub fn project_context_resolver(
376 mut self,
377 v: Arc<crate::project_context::ProjectContextResolver>,
378 ) -> Self {
379 self.inner = self.inner.project_context_resolver(v);
380 self
381 }
382
383 pub fn metrics_collector(mut self, v: bamboo_metrics::MetricsCollector) -> Self {
384 self.inner = self.inner.metrics_collector(v);
385 self
386 }
387
388 pub fn config(mut self, v: Arc<tokio::sync::RwLock<bamboo_llm::Config>>) -> Self {
389 self.inner = self.inner.config(v);
390 self
391 }
392
393 pub fn permission_config(mut self, v: Arc<bamboo_tools::permission::PermissionConfig>) -> Self {
394 self.inner = self.inner.permission_config(v);
395 self
396 }
397
398 pub fn permission_mode(mut self, v: bamboo_domain::PermissionMode) -> Self {
399 self.inner = self.inner.permission_mode(v);
400 self
401 }
402
403 pub fn provider(mut self, v: Arc<dyn bamboo_llm::LLMProvider>) -> Self {
404 self.inner = self.inner.provider(v);
405 self
406 }
407
408 pub fn memory_store(mut self, v: bamboo_memory::memory_store::MemoryStore) -> Self {
409 self.inner = self.inner.memory_store(v);
410 self
411 }
412
413 pub fn default_tools(mut self, v: Arc<dyn bamboo_agent_core::tools::ToolExecutor>) -> Self {
414 self.inner = self.inner.default_tools(v);
415 self
416 }
417
418 pub fn hook_runner(mut self, v: Arc<crate::runtime::HookRunner>) -> Self {
420 self.inner = self.inner.hook_runner(v);
421 self
422 }
423
424 pub fn build(self) -> Result<Agent, &'static str> {
425 let runtime = self.inner.build()?;
426 Ok(Agent {
427 runtime: Arc::new(runtime),
428 })
429 }
430}
431
432impl Default for AgentBuilder {
433 fn default() -> Self {
434 Self::new()
435 }
436}