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}