Skip to main content

nexus_memory_web/
models.rs

1//! Request and response models for the web dashboard API
2
3use chrono::Utc;
4use nexus_core::{AgentNamespace, Memory, MemoryCategory, MemoryLaneType};
5use serde::{Deserialize, Serialize};
6
7// =============================================================================
8// Memory Request/Response Models
9// =============================================================================
10
11/// Request to create a new memory
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CreateMemoryRequest {
14    pub content: String,
15    pub agent_type: String,
16    #[serde(default)]
17    pub category: MemoryCategory,
18    pub memory_lane_type: Option<MemoryLaneType>,
19    #[serde(default)]
20    pub labels: Vec<String>,
21    #[serde(default)]
22    pub metadata: serde_json::Value,
23}
24
25/// Request to update an existing memory
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UpdateMemoryRequest {
28    pub content: Option<String>,
29    pub category: Option<MemoryCategory>,
30    pub memory_lane_type: Option<MemoryLaneType>,
31    pub labels: Option<Vec<String>>,
32    pub metadata: Option<serde_json::Value>,
33    pub is_active: Option<bool>,
34    pub is_archived: Option<bool>,
35}
36
37/// Memory response model
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct MemoryResponse {
40    pub id: i64,
41    pub content: String,
42    pub category: String,
43    pub category_description: Option<String>,
44    pub memory_lane_type: Option<String>,
45    pub labels: Vec<String>,
46    pub metadata: serde_json::Value,
47    pub similarity_score: Option<f32>,
48    pub relevance_score: Option<f32>,
49    pub created_at: String,
50    pub updated_at: Option<String>,
51    pub last_accessed: Option<String>,
52    pub is_active: bool,
53    pub is_archived: bool,
54    pub access_count: i64,
55}
56
57impl From<Memory> for MemoryResponse {
58    fn from(memory: Memory) -> Self {
59        Self {
60            id: memory.id,
61            content: memory.content,
62            category: memory.category.to_string(),
63            category_description: Some(memory.category.description().to_string()),
64            memory_lane_type: memory.memory_lane_type.map(|t| t.to_string()),
65            labels: memory.labels,
66            metadata: memory.metadata,
67            similarity_score: memory.similarity_score,
68            relevance_score: memory.relevance_score,
69            created_at: memory.created_at.to_rfc3339(),
70            updated_at: memory.updated_at.map(|d| d.to_rfc3339()),
71            last_accessed: memory.last_accessed.map(|d| d.to_rfc3339()),
72            is_active: memory.is_active,
73            is_archived: memory.is_archived,
74            access_count: memory.access_count,
75        }
76    }
77}
78
79/// Response for listing memories
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct MemoryListResponse {
82    pub success: bool,
83    pub total: i64,
84    pub results: Vec<MemoryResponse>,
85    pub query: Option<String>,
86    pub agent_type: String,
87    pub filters: serde_json::Value,
88}
89
90/// Response for creating a memory
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct MemoryCreateResponse {
93    pub success: bool,
94    pub memory_id: Option<i64>,
95    pub agent_type: String,
96    pub category: String,
97    pub error: Option<String>,
98}
99
100// =============================================================================
101// Search Request/Response Models
102// =============================================================================
103
104/// Request for semantic search
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct SearchRequest {
107    pub query: String,
108    #[serde(default = "default_agent_type")]
109    pub agent_type: String,
110    #[serde(default = "default_limit")]
111    pub limit: usize,
112    #[serde(default)]
113    pub offset: usize,
114    pub category: Option<MemoryCategory>,
115    pub memory_lane_type: Option<MemoryLaneType>,
116    pub threshold: Option<f32>,
117}
118
119fn default_agent_type() -> String {
120    "general".to_string()
121}
122
123fn default_limit() -> usize {
124    20
125}
126
127impl Default for SearchRequest {
128    fn default() -> Self {
129        Self {
130            query: String::new(),
131            agent_type: default_agent_type(),
132            limit: default_limit(),
133            offset: 0,
134            category: None,
135            memory_lane_type: None,
136            threshold: None,
137        }
138    }
139}
140
141/// Response for semantic search
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SearchResponse {
144    pub success: bool,
145    pub results: Vec<MemoryResponse>,
146    pub total: i64,
147    pub query: String,
148    pub agent_type: String,
149    pub filters: serde_json::Value,
150    pub error: Option<String>,
151}
152
153// =============================================================================
154// Namespace Request/Response Models
155// =============================================================================
156
157/// Request to create a namespace
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct CreateNamespaceRequest {
160    pub name: String,
161    pub agent_type: String,
162    pub description: Option<String>,
163}
164
165/// Namespace response model
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct NamespaceResponse {
168    pub id: i64,
169    pub name: String,
170    pub description: Option<String>,
171    pub agent_type: String,
172    pub created_at: String,
173    pub updated_at: Option<String>,
174}
175
176impl From<AgentNamespace> for NamespaceResponse {
177    fn from(ns: AgentNamespace) -> Self {
178        Self {
179            id: ns.id,
180            name: ns.name,
181            description: ns.description,
182            agent_type: ns.agent_type,
183            created_at: ns.created_at.to_rfc3339(),
184            updated_at: ns.updated_at.map(|d| d.to_rfc3339()),
185        }
186    }
187}
188
189/// Response for listing namespaces
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct NamespaceListResponse {
192    pub success: bool,
193    pub namespaces: Vec<NamespaceResponse>,
194    pub total: usize,
195}
196
197// =============================================================================
198// Stats Request/Response Models
199// =============================================================================
200
201/// Response for statistics
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct StatsResponse {
204    pub success: bool,
205    pub total_memories: i64,
206    pub active_memories: i64,
207    pub archived_memories: i64,
208    pub categories: serde_json::Value,
209    pub agents: Vec<AgentStats>,
210    pub system_info: Option<SystemInfo>,
211}
212
213/// Statistics for a single agent
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct AgentStats {
216    pub agent_type: String,
217    pub namespace_name: String,
218    pub total_memories: i64,
219    pub active_memories: i64,
220    pub archived_memories: i64,
221    pub categories: serde_json::Value,
222    pub oldest_memory: Option<String>,
223    pub newest_memory: Option<String>,
224}
225
226/// System information
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct SystemInfo {
229    pub version: String,
230    pub uptime_seconds: u64,
231    pub active_sessions: usize,
232}
233
234// =============================================================================
235// Health and Error Response Models
236// =============================================================================
237
238/// Health check response
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct HealthResponse {
241    pub status: String,
242    pub timestamp: String,
243    pub version: String,
244}
245
246/// Error response
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct ErrorResponse {
249    pub success: bool,
250    pub error: String,
251    pub detail: Option<String>,
252}
253
254// =============================================================================
255// WebSocket Message Models
256// =============================================================================
257
258/// WebSocket message types
259#[derive(Debug, Clone, Serialize, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub enum WebSocketMessageType {
262    MemoryStored,
263    MemoryUpdated,
264    MemoryDeleted,
265    SessionStarted,
266    SessionEnded,
267    StatsUpdated,
268    Ping,
269    Pong,
270}
271
272/// WebSocket message
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct WebSocketMessage {
275    #[serde(rename = "type")]
276    pub message_type: WebSocketMessageType,
277    pub data: serde_json::Value,
278    pub timestamp: String,
279}
280
281impl WebSocketMessage {
282    pub fn new(message_type: WebSocketMessageType, data: serde_json::Value) -> Self {
283        Self {
284            message_type,
285            data,
286            timestamp: Utc::now().to_rfc3339(),
287        }
288    }
289
290    pub fn memory_stored(memory: &MemoryResponse, agent_type: &str) -> Self {
291        let data = serde_json::json!({
292            "memory": memory,
293            "agent_type": agent_type,
294        });
295        Self::new(WebSocketMessageType::MemoryStored, data)
296    }
297
298    pub fn memory_updated(memory_id: i64) -> Self {
299        let data = serde_json::json!({
300            "memory_id": memory_id,
301        });
302        Self::new(WebSocketMessageType::MemoryUpdated, data)
303    }
304
305    pub fn memory_deleted(memory_id: i64) -> Self {
306        let data = serde_json::json!({
307            "memory_id": memory_id,
308        });
309        Self::new(WebSocketMessageType::MemoryDeleted, data)
310    }
311
312    pub fn ping() -> Self {
313        Self::new(WebSocketMessageType::Ping, serde_json::Value::Null)
314    }
315
316    pub fn pong() -> Self {
317        Self::new(WebSocketMessageType::Pong, serde_json::Value::Null)
318    }
319}
320
321// =============================================================================
322// Agent Request/Response Models
323// =============================================================================
324
325/// Request to ingest via the agent
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct AgentIngestRequest {
328    pub text: String,
329    #[serde(default = "default_source")]
330    pub source: String,
331}
332
333fn default_source() -> String {
334    "api".to_string()
335}
336
337/// Response from agent ingest
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct AgentIngestResponse {
340    pub success: bool,
341    pub memory_id: Option<i64>,
342    pub summary: Option<String>,
343    pub error: Option<String>,
344}
345
346/// Request to query the agent
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct AgentQueryRequest {
349    pub question: String,
350}
351
352/// Response from agent query
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct AgentQueryResponse {
355    pub success: bool,
356    pub question: String,
357    pub answer: Option<String>,
358    pub error: Option<String>,
359}
360
361/// Response from agent consolidate
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct AgentConsolidateResponse {
364    pub success: bool,
365    pub memories_processed: usize,
366    pub error: Option<String>,
367}
368
369/// Response from agent status
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct AgentStatusResponse {
372    pub enabled: bool,
373    pub namespace: String,
374    pub inbox_dir: String,
375    pub files_processed: u64,
376    pub memories_consolidated: u64,
377    pub queries_answered: u64,
378    pub last_scan: Option<String>,
379    pub last_consolidation: Option<String>,
380    pub errors: Vec<String>,
381    pub uptime_secs: u64,
382}
383
384// =============================================================================
385// Observability Request/Response Models
386// =============================================================================
387
388/// A single job entry returned by the observability endpoints.
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct JobEntry {
391    pub id: i64,
392    pub job_type: String,
393    pub status: String,
394    pub priority: i64,
395    pub attempts: i64,
396    pub last_error: Option<String>,
397    pub lease_owner: Option<String>,
398    pub lease_expires_at: Option<String>,
399    pub created_at: String,
400    pub updated_at: String,
401}
402
403impl From<nexus_storage::MemoryJobRow> for JobEntry {
404    fn from(row: nexus_storage::MemoryJobRow) -> Self {
405        Self {
406            id: row.id,
407            job_type: row.job_type,
408            status: row.status,
409            priority: row.priority,
410            attempts: row.attempts,
411            last_error: row.last_error,
412            lease_owner: row.lease_owner,
413            lease_expires_at: row.lease_expires_at,
414            created_at: row.created_at,
415            updated_at: row.updated_at,
416        }
417    }
418}
419
420/// Response for listing jobs.
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct JobListResponse {
423    pub success: bool,
424    pub namespace: String,
425    pub jobs: Vec<JobEntry>,
426    pub total: i64,
427}
428
429/// Response for job status summary.
430#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct JobSummaryResponse {
432    pub success: bool,
433    pub namespace: String,
434    pub counts: std::collections::HashMap<String, i64>,
435}
436
437/// A single session digest entry returned by the observability endpoints.
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct DigestEntry {
440    pub id: i64,
441    pub session_key: String,
442    pub digest_kind: String,
443    pub memory_id: i64,
444    pub start_memory_id: Option<i64>,
445    pub end_memory_id: Option<i64>,
446    pub token_count: i64,
447    pub created_at: String,
448}
449
450impl From<nexus_storage::SessionDigestRow> for DigestEntry {
451    fn from(row: nexus_storage::SessionDigestRow) -> Self {
452        Self {
453            id: row.id,
454            session_key: row.session_key,
455            digest_kind: row.digest_kind,
456            memory_id: row.memory_id,
457            start_memory_id: row.start_memory_id,
458            end_memory_id: row.end_memory_id,
459            token_count: row.token_count,
460            created_at: row.created_at,
461        }
462    }
463}
464
465/// Response for listing digests.
466#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct DigestListResponse {
468    pub success: bool,
469    pub namespace: String,
470    pub digests: Vec<DigestEntry>,
471    pub total: i64,
472}
473
474/// Response for runtime health.
475#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct RuntimeResponse {
477    pub success: bool,
478    pub version: String,
479    pub uptime_seconds: u64,
480    pub db_connected: bool,
481    pub agent_enabled: bool,
482    pub active_sessions: usize,
483}
484
485/// A compact reflection sample shown in the observability endpoints.
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct ReflectionSampleEntry {
488    pub id: i64,
489    pub content: String,
490    pub created_at: String,
491    pub confidence: Option<f64>,
492}
493
494impl From<Memory> for ReflectionSampleEntry {
495    fn from(memory: Memory) -> Self {
496        let confidence = memory
497            .metadata
498            .get("cognitive")
499            .and_then(|cognitive| cognitive.get("confidence"))
500            .and_then(|value| value.as_f64());
501
502        Self {
503            id: memory.id,
504            content: memory.content,
505            created_at: memory.created_at.to_rfc3339(),
506            confidence,
507        }
508    }
509}
510
511/// Response for reflection/dream observability.
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct ReflectionStateResponse {
514    pub success: bool,
515    pub namespace: String,
516    pub contradiction_count: i64,
517    pub derived_count: i64,
518    pub recent_contradictions: Vec<ReflectionSampleEntry>,
519    pub recent_derived: Vec<ReflectionSampleEntry>,
520}
521
522/// Response for cognition overview (aggregated job/digest/evidence counts).
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct CognitionOverviewResponse {
525    pub success: bool,
526    pub namespace: String,
527    pub jobs_by_status: std::collections::HashMap<String, i64>,
528    pub digest_count: i64,
529    pub evidence_count: i64,
530    pub stage_metrics: std::collections::HashMap<String, f64>,
531}
532
533/// Response for query introspection (ranking decisions without LLM calls).
534#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct QueryIntrospectionResponse {
536    pub success: bool,
537    pub namespace: String,
538    pub question: String,
539    pub introspection: nexus_agent::QueryIntrospection,
540}
541
542// =============================================================================
543// Operator Dashboard Response Models
544// =============================================================================
545
546/// Dream throughput and job lifecycle counters.
547#[derive(Debug, Clone, Serialize, Deserialize)]
548pub struct DreamState {
549    pub completed_reflections: i64,
550    pub completed_digests: i64,
551    pub failed_jobs: i64,
552    pub pending_jobs: i64,
553    pub last_dream_at: Option<String>,
554}
555
556/// Digest freshness and session coverage.
557#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct DigestFreshnessState {
559    pub total_digests: i64,
560    pub sessions_with_cognition: i64,
561    pub latest_digest_age_seconds: Option<i64>,
562    pub latest_digest_at: Option<String>,
563}
564
565/// Breakdown of memories by cognitive level for recall composition.
566#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct RecallComposition {
568    pub raw: i64,
569    pub explicit: i64,
570    pub derived: i64,
571    pub summary_short: i64,
572    pub summary_long: i64,
573    pub contradiction: i64,
574    pub total: i64,
575}
576
577/// Adaptive dream scheduling state and configuration.
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct AdaptiveDreamState {
580    pub enabled: bool,
581    pub current_interval_secs: u64,
582    pub min_interval_secs: u64,
583    pub max_interval_secs: u64,
584    pub contradiction_count: i64,
585    pub contradiction_density: f64,
586}
587
588/// Operator dashboard response — at-a-glance cognition health.
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct DashboardResponse {
591    pub success: bool,
592    pub namespace: String,
593    pub dream: DreamState,
594    pub digest: DigestFreshnessState,
595    pub recall: RecallComposition,
596    pub adaptive: AdaptiveDreamState,
597}