gglib_core/ports/
mod.rs

1//! Port definitions (trait abstractions) for external systems.
2//!
3//! Ports define the interfaces that the core domain expects from infrastructure.
4//! They contain no implementation details and use only domain types.
5//!
6//! # Design Rules
7//!
8//! - No `sqlx` types in any signature
9//! - No process/filesystem implementation details
10//! - Traits are minimal and CRUD-focused for repositories
11//! - Intent-based methods for process runner (not implementation-leaking)
12
13pub mod agent;
14pub mod chat_history;
15pub mod download;
16pub mod download_event_emitter;
17pub mod download_manager;
18pub mod download_state;
19pub mod event_emitter;
20pub mod gguf_parser;
21pub mod huggingface;
22pub mod llm_completion;
23pub mod mcp_dto;
24pub mod mcp_error;
25pub mod mcp_repository;
26pub mod model_catalog;
27pub mod model_registrar;
28pub mod model_repository;
29pub mod model_runtime;
30pub mod process_runner;
31pub mod server_health;
32pub mod server_log_sink;
33pub mod settings_repository;
34pub mod system_probe;
35pub mod tool_executor_filter;
36pub mod tool_support;
37
38use std::sync::Arc;
39use thiserror::Error;
40
41// Re-export agent port types for convenience
42pub use agent::{AgentError, AgentLoopPort, AgentRunOutput, ToolExecutorPort};
43// Re-export LLM completion port (LlmStreamEvent lives in domain::agent)
44pub use llm_completion::LlmCompletionPort;
45// Re-export tool-executor filter decorators
46pub use tool_executor_filter::{EmptyToolExecutor, FilteredToolExecutor, TOOL_NOT_AVAILABLE_MSG};
47
48// Re-export repository traits for convenience
49pub use chat_history::{ChatHistoryError, ChatHistoryRepository};
50pub use download::{QuantizationResolver, Resolution, ResolvedFile};
51pub use download_event_emitter::{AppEventBridge, DownloadEventEmitterPort, NoopDownloadEmitter};
52pub use download_manager::{DownloadManagerConfig, DownloadManagerPort, DownloadRequest};
53pub use download_state::DownloadStateRepositoryPort;
54pub use event_emitter::{AppEventEmitter, NoopEmitter};
55pub use gguf_parser::{
56    GgufCapabilities, GgufMetadata, GgufParseError, GgufParserPort, NoopGgufParser,
57};
58pub use huggingface::{
59    HfClientPort, HfFileInfo, HfPortError, HfQuantInfo, HfRepoInfo, HfSearchOptions, HfSearchResult,
60};
61pub use mcp_dto::{ResolutionAttempt, ResolutionStatus};
62pub use mcp_error::{McpErrorCategory, McpErrorInfo, McpServiceError};
63pub use mcp_repository::{McpRepositoryError, McpServerRepository};
64pub use model_catalog::{CatalogError, ModelCatalogPort, ModelLaunchSpec, ModelSummary};
65pub use model_registrar::{CompletedDownload, ModelRegistrarPort};
66pub use model_repository::ModelRepository;
67pub use model_runtime::{ModelRuntimeError, ModelRuntimePort, RunningTarget};
68pub use process_runner::{ProcessHandle, ProcessRunner, ServerConfig, ServerHealth};
69pub use server_health::ServerHealthStatus;
70pub use server_log_sink::ServerLogSinkPort;
71pub use settings_repository::SettingsRepository;
72pub use system_probe::{SystemProbeError, SystemProbePort, SystemProbeResult};
73pub use tool_support::{
74    ModelSource, ToolFormat, ToolSupportDetection, ToolSupportDetectionInput,
75    ToolSupportDetectorPort,
76};
77
78/// Container for all repository trait objects.
79///
80/// This struct provides a consistent way to wire repositories across adapters
81/// without coupling them to concrete implementations. It lives in `gglib-core`
82/// so that `AppCore` can accept it without depending on `gglib-db`.
83///
84/// # Example
85///
86/// ```ignore
87/// // In gglib-db factory:
88/// pub fn build_repos(pool: &SqlitePool) -> Repos { ... }
89///
90/// // In adapter bootstrap:
91/// let repos = gglib_db::factory::build_repos(&pool);
92/// let core = AppCore::new(repos, runner);
93/// ```
94#[derive(Clone)]
95pub struct Repos {
96    /// Model repository for CRUD operations on models.
97    pub models: Arc<dyn ModelRepository>,
98    /// Settings repository for application settings.
99    pub settings: Arc<dyn SettingsRepository>,
100    /// MCP server repository for MCP server configurations.
101    pub mcp_servers: Arc<dyn McpServerRepository>,
102    /// Chat history repository for conversations and messages.
103    pub chat_history: Arc<dyn ChatHistoryRepository>,
104}
105
106impl Repos {
107    /// Create a new Repos container.
108    pub fn new(
109        models: Arc<dyn ModelRepository>,
110        settings: Arc<dyn SettingsRepository>,
111        mcp_servers: Arc<dyn McpServerRepository>,
112        chat_history: Arc<dyn ChatHistoryRepository>,
113    ) -> Self {
114        Self {
115            models,
116            settings,
117            mcp_servers,
118            chat_history,
119        }
120    }
121}
122
123/// Domain-specific errors for repository operations.
124///
125/// This error type abstracts away storage implementation details (e.g., sqlx errors)
126/// and provides a clean interface for services to handle storage failures.
127#[derive(Debug, Error)]
128pub enum RepositoryError {
129    /// The requested entity was not found.
130    #[error("Not found: {0}")]
131    NotFound(String),
132
133    /// An entity with the same identifier already exists.
134    #[error("Already exists: {0}")]
135    AlreadyExists(String),
136
137    /// Storage backend error (database, filesystem, etc.).
138    #[error("Storage error: {0}")]
139    Storage(String),
140
141    /// Serialization or deserialization failed.
142    #[error("Serialization error: {0}")]
143    Serialization(String),
144
145    /// A constraint was violated (e.g., foreign key, unique constraint).
146    #[error("Constraint violation: {0}")]
147    Constraint(String),
148}
149
150/// Domain-specific errors for process runner operations.
151///
152/// This error type abstracts away process management implementation details
153/// and provides a clean interface for services to handle process failures.
154#[derive(Debug, Error)]
155pub enum ProcessError {
156    /// Failed to start the process.
157    #[error("Failed to start: {0}")]
158    StartFailed(String),
159
160    /// Failed to stop the process.
161    #[error("Failed to stop: {0}")]
162    StopFailed(String),
163
164    /// The process is not running.
165    #[error("Process not running: {0}")]
166    NotRunning(String),
167
168    /// Health check failed.
169    #[error("Health check failed: {0}")]
170    HealthCheckFailed(String),
171
172    /// Configuration error.
173    #[error("Configuration error: {0}")]
174    Configuration(String),
175
176    /// Resource exhaustion (e.g., no available ports).
177    #[error("Resource exhaustion: {0}")]
178    ResourceExhausted(String),
179
180    /// Internal process error.
181    #[error("Internal error: {0}")]
182    Internal(String),
183}
184
185/// Core error type for semantic domain errors.
186///
187/// This is the canonical error type used across the core domain.
188/// Adapters should map this to their own error types (HTTP status codes,
189/// CLI exit codes, Tauri serialized errors).
190#[derive(Debug, Error)]
191pub enum CoreError {
192    /// Repository operation failed.
193    #[error(transparent)]
194    Repository(#[from] RepositoryError),
195
196    /// Process operation failed.
197    #[error(transparent)]
198    Process(#[from] ProcessError),
199
200    /// Settings validation error.
201    #[error(transparent)]
202    Settings(#[from] crate::settings::SettingsError),
203
204    /// Validation error (invalid input).
205    #[error("Validation error: {0}")]
206    Validation(String),
207
208    /// Configuration error.
209    #[error("Configuration error: {0}")]
210    Configuration(String),
211
212    /// External service error.
213    #[error("External service error: {0}")]
214    ExternalService(String),
215
216    /// Internal error (unexpected condition).
217    #[error("Internal error: {0}")]
218    Internal(String),
219}