Skip to main content

dakera_client/
events.rs

1//! SSE streaming event types and client support (CE-1).
2//!
3//! ## Usage
4//!
5//! ```rust,no_run
6//! use dakera_client::DakeraClient;
7//! use tokio::sync::mpsc;
8//!
9//! #[tokio::main]
10//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
11//!     let client = DakeraClient::new("http://localhost:3000")?;
12//!
13//!     let mut rx = client.stream_namespace_events("my-ns").await?;
14//!     while let Some(result) = rx.recv().await {
15//!         let event = result?;
16//!         println!("Event: {:?}", event);
17//!     }
18//!     Ok(())
19//! }
20//! ```
21
22use serde::{Deserialize, Serialize};
23
24/// Operation status for [`DakeraEvent::OperationProgress`] events.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum OpStatus {
28    Pending,
29    Running,
30    Completed,
31    Failed,
32}
33
34/// Vector mutation operation type for [`DakeraEvent::VectorsMutated`] events.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum VectorMutationOp {
38    Upserted,
39    Deleted,
40}
41
42/// An event received from a Dakera SSE stream.
43///
44/// Mirrors the server-side `DakeraEvent` enum.  Events are delivered over
45/// `GET /v1/namespaces/:ns/events` (namespace-scoped, Read scope) or
46/// `GET /ops/events` (global, Admin scope).
47///
48/// Use [`DakeraClient::stream_namespace_events`] or
49/// [`DakeraClient::stream_global_events`] to subscribe.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(tag = "type", rename_all = "snake_case")]
52pub enum DakeraEvent {
53    /// A new namespace was created.
54    NamespaceCreated { namespace: String, dimension: usize },
55
56    /// A namespace was deleted.
57    NamespaceDeleted { namespace: String },
58
59    /// Progress update for a long-running operation (progress: 0–100).
60    OperationProgress {
61        operation_id: String,
62        #[serde(skip_serializing_if = "Option::is_none")]
63        namespace: Option<String>,
64        op_type: String,
65        /// Progress percentage 0–100.
66        progress: u8,
67        status: OpStatus,
68        #[serde(skip_serializing_if = "Option::is_none")]
69        message: Option<String>,
70        /// Unix milliseconds.
71        updated_at: u64,
72    },
73
74    /// A background job changed status.
75    JobProgress {
76        job_id: String,
77        job_type: String,
78        #[serde(skip_serializing_if = "Option::is_none")]
79        namespace: Option<String>,
80        progress: u8,
81        status: String,
82    },
83
84    /// Vectors were upserted or deleted in bulk (threshold: >100 vectors).
85    VectorsMutated {
86        namespace: String,
87        op: VectorMutationOp,
88        count: usize,
89    },
90
91    /// Subscriber fell too far behind — some events were dropped.
92    /// Reconnect to resume the stream.
93    StreamLagged { dropped: u64, hint: String },
94
95    /// Emitted immediately on stream subscription to confirm the connection is live.
96    ///
97    /// Clients can use this to distinguish *connected-and-idle* from *not-yet-connected*.
98    Connected {
99        /// Unix milliseconds when the connection was confirmed.
100        timestamp: u64,
101    },
102}
103
104/// A memory lifecycle event received from the `GET /v1/events/stream` SSE endpoint (DASH-B).
105///
106/// The `event_type` field identifies the operation:
107/// - `connected` — emitted immediately on subscription; `agent_id` will be an empty string
108/// - `stored` — a memory was stored (content, importance, tags present)
109/// - `recalled` — a memory was recalled
110/// - `forgotten` — a memory was deleted
111/// - `consolidated` — memories were merged
112/// - `importance_updated` — importance score changed
113/// - `session_started` / `session_ended` — agent session lifecycle
114/// - `stream_lagged` — consumer fell behind; some events were dropped
115///
116/// Use [`DakeraClient::stream_memory_events`] to subscribe.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct MemoryEvent {
119    /// Event type. The `connected` handshake event uses the JSON `"type"` key
120    /// rather than `"event_type"` — the SDK normalises this automatically.
121    #[serde(alias = "type", default)]
122    pub event_type: String,
123    /// Agent that owns the memory. Empty string for `connected` handshake events.
124    #[serde(default)]
125    pub agent_id: String,
126    /// Unix milliseconds.
127    pub timestamp: u64,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub memory_id: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub content: Option<String>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub importance: Option<f32>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub tags: Option<Vec<String>>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub session_id: Option<String>,
138}
139
140impl DakeraEvent {
141    /// Returns the SSE `event` type string for this event variant.
142    pub fn event_type(&self) -> &'static str {
143        match self {
144            DakeraEvent::NamespaceCreated { .. } => "namespace_created",
145            DakeraEvent::NamespaceDeleted { .. } => "namespace_deleted",
146            DakeraEvent::OperationProgress { .. } => "operation_progress",
147            DakeraEvent::JobProgress { .. } => "job_progress",
148            DakeraEvent::VectorsMutated { .. } => "vectors_mutated",
149            DakeraEvent::StreamLagged { .. } => "stream_lagged",
150            DakeraEvent::Connected { .. } => "connected",
151        }
152    }
153}