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