Skip to main content

bamboo_agent/
lib.rs

1//! Bamboo - A fully self-contained AI agent backend framework
2//!
3//! Bamboo provides a complete backend system for AI agents, including:
4//! - Built-in HTTP/HTTPS server (Actix-web)
5//! - Agent execution loop with tool support
6//! - LLM provider integrations (OpenAI, Anthropic, Google Gemini, GitHub Copilot)
7//! - Session management and persistence
8//! - Workflow and slash command systems
9//! - Process management for external tools
10//!
11//! # Features
12//!
13//! - **Dual mode**: Binary (standalone server) or library (embedded)
14//! - **Unified directory**: All data in the Bamboo data directory (default `${HOME}/.bamboo`)
15//! - **Production-ready**: Built-in CORS, rate limiting, security headers
16//!
17//! # Quick Start
18//!
19//! ## Binary Mode
20
21// Allow some clippy lints that are pre-existing
22#![allow(clippy::module_inception)]
23#![allow(clippy::doc_overindented_list_items)]
24#![allow(clippy::incompatible_msrv)]
25//!
26//! ```bash
27//! bamboo serve --port 9562 --data-dir "$HOME/.bamboo"
28//! ```
29//!
30//! ## Library Mode
31//!
32//! ```rust,ignore
33//! use bamboo_agent::{BambooServer, Config};
34//!
35//! #[tokio::main]
36//! async fn main() {
37//!     let config = Config::new();
38//!     let server = BambooServer::new(config);
39//!     server.start().await.unwrap();
40//! }
41//! ```
42
43use std::path::PathBuf;
44
45#[cfg(test)]
46pub(crate) mod test_support {
47    use std::sync::{Mutex, MutexGuard, OnceLock};
48
49    pub(crate) fn env_cache_lock() -> &'static Mutex<()> {
50        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
51        LOCK.get_or_init(|| Mutex::new(()))
52    }
53
54    pub(crate) fn env_cache_lock_acquire() -> MutexGuard<'static, ()> {
55        env_cache_lock()
56            .lock()
57            .unwrap_or_else(|poisoned| poisoned.into_inner())
58    }
59}
60
61pub mod error;
62
63// Placeholder modules (will be populated during migration)
64pub mod agent;
65pub mod commands;
66
67// Server module is now a separate workspace crate
68pub use bamboo_server as server;
69
70// Ergonomic re-export: `bamboo_agent::tools` → `bamboo_tools` for backward compatibility.
71pub use bamboo_tools as tools;
72
73// Compatibility re-export matching the published crate API (`bamboo_agent::core::...`).
74pub use bamboo_infrastructure as core;
75
76// Re-export infrastructure crate so consumers can access config, paths, encryption, etc.
77// via `bamboo_agent::infrastructure::...`
78pub use agent::{Agent, AgentBuilder};
79pub use bamboo_infrastructure as infrastructure;
80
81// Re-export core Config as the primary configuration type
82pub use bamboo_infrastructure::config::ServerConfig;
83pub use bamboo_infrastructure::Config;
84pub use error::{BambooError, Result};
85
86/// Main Bamboo server instance
87pub struct BambooServer {
88    config: bamboo_infrastructure::Config,
89    data_dir: PathBuf,
90}
91
92impl BambooServer {
93    /// Create a new Bamboo server with configuration
94    pub fn new(config: bamboo_infrastructure::Config) -> Self {
95        Self {
96            config,
97            data_dir: bamboo_infrastructure::paths::bamboo_dir(),
98        }
99    }
100
101    /// Create a new Bamboo server with an explicit data directory.
102    pub fn new_with_data_dir(config: bamboo_infrastructure::Config, data_dir: PathBuf) -> Self {
103        Self { config, data_dir }
104    }
105
106    /// Start the HTTP server (blocking).
107    ///
108    /// Delegates to the appropriate server entrypoint based on the configuration:
109    /// - If `static_dir` is set, serves static files alongside the API (Docker mode).
110    /// - Otherwise, runs the API server with the configured bind address and port.
111    ///
112    /// This method blocks until the server shuts down.
113    pub async fn start(self) -> Result<()> {
114        bamboo_infrastructure::paths::init_bamboo_dir(self.data_dir.clone());
115
116        let result = if self.config.server.static_dir.is_some() {
117            server::run_with_bind_and_static(
118                self.data_dir,
119                self.config.server.port,
120                &self.config.server.bind,
121                self.config.server.static_dir.clone(),
122            )
123            .await
124        } else if self.config.server.bind == "127.0.0.1" {
125            server::run(self.data_dir, self.config.server.port).await
126        } else {
127            server::run_with_bind(
128                self.data_dir,
129                self.config.server.port,
130                &self.config.server.bind,
131            )
132            .await
133        };
134
135        result.map_err(BambooError::HttpServer)
136    }
137
138    /// Get the server address
139    pub fn server_addr(&self) -> String {
140        self.config.server_addr()
141    }
142}
143
144/// Builder pattern for creating BambooServer
145///
146/// Provides a fluent API for configuring and instantiating a BambooServer.
147///
148/// # Example
149///
150/// ```rust,ignore
151/// use bamboo_agent::{BambooBuilder, BambooServer};
152/// use std::path::PathBuf;
153///
154/// let server = BambooBuilder::new()
155///     .port(9562)
156///     .bind("127.0.0.1")
157///     .data_dir(PathBuf::from("/path/to/bamboo-data-dir"))
158///     .build()
159///     .unwrap();
160/// ```
161pub struct BambooBuilder {
162    config: bamboo_infrastructure::Config,
163    data_dir: PathBuf,
164}
165
166impl BambooBuilder {
167    /// Create a new BambooBuilder with default configuration
168    pub fn new() -> Self {
169        Self {
170            config: bamboo_infrastructure::Config::new(),
171            data_dir: bamboo_infrastructure::paths::bamboo_dir(),
172        }
173    }
174
175    /// Set the server port
176    ///
177    /// # Arguments
178    ///
179    /// * `port` - Port number to listen on
180    pub fn port(mut self, port: u16) -> Self {
181        self.config.server.port = port;
182        self
183    }
184
185    /// Set the bind address
186    ///
187    /// # Arguments
188    ///
189    /// * `addr` - IP address to bind to (e.g., "127.0.0.1", "0.0.0.0")
190    pub fn bind(mut self, addr: impl Into<String>) -> Self {
191        self.config.server.bind = addr.into();
192        self
193    }
194
195    /// Set the data directory for storing configuration and data
196    ///
197    /// # Arguments
198    ///
199    /// * `dir` - Path to the data directory
200    pub fn data_dir(mut self, dir: PathBuf) -> Self {
201        self.data_dir = dir;
202        self
203    }
204
205    /// Set the static files directory
206    ///
207    /// # Arguments
208    ///
209    /// * `dir` - Path to static files directory
210    pub fn static_dir(mut self, dir: PathBuf) -> Self {
211        self.config.server.static_dir = Some(dir);
212        self
213    }
214
215    /// Set the number of workers
216    ///
217    /// # Arguments
218    ///
219    /// * `workers` - Number of worker threads
220    pub fn workers(mut self, workers: usize) -> Self {
221        self.config.server.workers = workers;
222        self
223    }
224
225    /// Build the BambooServer instance
226    ///
227    /// # Returns
228    ///
229    /// A Result containing the configured BambooServer or an error
230    pub fn build(self) -> Result<BambooServer> {
231        Ok(BambooServer::new_with_data_dir(self.config, self.data_dir))
232    }
233}
234
235impl Default for BambooBuilder {
236    fn default() -> Self {
237        Self::new()
238    }
239}