Skip to main content

bamboo_server/app_state/
builder.rs

1use super::init::{
2    build_provider_handles, build_schedule_manager, build_spawn_scheduler, init_mcp_manager,
3    init_metrics_service, init_schedule_store, init_skill_manager, init_storage,
4    load_permission_checker, spawn_runner_cleanup_task,
5};
6use super::tools::{build_base_tools, build_root_tools};
7use super::*;
8use crate::tools::OptionalSubagentModelResolver;
9use bamboo_agent_core::storage::Storage;
10
11impl AppState {
12    /// Create unified app state with direct provider access
13    ///
14    /// This eliminates the proxy pattern where we created an AgentAppState
15    /// that called back to web_service via HTTP. Now we have direct provider access.
16    ///
17    /// # Arguments
18    ///
19    /// * `bamboo_home_dir` - Bamboo home directory containing all application data.
20    ///   This is the root directory (e.g., `${HOME}/.bamboo`) that contains:
21    ///   - config.json: Configuration file
22    ///   - sessions/: Conversation history
23    ///   - skills/: Skill definitions
24    ///   - workflows/: Workflow definitions
25    ///   - cache/: Cached data
26    ///   - runtime/: Runtime files
27    ///
28    /// # Returns
29    ///
30    /// A fully initialized AppState with all components ready for use.
31    /// # Example
32    ///
33    /// ```rust,no_run
34    /// use bamboo_server::app_state::AppState;
35    /// use std::path::PathBuf;
36    ///
37    /// #[tokio::main]
38    /// async fn main() {
39    ///     let state = AppState::new(PathBuf::from("/path/to/bamboo-data-dir"))
40    ///         .await
41    ///         .expect("failed to initialize app state");
42    ///     let provider = state.get_provider().await;
43    ///     let _models = provider.list_models().await.ok();
44    /// }
45    /// ```
46    pub async fn new(bamboo_home_dir: PathBuf) -> Result<Self, AppError> {
47        // Ensure all helpers that rely on `core::paths::bamboo_dir()` see the same
48        // directory as the server runtime.
49        bamboo_infrastructure::paths::init_bamboo_dir(bamboo_home_dir.clone());
50
51        // Load config from the specified data directory
52        let config = Config::from_data_dir(Some(bamboo_home_dir.clone()));
53
54        // Create provider with direct access (no HTTP proxy)
55        let provider =
56            match bamboo_infrastructure::create_provider_with_dir(&config, bamboo_home_dir.clone())
57                .await
58            {
59                Ok(p) => p,
60                Err(e) => {
61                    // Keep the server usable for configuration/UI even when provider init fails,
62                    // but do not silently fall back to a different provider.
63                    tracing::error!("Failed to create provider: {}.", e);
64                    Arc::new(UnconfiguredProvider {
65                        message: e.to_string(),
66                    })
67                }
68            };
69
70        Self::new_with_provider(bamboo_home_dir, config, provider).await
71    }
72
73    /// Create unified app state with a specific provider
74    ///
75    /// Allows injecting a custom LLM provider instead of creating
76    /// one from configuration. Useful for testing and custom deployments.
77    ///
78    /// # Arguments
79    ///
80    /// * `bamboo_home_dir` - Bamboo home directory containing all application data
81    /// * `config` - Application configuration
82    /// * `provider` - Pre-configured LLM provider implementation
83    ///
84    /// # Returns
85    ///
86    /// A fully initialized AppState with the provided provider.
87    pub async fn new_with_provider(
88        bamboo_home_dir: PathBuf,
89        config: Config,
90        provider: Arc<dyn LLMProvider>,
91    ) -> Result<Self, AppError> {
92        let data_dir = bamboo_home_dir.clone();
93        let (session_store, storage) = init_storage(&data_dir).await?;
94        let persistence = Arc::new(LockedSessionStore::new(storage.clone()));
95
96        // In-memory session cache (shared across handlers and background jobs).
97        let sessions: Arc<RwLock<HashMap<String, bamboo_agent_core::Session>>> =
98            Arc::new(RwLock::new(HashMap::new()));
99
100        let config = Arc::new(RwLock::new(config));
101
102        let permission_checker = load_permission_checker(&bamboo_home_dir).await;
103        let mcp_manager = init_mcp_manager(config.clone());
104        let skill_manager = init_skill_manager(&data_dir).await;
105        let metrics_service = init_metrics_service(&data_dir).await?;
106
107        let startup_sessions = {
108            let entries = session_store.list_index_entries().await;
109            let mut sessions = Vec::new();
110            for entry in entries {
111                if let Some(session) = session_store.load_session(&entry.id).await.map_err(AppError::StorageError)? {
112                    sessions.push(session);
113                }
114            }
115            sessions
116        };
117        metrics_service
118            .reconcile_startup_sessions(startup_sessions, &[])
119            .await
120            .map_err(|error| {
121                AppError::InternalError(anyhow::anyhow!(
122                    "Failed to reconcile stale metrics state on startup: {error}"
123                ))
124            })?;
125
126        let agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>> =
127            Arc::new(RwLock::new(HashMap::new()));
128        spawn_runner_cleanup_task(agent_runners.clone(), None);
129
130        let process_registry = Arc::new(ProcessRegistry::new());
131        let (provider_lock, provider_handle) = build_provider_handles(provider);
132
133        // Initialize multi-provider registry (for features.provider_model_ref).
134        let config_snapshot = config.read().await;
135        let provider_registry = match bamboo_infrastructure::ProviderRegistry::from_config(
136            &config_snapshot,
137            bamboo_home_dir.clone(),
138        )
139        .await
140        {
141            Ok(registry) => Arc::new(registry),
142            Err(e) => {
143                tracing::error!("Failed to create provider registry: {}", e);
144                Arc::new(
145                    bamboo_infrastructure::ProviderRegistry::from_config(
146                        &Config::default(),
147                        bamboo_home_dir.clone(),
148                    )
149                    .await
150                    .expect("Cannot create even an empty provider registry"),
151                )
152            }
153        };
154        drop(config_snapshot);
155
156        let provider_router = Arc::new(bamboo_infrastructure::ProviderModelRouter::new(
157            provider_registry.clone(),
158        ));
159        let model_catalog = Arc::new(bamboo_infrastructure::ModelCatalogService::new(
160            provider_registry.clone(),
161        ));
162
163        let base_tools = build_base_tools(
164            config.clone(),
165            permission_checker,
166            mcp_manager.clone(),
167            skill_manager.clone(),
168            storage.clone(),
169            persistence.clone(),
170            sessions.clone(),
171            bamboo_home_dir.clone(),
172        );
173
174        // Long-lived session event senders map (UI subscriptions + background tasks).
175        let session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>> =
176            Arc::new(RwLock::new(HashMap::new()));
177
178        // Subagent profile registry: built-ins + user/project/env overrides.
179        // Loaded here (rather than further down) so we can wrap the child
180        // tool executor with `PolicyAwareToolExecutor` before it is handed
181        // to the spawn scheduler. Workspace path is intentionally `None` —
182        // the registry is a process-wide singleton; per-workspace overrides
183        // can still be picked up via `BAMBOO_SUBAGENT_PROFILES_FILE` or by
184        // resolving against `<bamboo_home_dir>/subagent_profiles.json`.
185        let subagent_profiles = crate::subagent_profiles::load_registry(&bamboo_home_dir, None)
186            .map_err(|e| {
187                crate::error::AppError::InternalError(anyhow::anyhow!(
188                    "failed to load subagent profile registry: {e}"
189                ))
190            })?;
191
192        // Child tools intentionally do not expose `SubAgent` (no nested
193        // child spawns). They are wrapped by `PolicyAwareToolExecutor` so
194        // that each child's `subagent_type` metadata is consulted to
195        // enforce its `ToolPolicy` (allow/deny/inherit) at tool-call time.
196        let child_tools: Arc<dyn bamboo_agent_core::tools::ToolExecutor> =
197            Arc::new(crate::tools::PolicyAwareToolExecutor::new(
198                base_tools.clone(),
199                subagent_profiles.clone(),
200                sessions.clone(),
201            ));
202
203        // Unified agent runtime (shared resources for all execution paths).
204        // default_tools = base_tools (builtin + MCP + memory + skills) as a safe fallback.
205        // Interactive execution paths pass an explicit tool surface override:
206        // root sessions use ToolSurface::Root; child sessions use ToolSurface::Child.
207        let agent = Arc::new(
208            bamboo_engine::Agent::builder()
209                .storage(storage.clone())
210                .persistence(persistence.clone())
211                .attachment_reader(session_store.clone())
212                .skill_manager(skill_manager.clone())
213                .metrics_collector(metrics_service.collector())
214                .config(config.clone())
215                .provider(provider_handle.clone())
216                .default_tools(base_tools.clone())
217                .build()
218                .expect("agent runtime should be fully configured"),
219        );
220
221        let child_completion_coordinator = Arc::new(
222            super::child_completion_coordinator::ChildCompletionCoordinator::new(
223                storage.clone(),
224                persistence.clone(),
225                sessions.clone(),
226                agent_runners.clone(),
227                session_event_senders.clone(),
228                agent.clone(),
229                config.clone(),
230                provider_registry.clone(),
231                provider_router.clone(),
232                data_dir.clone(),
233            ),
234        );
235
236        // Initialize sub-session spawn scheduler (async background jobs).
237        let config_snapshot = config.read().await.clone();
238        let external_runner =
239            crate::external_agents::runtime::build_external_child_runner(&config_snapshot);
240        let spawn_scheduler = build_spawn_scheduler(
241            agent.clone(),
242            child_tools,
243            sessions.clone(),
244            agent_runners.clone(),
245            session_event_senders.clone(),
246            external_runner,
247            Some(provider_router.clone()),
248            Some(child_completion_coordinator.clone()),
249            Some(data_dir.clone()),
250        );
251
252        let tools_with_task = base_tools.clone();
253
254        let schedule_store = init_schedule_store(&data_dir).await?;
255        let schedule_manager = build_schedule_manager(
256            schedule_store.clone(),
257            agent.clone(),
258            tools_with_task.clone(),
259            sessions.clone(),
260            agent_runners.clone(),
261            session_event_senders.clone(),
262            persistence.clone(),
263            config.clone(),
264            Some(data_dir.clone()),
265        );
266
267        crate::services::auto_dream::spawn_auto_dream_task(
268            crate::services::auto_dream::AutoDreamContext {
269                session_store: session_store.clone(),
270                storage: storage.clone(),
271                provider: provider_handle.clone(),
272                config: config.clone(),
273                provider_registry: provider_registry.clone(),
274            },
275        );
276
277        let config_for_resolver = config.clone();
278        let subagent_model_resolver: OptionalSubagentModelResolver = {
279            let registry = provider_registry.clone();
280            Some(Arc::new(
281                move |subagent_type: String| -> futures::future::BoxFuture<
282                    'static,
283                    Option<bamboo_domain::ProviderModelRef>,
284                > {
285                    let config_for_resolver = config_for_resolver.clone();
286                    let registry = registry.clone();
287                    Box::pin(async move {
288                        let config_snap = config_for_resolver.read().await.clone();
289                        crate::model_config_helper::resolve_subagent_model_ref(
290                            &config_snap,
291                            &config_snap.provider,
292                            &registry,
293                            &subagent_type,
294                        )
295                    })
296                },
297            ))
298        };
299
300        let tools = build_root_tools(
301            tools_with_task.clone(),
302            schedule_store.clone(),
303            schedule_manager.clone(),
304            session_store.clone(),
305            storage.clone(),
306            persistence.clone(),
307            spawn_scheduler.clone(),
308            sessions.clone(),
309            agent_runners.clone(),
310            session_event_senders.clone(),
311            subagent_model_resolver,
312            config.clone(),
313            subagent_profiles.clone(),
314        );
315
316        child_completion_coordinator
317            .set_root_tools(tools.clone())
318            .await;
319
320        let tool_factory =
321            crate::tools::ToolSurfaceFactory::new(base_tools, tools_with_task, tools);
322
323        Ok(Self {
324            app_data_dir: bamboo_home_dir,
325            config,
326            provider: provider_lock,
327            provider_handle,
328            sessions,
329            storage,
330            session_store,
331            persistence,
332            spawn_scheduler,
333            child_completion_coordinator,
334            schedule_store,
335            schedule_manager,
336            tool_factory,
337            subagent_profiles,
338            cancel_tokens: Arc::new(RwLock::new(HashMap::new())),
339            skill_manager,
340            mcp_manager,
341            metrics_service,
342            agent_runners,
343            session_event_senders,
344            process_registry,
345            metrics_bus: None, // Will be set by server if needed
346            agent,
347            provider_registry,
348            provider_router,
349            model_catalog,
350            title_gen_in_flight: Arc::new(dashmap::DashSet::new()),
351        })
352    }
353}