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
92impl AppRuntime<bitrouter_config::ConfigRoutingTable> {
94 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 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 pub daemon_pid: Option<u32>,
140}