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