Skip to main content

bitrouter_runtime/
app.rs

1use std::path::PathBuf;
2
3use bitrouter_config::BitrouterConfig;
4use bitrouter_core::routers::routing_table::RoutingTable;
5
6use crate::{control::ControlClient, error::Result, paths::RuntimePaths};
7
8pub struct AppRuntime<R> {
9    config: BitrouterConfig,
10    paths: RuntimePaths,
11    routing_table: R,
12}
13
14impl<R: RoutingTable + Send + Sync + 'static> AppRuntime<R> {
15    pub fn new(config: BitrouterConfig, paths: RuntimePaths, routing_table: R) -> Self {
16        Self {
17            config,
18            paths,
19            routing_table,
20        }
21    }
22
23    pub fn paths(&self) -> &RuntimePaths {
24        &self.paths
25    }
26
27    pub fn config(&self) -> &BitrouterConfig {
28        &self.config
29    }
30
31    pub fn routing_table(&self) -> &R {
32        &self.routing_table
33    }
34
35    pub fn control_client(&self) -> ControlClient {
36        ControlClient::new(self.paths.clone())
37    }
38
39    pub fn status(&self) -> RuntimeStatus {
40        let daemon_pid = crate::daemon::DaemonManager::new(self.paths.clone())
41            .is_running()
42            .ok()
43            .flatten();
44        RuntimeStatus {
45            home_dir: self.paths.home_dir.clone(),
46            config_file: self.paths.config_file.clone(),
47            runtime_dir: self.paths.runtime_dir.clone(),
48            listen_addr: self.config.server.listen,
49            providers: self.config.providers.keys().cloned().collect(),
50            models: self.config.models.keys().cloned().collect(),
51            daemon_pid,
52        }
53    }
54
55    pub async fn serve<M>(self, model_router: M) -> Result<()>
56    where
57        M: bitrouter_core::routers::model_router::LanguageModelRouter + Send + Sync + 'static,
58    {
59        use crate::server::ServerPlan;
60        use std::sync::Arc;
61        ServerPlan::new(
62            self.config,
63            Arc::new(self.routing_table),
64            Arc::new(model_router),
65        )
66        .serve()
67        .await
68    }
69
70    pub async fn start(&self) -> Result<()> {
71        let dm = crate::daemon::DaemonManager::new(self.paths.clone());
72        let pid = dm.start().await?;
73        println!("bitrouter daemon started (pid {pid})");
74        Ok(())
75    }
76
77    pub async fn stop(&self) -> Result<()> {
78        let dm = crate::daemon::DaemonManager::new(self.paths.clone());
79        dm.stop().await?;
80        println!("bitrouter daemon stopped");
81        Ok(())
82    }
83
84    pub async fn restart(&self) -> Result<()> {
85        let dm = crate::daemon::DaemonManager::new(self.paths.clone());
86        let pid = dm.restart().await?;
87        println!("bitrouter daemon restarted (pid {pid})");
88        Ok(())
89    }
90}
91
92/// Convenience constructors for the default `ConfigRoutingTable`.
93impl AppRuntime<bitrouter_config::ConfigRoutingTable> {
94    /// Load config from resolved paths. The `.env` file (if it exists) is loaded
95    /// automatically from `paths.env_file`.
96    pub fn load(paths: RuntimePaths) -> Result<Self> {
97        let env_file = paths.env_file.exists().then_some(paths.env_file.as_path());
98        let config = BitrouterConfig::load_from_file(&paths.config_file, env_file)?;
99        let routing_table = bitrouter_config::ConfigRoutingTable::new(
100            config.providers.clone(),
101            config.models.clone(),
102        );
103        Ok(Self {
104            config,
105            paths,
106            routing_table,
107        })
108    }
109
110    /// Build a runtime with default config (no file on disk).
111    ///
112    /// Loads builtin providers with env_prefix resolution so that
113    /// environment-provided API keys (e.g. `OPENAI_API_KEY`) are
114    /// automatically picked up even without a config file.
115    pub fn scaffold(paths: RuntimePaths) -> Self {
116        let env_file = paths.env_file.exists().then_some(paths.env_file.as_path());
117        let config = BitrouterConfig::load_from_str("{}", env_file).unwrap_or_default();
118        let routing_table = bitrouter_config::ConfigRoutingTable::new(
119            config.providers.clone(),
120            config.models.clone(),
121        );
122        Self {
123            config,
124            paths,
125            routing_table,
126        }
127    }
128}
129
130#[derive(Debug, Clone)]
131pub struct RuntimeStatus {
132    pub home_dir: PathBuf,
133    pub config_file: PathBuf,
134    pub runtime_dir: PathBuf,
135    pub listen_addr: std::net::SocketAddr,
136    pub providers: Vec<String>,
137    pub models: Vec<String>,
138    /// PID of the running daemon, or `None` if no daemon is active.
139    pub daemon_pid: Option<u32>,
140}