use serde::Serialize;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum StatusEvent {
Starting,
LoadingModel {
#[serde(flatten)]
phase: LoadPhase,
},
Capabilities {
backend: String,
v2: bool,
vision: bool,
audio: bool,
tools: bool,
thinking: bool,
embed: bool,
accelerator: String,
gpu_layers: u32,
},
Ready,
Restarting {
#[serde(flatten)]
phase: LoadPhase,
},
Draining,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "phase", rename_all = "snake_case")]
pub enum LoadPhase {
CheckingLocal {
path: PathBuf,
},
Download {
downloaded_bytes: u64,
total_bytes: Option<u64>,
source_url: String,
},
Verify {
path: PathBuf,
},
Quarantine {
path: PathBuf,
expected_sha256: String,
actual_sha256: String,
quarantine_path: PathBuf,
},
Mmap {
path: PathBuf,
},
KvCache {
n_ctx: u32,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn starting_serialises_as_simple_status() {
let s = serde_json::to_value(StatusEvent::Starting).unwrap();
assert_eq!(s["status"], "starting");
}
#[test]
fn ready_serialises_as_simple_status() {
let s = serde_json::to_value(StatusEvent::Ready).unwrap();
assert_eq!(s["status"], "ready");
}
#[test]
fn loading_model_download_carries_progress() {
let ev = StatusEvent::LoadingModel {
phase: LoadPhase::Download {
downloaded_bytes: 33_554_432,
total_bytes: Some(5_126_304_928),
source_url: "https://example.com/x.gguf".into(),
},
};
let s = serde_json::to_value(ev).unwrap();
assert_eq!(s["status"], "loading_model");
assert_eq!(s["phase"], "download");
assert_eq!(s["downloaded_bytes"], 33_554_432);
assert_eq!(s["total_bytes"], 5_126_304_928u64);
assert_eq!(s["source_url"], "https://example.com/x.gguf");
}
#[test]
fn loading_model_checking_local_carries_path() {
let ev = StatusEvent::LoadingModel {
phase: LoadPhase::CheckingLocal {
path: PathBuf::from("/tmp/x.gguf"),
},
};
let s = serde_json::to_value(ev).unwrap();
assert_eq!(s["status"], "loading_model");
assert_eq!(s["phase"], "checking_local");
assert!(s["path"].is_string());
}
#[test]
fn quarantine_carries_both_hashes() {
let ev = StatusEvent::LoadingModel {
phase: LoadPhase::Quarantine {
path: PathBuf::from("/tmp/x.gguf"),
expected_sha256: "aaaa".into(),
actual_sha256: "bbbb".into(),
quarantine_path: PathBuf::from("/tmp/x.gguf.quarantine.20260518T000000Z"),
},
};
let s = serde_json::to_value(ev).unwrap();
assert_eq!(s["phase"], "quarantine");
assert_eq!(s["expected_sha256"], "aaaa");
assert_eq!(s["actual_sha256"], "bbbb");
}
}