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).
77pub trait StoreScanner {
78 /// The source kinds this scanner produces (used to mark wholesale failures).
79 fn kinds(&self) -> Vec<SourceKind>;
80
81 /// Scan the store, returning everything found plus any issues.
82 fn scan(&self) -> ScanResult;
83}