Skip to main content

kernel/discovery/
scanner.rs

1//! The shared discovery surface: a [`StoreScanner`] inspects one on-disk model
2//! store (an Ollama blob store, a Hugging Face cache, …) and returns the models
3//! it found as [`DiscoveredModel`] hints, plus any per-store issues, in a
4//! [`ScanResult`]. Identification later turns these hints into full records.
5
6use crate::records::{Capability, ExecutionMode, Modality, ModelSource, SourceKind};
7
8/// A model a scanner found on disk, as hints (not yet a resolved record). The
9/// `*_hint` fields are best-effort; identification refines or overrides them.
10#[derive(Debug, Clone, PartialEq)]
11pub struct DiscoveredModel {
12    /// The display name (e.g. `llama3.2:latest`).
13    pub name: String,
14    /// Where the weights live and what kind of store they came from.
15    pub source: ModelSource,
16    /// The guessed modality, if the store hinted one.
17    pub modality_hint: Option<Modality>,
18    /// The guessed capabilities.
19    pub capabilities_hint: Vec<Capability>,
20    /// How the model executes (streaming vs one-shot job).
21    pub execution_hint: ExecutionMode,
22    /// The on-disk footprint in bytes.
23    pub footprint_bytes: i64,
24    /// The primary weight file, if identified.
25    pub primary_weight_path: Option<String>,
26    /// Free-text notes about this specific model.
27    pub diagnostics: Vec<String>,
28    /// A context-window hint, if the store recorded one.
29    pub context_length_hint: Option<i64>,
30    /// Whether the store recorded a chat template.
31    pub has_chat_template_hint: Option<bool>,
32    /// Whether the model's chat template supports tool calling, when the store
33    /// let it be read. `None` means undetermined.
34    pub tool_capable_hint: Option<bool>,
35    /// Stop tokens the store recorded.
36    pub stop_tokens_hint: Option<Vec<String>>,
37    /// Whether the model is still downloading (weights incomplete).
38    pub downloading: bool,
39}
40
41impl DiscoveredModel {
42    /// A discovered model with just a name and source; hints default to empty.
43    pub fn new(name: impl Into<String>, source: ModelSource) -> Self {
44        Self {
45            name: name.into(),
46            source,
47            modality_hint: None,
48            capabilities_hint: Vec::new(),
49            execution_hint: ExecutionMode::default(),
50            footprint_bytes: 0,
51            primary_weight_path: None,
52            diagnostics: Vec::new(),
53            context_length_hint: None,
54            has_chat_template_hint: None,
55            tool_capable_hint: None,
56            stop_tokens_hint: None,
57            downloading: false,
58        }
59    }
60}
61
62/// The outcome of scanning one or more stores: the models found, free-text
63/// issues (a store was reachable but a single entry was malformed), and the set
64/// of source kinds whose scan failed wholesale (unreadable root).
65#[derive(Debug, Clone, Default, PartialEq)]
66pub struct ScanResult {
67    /// The models found across the scanned stores.
68    pub discovered: Vec<DiscoveredModel>,
69    /// Non-fatal issues (one bad entry, an unreadable sidecar) worth surfacing.
70    pub issues: Vec<String>,
71    /// Source kinds whose scan failed entirely (e.g. an unreadable root).
72    pub failed_kinds: Vec<SourceKind>,
73}
74
75/// A scanner over one class of on-disk model store. Synchronous: the kernel does
76/// its filesystem work inline (the runtime layer decides about threads).
77/// `Send`, so a kernel that owns scanners can be driven from a spawned task.
78pub trait StoreScanner: Send {
79    /// The source kinds this scanner produces (used to mark wholesale failures).
80    fn kinds(&self) -> Vec<SourceKind>;
81
82    /// Scan the store, returning everything found plus any issues.
83    fn scan(&self) -> ScanResult;
84}