pub mod api;
pub mod dashboard;
pub mod openai;
pub mod streaming;
use axum::{Json, Router, routing::get};
use axum::extract::State;
use cortiq_engine::{CortiqRuntime, Pipeline};
use std::sync::Arc;
use tokio::sync::{Mutex, OwnedMutexGuard, OwnedSemaphorePermit, Semaphore};
use tower_http::cors::CorsLayer;
pub struct PipelinePool {
slots: Vec<Arc<Mutex<Pipeline>>>,
devices: Vec<usize>,
sem: Arc<Semaphore>,
}
pub struct SlotGuard {
pub pipe: OwnedMutexGuard<Pipeline>,
pub device: usize,
_permit: OwnedSemaphorePermit,
}
impl PipelinePool {
pub fn new(pipelines: Vec<Pipeline>) -> Self {
let n = pipelines.len();
Self::with_devices(pipelines, vec![cortiq_engine::gpu::default_device(); n])
}
pub fn with_devices(pipelines: Vec<Pipeline>, devices: Vec<usize>) -> Self {
assert!(
!pipelines.is_empty(),
"pipeline pool needs at least one slot"
);
assert_eq!(
pipelines.len(),
devices.len(),
"one device per slot: {} pipelines, {} devices",
pipelines.len(),
devices.len()
);
let sem = Arc::new(Semaphore::new(pipelines.len()));
Self {
slots: pipelines
.into_iter()
.map(|p| Arc::new(Mutex::new(p)))
.collect(),
devices,
sem,
}
}
pub fn n_slots(&self) -> usize {
self.slots.len()
}
pub async fn acquire(&self) -> SlotGuard {
let permit = self
.sem
.clone()
.acquire_owned()
.await
.expect("slot semaphore closed");
for (i, s) in self.slots.iter().enumerate() {
if let Ok(pipe) = s.clone().try_lock_owned() {
let device = self.devices[i];
cortiq_engine::gpu::set_current_device(device);
return SlotGuard {
pipe,
device,
_permit: permit,
};
}
}
unreachable!("semaphore permit held but every slot is locked")
}
}
pub struct AppState {
pub runtime: CortiqRuntime,
pub tokenizer: Arc<cortiq_engine::tokenizer::Tokenizer>,
pub slots: PipelinePool,
pub remote: Option<Arc<std::sync::Mutex<cortiq_net::RemoteSegment>>>,
}
async fn healthz(State(st): State<Arc<AppState>>) -> Json<serde_json::Value> {
let tools = st
.tokenizer
.chat_template
.as_deref()
.map(|t| t.contains("tool"))
.unwrap_or(false);
Json(serde_json::json!({
"status": "ok",
"capabilities": { "tools": tools }
}))
}
pub fn build_router(state: Arc<AppState>) -> Router {
Router::new()
.route("/healthz", get(healthz))
.merge(openai::routes())
.merge(api::routes())
.merge(dashboard::routes())
.layer(CorsLayer::permissive())
.with_state(state)
}