use crate::admin::keys::VirtualKeyMeta;
use crate::backend::BackendClient;
use crate::config::ModelMapping;
use crate::metrics::Metrics;
use dashmap::DashMap;
use indexmap::IndexMap;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
use tokio::sync::broadcast;
pub type LogReloadFn = Arc<dyn Fn(&str) -> bool + Send + Sync>;
#[derive(Clone)]
pub struct SharedState {
pub db: Arc<Mutex<rusqlite::Connection>>,
pub events_tx: broadcast::Sender<AdminEvent>,
pub runtime_config: Arc<RwLock<RuntimeConfig>>,
pub backend_metrics: Arc<HashMap<String, Metrics>>,
pub log_tx: tokio::sync::mpsc::Sender<RequestLogEntry>,
pub log_reload: Option<LogReloadFn>,
pub config_write_lock: Arc<tokio::sync::Mutex<()>>,
pub virtual_keys: Arc<DashMap<[u8; 32], VirtualKeyMeta>>,
pub hmac_secret: Arc<Vec<u8>>,
pub model_router: Option<Arc<RwLock<crate::config::model_router::ModelRouter>>>,
pub mcp_manager: Option<Arc<crate::tools::McpServerManager>>,
pub issued_csrf_tokens: Arc<moka::sync::Cache<String, ()>>,
pub started_at: std::time::SystemTime,
pub managed_backends:
Arc<RwLock<HashMap<String, (crate::admin::db::ManagedBackendRow, BackendClient)>>>,
}
pub async fn with_db<F, T>(db: &Arc<Mutex<rusqlite::Connection>>, f: F) -> Option<T>
where
F: FnOnce(&rusqlite::Connection) -> T + Send + 'static,
T: Send + 'static,
{
let db = db.clone();
tokio::task::spawn_blocking(move || {
let conn = db.lock().unwrap_or_else(|e| e.into_inner());
f(&conn)
})
.await
.ok()
}
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub model_mappings: IndexMap<String, ModelMapping>,
pub log_level: String,
pub log_bodies: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "data")]
pub enum AdminEvent {
#[serde(rename = "request_completed")]
RequestCompleted(RequestLogEntry),
#[serde(rename = "metrics_snapshot")]
MetricsSnapshot(MetricsSnapshotData),
#[serde(rename = "config_changed")]
ConfigChanged { key: String, value: String },
#[serde(rename = "backend_health_changed")]
BackendHealthChanged {
backend: String,
status: String,
latency_ms: Option<u64>,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RequestLogEntry {
pub request_id: String,
pub timestamp: String,
pub backend: String,
pub model_requested: Option<String>,
pub model_mapped: Option<String>,
pub status_code: u16,
pub latency_ms: u64,
pub input_tokens: Option<u64>,
pub output_tokens: Option<u64>,
pub is_streaming: bool,
pub error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
}
impl SharedState {
pub fn new_for_test() -> Self {
let conn = rusqlite::Connection::open_in_memory().expect("in-memory sqlite");
crate::admin::db::init_db(&conn).expect("init_db");
let hmac_secret = crate::admin::db::ensure_hmac_secret(&conn);
let (events_tx, _) = broadcast::channel(4);
let (log_tx, _) = tokio::sync::mpsc::channel(4);
Self {
db: Arc::new(Mutex::new(conn)),
events_tx,
runtime_config: Arc::new(RwLock::new(RuntimeConfig {
model_mappings: IndexMap::new(),
log_level: "info".to_string(),
log_bodies: false,
})),
backend_metrics: Arc::new(HashMap::new()),
log_tx,
log_reload: None,
config_write_lock: Arc::new(tokio::sync::Mutex::new(())),
virtual_keys: Arc::new(DashMap::new()),
hmac_secret: Arc::new(hmac_secret),
model_router: None,
mcp_manager: None,
issued_csrf_tokens: Arc::new(
moka::sync::Cache::builder()
.max_capacity(1_000)
.time_to_live(Duration::from_secs(86400))
.build(),
),
started_at: std::time::SystemTime::now(),
managed_backends: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct MetricsSnapshotData {
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub requests_per_minute: f64,
pub p50_latency_ms: Option<u64>,
pub p95_latency_ms: Option<u64>,
pub error_rate: f64,
pub streams_started: u64,
pub streams_completed: u64,
pub streams_failed: u64,
pub streams_client_disconnected: u64,
}