1use std::sync::Arc;
6
7use crate::gate::GateHealthRegistry;
8use crate::provider::ProviderRegistry;
9use crate::{AppState, ProxyConfig, app, store};
10
11pub fn init_tracing() {
14 tracing_subscriber::fmt()
15 .with_env_filter(
16 tracing_subscriber::EnvFilter::try_from_default_env()
17 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
18 )
19 .init();
20}
21
22#[must_use]
25pub fn build_gate_health(config: &ProxyConfig) -> GateHealthRegistry {
26 let mut gate_health = GateHealthRegistry::new();
27 if let Some(routing) = config.routing.as_ref() {
28 let mut seen = std::collections::HashSet::new();
29 for route in &routing.routes {
30 for gate in route.gates.iter().chain(&route.deferred_gates) {
31 if seen.insert(gate.clone()) {
32 gate_health = gate_health.with_budget(gate.clone(), 50, 0.25);
33 }
34 }
35 }
36 }
37 gate_health
38}
39
40pub async fn serve(config: ProxyConfig) -> Result<(), Box<dyn std::error::Error>> {
46 let (traces, writer) = store::open(&config.db_path)?;
47 let bind = config.bind.clone();
48 let providers = ProviderRegistry::new(&config.upstream_anthropic, &config.upstream_openai);
49 let gate_health = build_gate_health(&config);
50
51 let state = AppState {
52 config: Arc::new(config),
53 http: reqwest::Client::builder()
57 .connect_timeout(std::time::Duration::from_secs(10))
58 .build()?,
59 providers,
60 gate_health: Arc::new(gate_health),
61 traces,
62 };
63
64 let listener = tokio::net::TcpListener::bind(&bind).await?;
65 tracing::info!(%bind, "firstpass listening");
66 tracing::info!("offboard: unset ANTHROPIC_BASE_URL");
67
68 axum::serve(listener, app(state)?)
69 .with_graceful_shutdown(shutdown_signal())
70 .await?;
71
72 drop(writer);
75 Ok(())
76}
77
78async fn shutdown_signal() {
79 if let Err(err) = tokio::signal::ctrl_c().await {
80 tracing::warn!(%err, "failed to install Ctrl-C handler; shutting down anyway");
81 }
82}