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    CognitiveDrift,
271    DreamCompleted,
272    MorningRecall,
273}
274
275/// WebSocket message
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct WebSocketMessage {
278    #[serde(rename = "type")]
279    pub message_type: WebSocketMessageType,
280    pub data: serde_json::Value,
281    pub timestamp: String,
282}
283
284impl WebSocketMessage {
285    pub fn new(message_type: WebSocketMessageType, data: serde_json::Value) -> Self {
286        Self {
287            message_type,
288            data,
289            timestamp: Utc::now().to_rfc3339(),
290        }
291    }
292
293    pub fn memory_stored(memory: &MemoryResponse, agent_type: &str) -> Self {
294        let data = serde_json::json!({
295            "memory": memory,
296            "agent_type": agent_type,
297        });
298        Self::new(WebSocketMessageType::MemoryStored, data)
299    }
300
301    pub fn memory_updated(memory_id: i64) -> Self {
302        let data = serde_json::json!({
303            "memory_id": memory_id,
304        });
305        Self::new(WebSocketMessageType::MemoryUpdated, data)
306    }
307
308    pub fn memory_deleted(memory_id: i64) -> Self {
309        let data = serde_json::json!({
310            "memory_id": memory_id,
311        });
312        Self::new(WebSocketMessageType::MemoryDeleted, data)
313    }
314
315    pub fn ping() -> Self {
316        Self::new(WebSocketMessageType::Ping, serde_json::Value::Null)
317    }
318
319    pub fn pong() -> Self {
320        Self::new(WebSocketMessageType::Pong, serde_json::Value::Null)
321    }
322
323    pub fn cognitive_drift(similarity: f32, agent_type: &str) -> Self {
324        let data = serde_json::json!({
325            "similarity": similarity,
326            "agent_type": agent_type,
327        });
328        Self::new(WebSocketMessageType::CognitiveDrift, data)
329    }
330
331    pub fn dream_completed(agent_type: &str, processed: usize) -> Self {
332        let data = serde_json::json!({
333            "agent_type": agent_type,
334            "processed": processed,
335        });
336        Self::new(WebSocketMessageType::DreamCompleted, data)
337    }
338
339    pub fn morning_recall(namespace: &str, count: usize) -> Self {
340        let data = serde_json::json!({
341            "namespace": namespace,
342            "count": count,
343        });
344        Self::new(WebSocketMessageType::MorningRecall, data)
345    }
346}
347
348// =============================================================================
349// Agent Request/Response Models
350// =============================================================================
351
352/// Request to ingest via the agent
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct AgentIngestRequest {
355    pub text: String,
356    #[serde(default = "default_source")]
357    pub source: String,
358}
359
360fn default_source() -> String {
361    "api".to_string()
362}
363
364/// Response from agent ingest
365#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct AgentIngestResponse {
367    pub success: bool,
368    pub memory_id: Option<i64>,
369    pub summary: Option<String>,
370    pub error: Option<String>,
371}
372
373/// Request to query the agent
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct AgentQueryRequest {
376    pub question: String,
377}
378
379/// Response from agent query
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct AgentQueryResponse {
382    pub success: bool,
383    pub question: String,
384    pub answer: Option<String>,
385    pub error: Option<String>,
386}
387
388/// Response from agent consolidate
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct AgentConsolidateResponse {
391    pub success: bool,
392    pub memories_processed: usize,
393    pub error: Option<String>,
394}
395
396/// Response from agent status
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct AgentStatusResponse {
399    pub enabled: bool,
400    pub namespace: String,
401    pub inbox_dir: String,
402    pub files_processed: u64,
403    pub memories_consolidated: u64,
404    pub queries_answered: u64,
405    pub last_scan: Option<String>,
406    pub last_consolidation: Option<String>,
407    pub errors: Vec<String>,
408    pub uptime_secs: u64,
409}
410
411/// Request to pin or boost a memory in cognitive cache
412#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct AgentBoostRequest {
414    pub memory_id: i64,
415    #[serde(default)]
416    pub pin: bool,
417    pub boost_score: Option<f32>,
418    /// Project root path. Required for the web API — the handler does not fall back to cwd.
419    #[serde(default)]
420    pub root_dir: Option<String>,
421}
422
423/// Response from agent boost
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct AgentBoostResponse {
426    pub success: bool,
427    pub error: Option<String>,
428}
429
430// =============================================================================
431// Observability Request/Response Models
432// =============================================================================
433
434/// A single job entry returned by the observability endpoints.
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct JobEntry {
437    pub id: i64,
438    pub job_type: String,
439    pub status: String,
440    pub priority: i64,
441    pub attempts: i64,
442    pub last_error: Option<String>,
443    pub lease_owner: Option<String>,
444    pub lease_expires_at: Option<String>,
445    pub created_at: String,
446    pub updated_at: String,
447}
448
449impl From<nexus_storage::MemoryJobRow> for JobEntry {
450    fn from(row: nexus_storage::MemoryJobRow) -> Self {
451        Self {
452            id: row.id,
453            job_type: row.job_type,
454            status: row.status,
455            priority: row.priority,
456            attempts: row.attempts,
457            last_error: row.last_error,
458            lease_owner: row.lease_owner,
459            lease_expires_at: row.lease_expires_at,
460            created_at: row.created_at,
461            updated_at: row.updated_at,
462        }
463    }
464}
465
466/// Response for listing jobs.
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct JobListResponse {
469    pub success: bool,
470    pub namespace: String,
471    pub jobs: Vec<JobEntry>,
472    pub total: i64,
473}
474
475/// Response for job status summary.
476#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct JobSummaryResponse {
478    pub success: bool,
479    pub namespace: String,
480    pub counts: std::collections::HashMap<String, i64>,
481}
482
483/// A single session digest entry returned by the observability endpoints.
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct DigestEntry {
486    pub id: i64,
487    pub session_key: String,
488    pub digest_kind: String,
489    pub memory_id: i64,
490    pub start_memory_id: Option<i64>,
491    pub end_memory_id: Option<i64>,
492    pub token_count: i64,
493    pub created_at: String,
494}
495
496impl From<nexus_storage::SessionDigestRow> for DigestEntry {
497    fn from(row: nexus_storage::SessionDigestRow) -> Self {
498        Self {
499            id: row.id,
500            session_key: row.session_key,
501            digest_kind: row.digest_kind,
502            memory_id: row.memory_id,
503            start_memory_id: row.start_memory_id,
504            end_memory_id: row.end_memory_id,
505            token_count: row.token_count,
506            created_at: row.created_at,
507        }
508    }
509}
510
511/// Response for listing digests.
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct DigestListResponse {
514    pub success: bool,
515    pub namespace: String,
516    pub digests: Vec<DigestEntry>,
517    pub total: i64,
518}
519
520/// Response for runtime health.
521#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct RuntimeResponse {
523    pub success: bool,
524    pub version: String,
525    pub uptime_seconds: u64,
526    pub db_connected: bool,
527    pub agent_enabled: bool,
528    pub active_sessions: usize,
529}
530
531/// A compact reflection sample shown in the observability endpoints.
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct ReflectionSampleEntry {
534    pub id: i64,
535    pub content: String,
536    pub created_at: String,
537    pub confidence: Option<f64>,
538}
539
540impl From<Memory> for ReflectionSampleEntry {
541    fn from(memory: Memory) -> Self {
542        let confidence = memory
543            .metadata
544            .get("cognitive")
545            .and_then(|cognitive| cognitive.get("confidence"))
546            .and_then(|value| value.as_f64());
547
548        Self {
549            id: memory.id,
550            content: memory.content,
551            created_at: memory.created_at.to_rfc3339(),
552            confidence,
553        }
554    }
555}
556
557/// Response for reflection/dream observability.
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct ReflectionStateResponse {
560    pub success: bool,
561    pub namespace: String,
562    pub contradiction_count: i64,
563    pub derived_count: i64,
564    pub recent_contradictions: Vec<ReflectionSampleEntry>,
565    pub recent_derived: Vec<ReflectionSampleEntry>,
566}
567
568/// Response for cognition overview (aggregated job/digest/evidence counts).
569#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct CognitionOverviewResponse {
571    pub success: bool,
572    pub namespace: String,
573    pub jobs_by_status: std::collections::HashMap<String, i64>,
574    pub digest_count: i64,
575    pub evidence_count: i64,
576    pub stage_metrics: std::collections::HashMap<String, f64>,
577}
578
579/// Response for query introspection (ranking decisions without LLM calls).
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct QueryIntrospectionResponse {
582    pub success: bool,
583    pub namespace: String,
584    pub question: String,
585    pub introspection: nexus_agent::QueryIntrospection,
586}
587
588// =============================================================================
589// Operator Dashboard Response Models
590// =============================================================================
591
592/// Dream throughput and job lifecycle counters.
593#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct DreamState {
595    pub completed_reflections: i64,
596    pub completed_digests: i64,
597    pub failed_jobs: i64,
598    pub pending_jobs: i64,
599    pub last_dream_at: Option<String>,
600}
601
602/// Digest freshness and session coverage.
603#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct DigestFreshnessState {
605    pub total_digests: i64,
606    pub sessions_with_cognition: i64,
607    pub latest_digest_age_seconds: Option<i64>,
608    pub latest_digest_at: Option<String>,
609}
610
611/// Breakdown of memories by cognitive level for recall composition.
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct RecallComposition {
614    pub raw: i64,
615    pub explicit: i64,
616    pub derived: i64,
617    pub summary_short: i64,
618    pub summary_long: i64,
619    pub contradiction: i64,
620    pub total: i64,
621}
622
623/// Adaptive dream scheduling state and configuration.
624#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct AdaptiveDreamState {
626    pub enabled: bool,
627    pub current_interval_secs: u64,
628    pub min_interval_secs: u64,
629    pub max_interval_secs: u64,
630    pub contradiction_count: i64,
631    pub contradiction_density: f64,
632}
633
634/// Operator dashboard response — at-a-glance cognition health.
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct DashboardResponse {
637    pub success: bool,
638    pub namespace: String,
639    pub dream: DreamState,
640    pub digest: DigestFreshnessState,
641    pub recall: RecallComposition,
642    pub adaptive: AdaptiveDreamState,
643}