bitrouter_runtime/
server.rs1use std::sync::Arc;
2
3use bitrouter_api::router::{anthropic, openai};
4use bitrouter_config::BitrouterConfig;
5use bitrouter_core::routers::{model_router::LanguageModelRouter, routing_table::RoutingTable};
6use warp::Filter;
7
8use crate::error::Result;
9
10pub struct StubModelRouter;
16
17impl LanguageModelRouter for StubModelRouter {
18 async fn route_model(
19 &self,
20 _target: bitrouter_core::routers::routing_table::RoutingTarget,
21 ) -> bitrouter_core::errors::Result<
22 Box<bitrouter_core::models::language::language_model::DynLanguageModel<'static>>,
23 > {
24 Err(bitrouter_core::errors::BitrouterError::unsupported(
25 "runtime",
26 "model routing",
27 Some("no model router configured — configure providers to enable API endpoints".into()),
28 ))
29 }
30}
31
32pub struct ServerPlan<T, R> {
33 config: BitrouterConfig,
34 table: Arc<T>,
35 router: Arc<R>,
36}
37
38impl<T, R> ServerPlan<T, R>
39where
40 T: RoutingTable + Send + Sync + 'static,
41 R: LanguageModelRouter + Send + Sync + 'static,
42{
43 pub fn new(config: BitrouterConfig, table: Arc<T>, router: Arc<R>) -> Self {
44 Self {
45 config,
46 table,
47 router,
48 }
49 }
50
51 pub async fn serve(self) -> Result<()> {
52 let addr = self.config.server.listen;
53
54 let health = warp::path("health")
55 .and(warp::get())
56 .map(|| warp::reply::json(&serde_json::json!({ "status": "ok" })));
57
58 let chat =
59 openai::chat::filters::chat_completions_filter(self.table.clone(), self.router.clone());
60 let messages =
61 anthropic::messages::filters::messages_filter(self.table.clone(), self.router.clone());
62 let responses =
63 openai::responses::filters::responses_filter(self.table.clone(), self.router.clone());
64
65 let routes = health
66 .or(chat)
67 .or(messages)
68 .or(responses)
69 .recover(openai::chat::filters::rejection_handler)
70 .with(warp::trace::request());
71
72 let server = warp::serve(routes)
73 .bind(addr)
74 .await
75 .graceful(shutdown_signal());
76
77 tracing::info!(%addr, "server listening");
78 server.run().await;
79 tracing::info!("server stopped");
80
81 Ok(())
82 }
83}
84
85async fn shutdown_signal() {
86 let ctrl_c = tokio::signal::ctrl_c();
87
88 #[cfg(unix)]
89 {
90 let mut term =
91 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap();
92 tokio::select! {
93 _ = ctrl_c => {}
94 _ = term.recv() => {}
95 }
96 }
97
98 #[cfg(not(unix))]
99 {
100 ctrl_c.await.ok();
101 }
102
103 tracing::info!("shutdown signal received");
104}