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