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}