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
45pub mod error;
46
47pub mod commands;
48
49/// The `bamboo subagent-worker` actor worker (provision via stdin, serve over WS).
50pub mod subagent_worker;
51
52/// `ClaudeCodeExecutor`: drives the official Claude Code CLI as a
53/// `ChildExecutor` (`ExecutorSpec::ClaudeCode`), sibling of `subagent_worker`.
54pub mod claude_code_executor;
55
56/// The `bamboo broker-agent serve` worker: connect to a central broker and
57/// answer Ask/Task (query/steer) for its mailbox; deployable local/Docker/remote.
58pub mod broker_agent;
59
60/// The `bamboo actor run` CLI: drive an actor from the terminal.
61pub mod actor_cli;
62
63/// The `bamboo health|status|sessions|session|stop|respond|schedules` admin
64/// CLI: a thin HTTP client over a running `bamboo serve` for operators (health
65/// probe, session list/inspect/stop/respond, schedule management).
66pub mod admin_cli;
67
68/// The `bamboo -p` headless server mode: full AppState, one-shot, resumable.
69pub mod headless;
70
71/// Shared `-m`/`--model` CLI-flag grammar (`provider:model` / bare model id),
72/// used by `-p -m`, `actor run|serve -m`, and `broker-agent spawn --model`
73/// so all three parse and validate the same string the same way (#246).
74pub mod model_spec;
75
76/// The `bamboo init` / `doctor` / `config set` onboarding CLI: configure a
77/// provider + API key and self-diagnose without the web UI (server-less).
78pub mod setup_cli;
79
80/// The `bamboo skills list` / `mcp list` read CLI: inspect the configured skill
81/// and MCP surfaces offline (straight from `<data_dir>`), no running server.
82pub mod read_cli;
83
84/// The `bamboo plugin install|list|remove|update` CLI: a thin HTTP client over
85/// a running `bamboo serve` instance's `/api/v1/plugins` routes.
86pub mod plugin_cli;
87
88// Server module is now a separate workspace crate
89pub use bamboo_server as server;
90
91// Ergonomic re-export: `bamboo_agent::tools` → `bamboo_tools` for backward compatibility.
92pub use bamboo_tools as tools;
93
94// Compatibility surface matching the PUBLISHED crate API (`bamboo_agent::core::...`).
95// `core` mirrors the infrastructure crate, plus a back-compat re-export of `paths`
96// and `ProxyAuth` — which moved out of `bamboo_infrastructure` into `bamboo_config`
97// in the 2026.6 reorg. Downstream consumers (e.g. bodhi) build against the published
98// bamboo-agent where these still live under `core::`; re-exporting them here keeps a
99// single `core::` import compiling against this local checkout too. The explicit
100// names take precedence over the glob, so there is no conflict.
101// See memory: bodhi-ci-builds-against-published-bamboo.
102pub mod core {
103    pub use bamboo_config::{paths, ProxyAuth};
104    pub use bamboo_infrastructure::*;
105}
106
107// Re-export infrastructure crate so consumers can access config, paths, encryption, etc.
108// via `bamboo_agent::infrastructure::...`
109//
110// `Agent` / `AgentBuilder` come from the ergonomic `agent` wrappers (the single
111// source of truth; resolves TD-2 — no more duplicate re-export through
112// `bamboo_engine`). The facade now lives in the leaf `bamboo-sdk` crate
113// (depends only on engine/infra/tools/agent-core/domain, never on
114// bamboo-server), and is re-exported here to keep existing public paths
115// (`bamboo_agent::agent::...`, `bamboo_agent::Agent`, ...) stable.
116pub use bamboo_infrastructure as infrastructure;
117pub use bamboo_sdk::agent;
118pub use bamboo_sdk::{Agent, AgentBuilder};
119
120// Re-export the runtime config crate so consumers can reach config, paths,
121// proxy auth, encryption, etc. via `bamboo_agent::config::...`. These moved out
122// of `bamboo_infrastructure` into the dedicated `bamboo-config` crate.
123pub use bamboo_config as config;
124
125// Re-export core Config as the primary configuration type
126pub use bamboo_config::ServerConfig;
127pub use bamboo_llm::Config;
128pub use error::{BambooError, Result};
129
130/// Main Bamboo server instance
131pub struct BambooServer {
132    config: bamboo_llm::Config,
133    data_dir: PathBuf,
134}
135
136impl BambooServer {
137    /// Create a new Bamboo server with configuration
138    pub fn new(config: bamboo_llm::Config) -> Self {
139        Self {
140            config,
141            data_dir: bamboo_config::paths::bamboo_dir(),
142        }
143    }
144
145    /// Create a new Bamboo server with an explicit data directory.
146    pub fn new_with_data_dir(config: bamboo_llm::Config, data_dir: PathBuf) -> Self {
147        Self { config, data_dir }
148    }
149
150    /// Start the HTTP server (blocking).
151    ///
152    /// Delegates to the appropriate server entrypoint based on the configuration:
153    /// - If `static_dir` is set, serves static files alongside the API (Docker mode).
154    /// - Otherwise, runs the API server with the configured bind address and port.
155    ///
156    /// This method blocks until the server shuts down.
157    pub async fn start(self) -> Result<()> {
158        bamboo_config::paths::init_bamboo_dir(self.data_dir.clone());
159
160        // v2-P1 (#181): when `server.tls` is set, terminate TLS in-process
161        // (fail-fast on bad/missing certs). When absent, the plaintext paths
162        // below are unchanged.
163        let tls = self.config.server.tls.clone();
164        let result = if self.config.server.static_dir.is_some() {
165            server::run_with_bind_and_static_tls(
166                self.data_dir,
167                self.config.server.port,
168                &self.config.server.bind,
169                self.config.server.static_dir.clone(),
170                tls,
171            )
172            .await
173        } else if self.config.server.bind == "127.0.0.1" {
174            server::run_with_tls(self.data_dir, self.config.server.port, tls).await
175        } else {
176            server::run_with_bind_tls(
177                self.data_dir,
178                self.config.server.port,
179                &self.config.server.bind,
180                tls,
181            )
182            .await
183        };
184
185        result.map_err(BambooError::HttpServer)
186    }
187
188    /// Get the server address
189    pub fn server_addr(&self) -> String {
190        self.config.server_addr()
191    }
192}
193
194/// Builder pattern for creating BambooServer
195///
196/// Provides a fluent API for configuring and instantiating a BambooServer.
197///
198/// # Example
199///
200/// ```rust,ignore
201/// use bamboo_agent::{BambooBuilder, BambooServer};
202/// use std::path::PathBuf;
203///
204/// let server = BambooBuilder::new()
205///     .port(9562)
206///     .bind("127.0.0.1")
207///     .data_dir(PathBuf::from("/path/to/bamboo-data-dir"))
208///     .build()
209///     .unwrap();
210/// ```
211pub struct BambooBuilder {
212    config: bamboo_llm::Config,
213    data_dir: PathBuf,
214    /// When `Some(debug)`, [`build`](Self::build) installs Bamboo's shared logging
215    /// policy (file + stdout). `None` leaves logging untouched. Opt-in by design:
216    /// a library must not install a global subscriber unless the host asks for it.
217    logging: Option<bool>,
218}
219
220impl BambooBuilder {
221    /// Create a new BambooBuilder with default configuration
222    pub fn new() -> Self {
223        Self {
224            config: bamboo_llm::Config::new(),
225            data_dir: bamboo_config::paths::bamboo_dir(),
226            logging: None,
227        }
228    }
229
230    /// Set the server port
231    ///
232    /// # Arguments
233    ///
234    /// * `port` - Port number to listen on
235    pub fn port(mut self, port: u16) -> Self {
236        self.config.server.port = port;
237        self
238    }
239
240    /// Set the bind address
241    ///
242    /// # Arguments
243    ///
244    /// * `addr` - IP address to bind to (e.g., "127.0.0.1", "0.0.0.0")
245    pub fn bind(mut self, addr: impl Into<String>) -> Self {
246        self.config.server.bind = addr.into();
247        self
248    }
249
250    /// Set the data directory for storing configuration and data
251    ///
252    /// # Arguments
253    ///
254    /// * `dir` - Path to the data directory
255    pub fn data_dir(mut self, dir: PathBuf) -> Self {
256        self.data_dir = dir;
257        self
258    }
259
260    /// Set the static files directory
261    ///
262    /// # Arguments
263    ///
264    /// * `dir` - Path to static files directory
265    pub fn static_dir(mut self, dir: PathBuf) -> Self {
266        self.config.server.static_dir = Some(dir);
267        self
268    }
269
270    /// Set the number of workers
271    ///
272    /// # Arguments
273    ///
274    /// * `workers` - Number of worker threads
275    pub fn workers(mut self, workers: usize) -> Self {
276        self.config.server.workers = workers;
277        self
278    }
279
280    /// Opt in to Bamboo's shared logging policy.
281    ///
282    /// When enabled, [`build`](Self::build) installs file + stdout logging rooted
283    /// at the builder's `data_dir` (daily-rotating files under `{data_dir}/logs`,
284    /// date-based retention, `RUST_LOG`-overridable level). Pass `debug = true`
285    /// (typically `cfg!(debug_assertions)`) to default to the `debug` level;
286    /// otherwise `info`.
287    ///
288    /// This is **opt-in**: by default a `BambooBuilder` never installs a global
289    /// subscriber, so embedding applications keep full control of their own
290    /// logging. Standalone or quick-start hosts can call this for a one-liner
291    /// setup. Installation is idempotent — if a subscriber is already set (e.g.
292    /// the host configured one), this is a no-op rather than an error.
293    pub fn with_default_logging(mut self, debug: bool) -> Self {
294        self.logging = Some(debug);
295        self
296    }
297
298    /// Build the BambooServer instance
299    ///
300    /// # Returns
301    ///
302    /// A Result containing the configured BambooServer or an error
303    pub fn build(self) -> Result<BambooServer> {
304        if let Some(debug) = self.logging {
305            bamboo_infrastructure::logging::init_logging_with_home(&self.data_dir, debug);
306        }
307        Ok(BambooServer::new_with_data_dir(self.config, self.data_dir))
308    }
309}
310
311impl Default for BambooBuilder {
312    fn default() -> Self {
313        Self::new()
314    }
315}