Skip to main content

gglib_core/events/
mod.rs

1#![doc = include_str!("README.md")]
2mod app;
3mod remote;
4mod server;
5
6#[cfg(test)]
7mod app_event_tests;
8
9use serde::{Deserialize, Serialize};
10
11use crate::ports::RuntimeErrorEnvelope;
12
13// Re-export event types
14pub use app::ModelSummary;
15pub use server::{NoopServerEvents, ServerEvents, ServerSnapshotEntry, ServerSummary};
16
17// Import download types for AppEvent::Download wrapper
18use crate::download::DownloadEvent;
19
20/// Canonical event types for all adapters.
21///
22/// This enum unifies server, download, and model events into a single
23/// discriminated union. Each variant includes all necessary context
24/// for the event to be self-describing.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS), ts(export))]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum AppEvent {
29    // ========== Server Events ==========
30    /// A model server has started and is ready to accept requests.
31    ServerStarted {
32        /// ID of the model being served.
33        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
34        #[serde(rename = "modelId")]
35        model_id: i64,
36        /// Name of the model being served.
37        #[serde(rename = "modelName")]
38        model_name: String,
39        /// Port the server is listening on.
40        port: u16,
41    },
42
43    /// A model server has been stopped (clean shutdown).
44    ServerStopped {
45        /// ID of the model that was being served.
46        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
47        #[serde(rename = "modelId")]
48        model_id: i64,
49        /// Name of the model that was being served.
50        #[serde(rename = "modelName")]
51        model_name: String,
52    },
53
54    /// A model server encountered an error.
55    ServerError {
56        /// ID of the model being served (if known).
57        ///
58        /// Serde always sends the key — an unparseable model ID arrives as
59        /// `null`, not as an absent field.
60        #[cfg_attr(feature = "ts-bindings", ts(type = "number | null"))]
61        #[serde(rename = "modelId")]
62        model_id: Option<i64>,
63        /// Name of the model being served.
64        #[serde(rename = "modelName")]
65        model_name: String,
66        /// Structured error detail (message, type discriminant, retryable
67        /// flag), mirroring the HTTP layer's `ErrorResponse` shape.
68        error: RuntimeErrorEnvelope,
69    },
70
71    /// Snapshot of all currently running servers.
72    ServerSnapshot {
73        /// List of currently running servers.
74        servers: Vec<ServerSnapshotEntry>,
75    },
76
77    // ========== Download Events ==========
78    /// Download lifecycle + progress events (including shard progress).
79    ///
80    /// Wraps `DownloadEvent` verbatim to preserve all detail including
81    /// shard-specific progress information.
82    #[serde(rename = "download")]
83    Download {
84        /// The download event payload.
85        event: DownloadEvent,
86    },
87
88    // ========== Model Events ==========
89    /// A model was added to the library.
90    ModelAdded {
91        /// Summary of the added model.
92        model: ModelSummary,
93    },
94
95    /// A model was removed from the library.
96    ModelRemoved {
97        /// ID of the removed model.
98        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
99        #[serde(rename = "modelId")]
100        model_id: i64,
101    },
102
103    /// A model was updated in the library.
104    ModelUpdated {
105        /// Summary of the updated model.
106        model: ModelSummary,
107    },
108
109    // ========== Verification Events ==========
110    /// Model verification progress update.
111    VerificationProgress {
112        /// ID of the model being verified.
113        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
114        #[serde(rename = "modelId")]
115        model_id: i64,
116        /// Name of the model being verified.
117        #[serde(rename = "modelName")]
118        model_name: String,
119        /// Name of the shard being verified.
120        #[serde(rename = "shardName")]
121        shard_name: String,
122        /// Bytes processed so far.
123        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
124        #[serde(rename = "bytesProcessed")]
125        bytes_processed: u64,
126        /// Total bytes to process.
127        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
128        #[serde(rename = "totalBytes")]
129        total_bytes: u64,
130    },
131
132    /// Model verification completed.
133    VerificationComplete {
134        /// ID of the verified model.
135        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
136        #[serde(rename = "modelId")]
137        model_id: i64,
138        /// Name of the verified model.
139        #[serde(rename = "modelName")]
140        model_name: String,
141        /// Overall health status.
142        #[serde(rename = "overallHealth")]
143        overall_health: crate::services::OverallHealth,
144    },
145
146    /// Server health status has changed.
147    ///
148    /// Emitted by continuous monitoring when a server's health state changes.
149    ServerHealthChanged {
150        /// Unique server instance identifier.
151        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
152        #[serde(rename = "serverId")]
153        server_id: i64,
154        /// ID of the model being served.
155        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
156        #[serde(rename = "modelId")]
157        model_id: i64,
158        /// New health status.
159        status: crate::ports::ServerHealthStatus,
160        /// Optional detail message (e.g., error description).
161        #[cfg_attr(feature = "ts-bindings", ts(optional))]
162        #[serde(skip_serializing_if = "Option::is_none")]
163        detail: Option<String>,
164        /// Unix timestamp in milliseconds when status changed.
165        #[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
166        timestamp: u64,
167    },
168
169    // ========== Proxy Events ==========
170    /// The OpenAI-compatible proxy has started.
171    ProxyStarted {
172        /// Port the proxy is listening on.
173        port: u16,
174    },
175
176    /// The proxy has been stopped (clean shutdown).
177    ProxyStopped,
178
179    /// The proxy crashed (task exited without cancellation).
180    ProxyCrashed,
181
182    // ========== Remote tunnel events (ADR 0012) ==========
183    /// The remote tunnel is up and has a ticket. The ticket itself is
184    /// never on the event stream — any local GUI client can read it — so
185    /// this carries the same twelve-character fingerprint the logs use.
186    RemoteEnabled {
187        /// Fingerprint of the ticket this session minted.
188        #[serde(rename = "ticketFingerprint")]
189        ticket_fingerprint: String,
190    },
191
192    /// The remote tunnel was taken down; nothing answers its ticket until
193    /// `enable` puts the same one back.
194    RemoteDisabled,
195
196    /// A device redeemed the pairing code and now holds the key.
197    RemotePaired {
198        /// The tunnel edge's fingerprint for that device, when it sent one.
199        peer: Option<String>,
200    },
201
202    /// This machine's connect side is up: a local port that is the remote
203    /// proxy.
204    RemoteConnected {
205        /// The loopback port the tunnel is bound to on this machine.
206        port: u16,
207    },
208
209    /// The connect side was taken down.
210    RemoteDisconnected,
211
212    /// The far machine has been away past the grace. The port stays bound
213    /// and is still being dialled; this says so, so a client is not left
214    /// reading "connected" over nothing.
215    RemoteAway {
216        /// The loopback port that is still bound.
217        port: u16,
218    },
219
220    /// The far machine answered again after being away.
221    RemoteBack {
222        /// The loopback port, unchanged.
223        port: u16,
224    },
225}
226
227impl AppEvent {
228    /// Colon-separated event names.
229    ///
230    /// **Not what goes on the wire.** `AppEvent` is `#[serde(tag = "type",
231    /// rename_all = "snake_case")]`, so SSE carries `download`/`model_added`;
232    /// these `download:started` spellings are the Tauri-bus vocabulary from
233    /// before the GUI backend moved into the daemon, and the Tauri event
234    /// branch that subscribed to them is gone.
235    ///
236    /// Nothing outside this module's tests calls it. Retiring it belongs with
237    /// the rest of the residual-Rust sweep, not here.
238    pub const fn event_name(&self) -> &'static str {
239        match self {
240            Self::ServerStarted { .. } => "server:started",
241            Self::ServerStopped { .. } => "server:stopped",
242            Self::ServerError { .. } => "server:error",
243            Self::ServerSnapshot { .. } => "server:snapshot",
244            Self::ServerHealthChanged { .. } => "server:health_changed",
245            Self::Download { event } => event.event_name(),
246            Self::ModelAdded { .. } => "model:added",
247            Self::ModelRemoved { .. } => "model:removed",
248            Self::ModelUpdated { .. } => "model:updated",
249            Self::VerificationProgress { .. } => "verification:progress",
250            Self::VerificationComplete { .. } => "verification:complete",
251            Self::ProxyStarted { .. } => "proxy:started",
252            Self::ProxyStopped => "proxy:stopped",
253            Self::ProxyCrashed => "proxy:crashed",
254            Self::RemoteEnabled { .. } => "remote:enabled",
255            Self::RemoteDisabled => "remote:disabled",
256            Self::RemotePaired { .. } => "remote:paired",
257            Self::RemoteConnected { .. } => "remote:connected",
258            Self::RemoteDisconnected => "remote:disconnected",
259            Self::RemoteAway { .. } => "remote:away",
260            Self::RemoteBack { .. } => "remote:back",
261        }
262    }
263}
264
265impl AppEvent {
266    /// Create a [`AppEvent::ProxyStarted`] event.
267    pub const fn proxy_started(port: u16) -> Self {
268        Self::ProxyStarted { port }
269    }
270
271    /// Create a [`AppEvent::ProxyStopped`] event.
272    pub const fn proxy_stopped() -> Self {
273        Self::ProxyStopped
274    }
275
276    /// Create a [`AppEvent::ProxyCrashed`] event.
277    pub const fn proxy_crashed() -> Self {
278        Self::ProxyCrashed
279    }
280}