gglib_core/ports/model_catalog.rs
1//! Model catalog port for listing and resolving models.
2//!
3//! This port defines the interface for querying the model catalog.
4//! It provides domain-level model information without exposing
5//! database or storage implementation details.
6
7use async_trait::async_trait;
8use std::fmt;
9use std::path::PathBuf;
10use thiserror::Error;
11
12use crate::domain::ModelCapabilities;
13
14/// Domain model summary for catalog operations (listing).
15///
16/// This is a domain type (not an `OpenAI` API type). The proxy layer
17/// is responsible for mapping this to OpenAI-compatible formats.
18///
19/// Note: Does NOT include `file_path` to avoid leaking filesystem details
20/// in catalog/listing operations.
21#[derive(Debug, Clone)]
22pub struct ModelSummary {
23 /// Database ID of the model.
24 pub id: u32,
25 /// Model name (used as identifier).
26 pub name: String,
27 /// Tags/labels associated with the model.
28 pub tags: Vec<String>,
29 /// Detected and persisted capability flags for this model.
30 ///
31 /// This is the single source of truth for model behaviour constraints
32 /// (strict-turn alternation, system-role support, tool calls, reasoning).
33 /// The proxy uses these directly rather than inferring from tags at
34 /// request time, eliminating the split-brain between tags and capabilities.
35 pub capabilities: ModelCapabilities,
36 /// Parameter count as string (e.g., "7B", "13B", "70B").
37 pub param_count: String,
38 /// Quantization type (e.g., "`Q4_K_M`", "`Q8_0`").
39 pub quantization: Option<String>,
40 /// Model architecture (e.g., "llama", "mistral", "qwen2").
41 pub architecture: Option<String>,
42 /// Unix timestamp when the model was added.
43 pub created_at: i64,
44 /// File size in bytes.
45 pub file_size: u64,
46}
47
48/// Launch specification for running a model.
49///
50/// Contains all information needed to actually launch a model,
51/// including the file path. Separate from `ModelSummary` to avoid
52/// leaking filesystem details in catalog operations.
53#[derive(Debug, Clone)]
54pub struct ModelLaunchSpec {
55 /// Database ID of the model.
56 pub id: u32,
57 /// Model name.
58 pub name: String,
59 /// Absolute path to the GGUF file.
60 pub file_path: PathBuf,
61 /// Tags/labels associated with the model.
62 pub tags: Vec<String>,
63 /// Model architecture (for runtime configuration).
64 pub architecture: Option<String>,
65 /// Maximum context length the model supports.
66 pub context_length: Option<u64>,
67}
68
69impl ModelSummary {
70 /// Create a description string for this model.
71 #[must_use]
72 pub fn description(&self) -> String {
73 let arch = self.architecture.as_deref().unwrap_or("unknown");
74 let quant = self.quantization.as_deref().unwrap_or("unknown");
75 format!("{} - {} parameters, {}", arch, self.param_count, quant)
76 }
77}
78
79/// Errors that can occur during catalog operations.
80#[derive(Debug, Error)]
81pub enum CatalogError {
82 /// Failed to query the catalog.
83 #[error("Failed to query catalog: {0}")]
84 QueryFailed(String),
85
86 /// Internal error during catalog operations.
87 #[error("Internal error: {0}")]
88 Internal(String),
89}
90
91/// Port for querying the model catalog.
92///
93/// This interface provides read-only access to the model catalog
94/// for listing and resolving models. It does not handle model
95/// registration or deletion.
96#[async_trait]
97pub trait ModelCatalogPort: Send + Sync + fmt::Debug {
98 /// List all models in the catalog.
99 ///
100 /// Returns a list of model summaries ordered by name.
101 ///
102 /// # Errors
103 ///
104 /// Returns `CatalogError` if the catalog cannot be queried.
105 async fn list_models(&self) -> Result<Vec<ModelSummary>, CatalogError>;
106
107 /// Resolve a model by name or alias.
108 ///
109 /// This method performs model resolution:
110 /// 1. Exact name match
111 /// 2. Case-insensitive name match
112 /// 3. Fuzzy/partial match (implementation-defined)
113 ///
114 /// Returns `None` if no matching model is found.
115 ///
116 /// # Arguments
117 ///
118 /// * `name` - Model name or alias to resolve
119 ///
120 /// # Errors
121 ///
122 /// Returns `CatalogError` if the catalog cannot be queried.
123 async fn resolve_model(&self, name: &str) -> Result<Option<ModelSummary>, CatalogError>;
124
125 /// Resolve a model for launching.
126 ///
127 /// Returns full launch specification including file path.
128 /// Use this when you need to actually run a model, not just list it.
129 ///
130 /// # Arguments
131 ///
132 /// * `name` - Model name or alias to resolve
133 ///
134 /// # Errors
135 ///
136 /// Returns `CatalogError` if the catalog cannot be queried.
137 async fn resolve_for_launch(&self, name: &str)
138 -> Result<Option<ModelLaunchSpec>, CatalogError>;
139}