1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::Result;
11use crate::DakeraClient;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct OpsStats {
20 pub version: String,
21 pub total_vectors: u64,
22 pub namespace_count: u64,
23 pub uptime_seconds: u64,
24 pub timestamp: u64,
25 pub state: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ClusterStatus {
31 pub cluster_id: String,
32 pub state: String,
33 pub node_count: u32,
34 pub total_vectors: u64,
35 pub namespace_count: u64,
36 pub version: String,
37 pub timestamp: u64,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub redis_healthy: Option<bool>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct NodeInfo {
46 pub node_id: String,
47 pub address: String,
48 pub role: String,
49 pub status: String,
50 pub version: String,
51 pub uptime_seconds: u64,
52 pub vector_count: u64,
53 pub memory_bytes: u64,
54 #[serde(default)]
55 pub cpu_percent: f32,
56 #[serde(default)]
57 pub memory_percent: f32,
58 pub last_heartbeat: u64,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct NodeListResponse {
64 pub nodes: Vec<NodeInfo>,
65 pub total: u32,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct IndexStats {
75 pub index_type: String,
76 pub is_built: bool,
77 pub size_bytes: u64,
78 pub indexed_vectors: u64,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub last_rebuild: Option<u64>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct NamespaceAdminInfo {
86 pub name: String,
87 pub vector_count: u64,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub dimension: Option<usize>,
90 pub index_type: String,
91 pub storage_bytes: u64,
92 pub document_count: u64,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub created_at: Option<u64>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub updated_at: Option<u64>,
97 pub index_stats: IndexStats,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct NamespaceListResponse {
103 pub namespaces: Vec<NamespaceAdminInfo>,
104 pub total: u64,
105 pub total_vectors: u64,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct OptimizeRequest {
111 #[serde(default)]
112 pub force: bool,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub target_index_type: Option<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct OptimizeResponse {
120 pub success: bool,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub job_id: Option<String>,
123 pub message: String,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct IndexStatsResponse {
133 pub namespaces: HashMap<String, IndexStats>,
134 pub total_indexed_vectors: u64,
135 pub total_size_bytes: u64,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct RebuildIndexRequest {
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub namespace: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub index_type: Option<String>,
145 #[serde(default)]
146 pub force: bool,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct RebuildIndexResponse {
152 pub success: bool,
153 pub job_id: String,
154 pub message: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct CacheStats {
164 pub enabled: bool,
165 pub cache_type: String,
166 pub entries: u64,
167 pub size_bytes: u64,
168 pub hits: u64,
169 pub misses: u64,
170 pub hit_rate: f64,
171 pub evictions: u64,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ClearCacheRequest {
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub namespace: Option<String>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ClearCacheResponse {
184 pub success: bool,
185 pub entries_cleared: u64,
186 pub message: String,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct RuntimeConfig {
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub max_vectors_per_namespace: Option<u64>,
198 pub default_index_type: String,
199 pub cache_enabled: bool,
200 pub cache_max_size_bytes: u64,
201 pub rate_limit_enabled: bool,
202 pub rate_limit_rps: u32,
203 pub query_timeout_ms: u64,
204 #[serde(default = "default_true")]
206 pub autopilot_enabled: bool,
207 #[serde(default = "default_dedup_threshold")]
209 pub autopilot_dedup_threshold: f32,
210 #[serde(default = "default_dedup_interval")]
212 pub autopilot_dedup_interval_hours: u64,
213 #[serde(default = "default_consolidation_interval")]
215 pub autopilot_consolidation_interval_hours: u64,
216}
217
218fn default_true() -> bool {
219 true
220}
221fn default_dedup_threshold() -> f32 {
222 0.93
223}
224fn default_dedup_interval() -> u64 {
225 6
226}
227fn default_consolidation_interval() -> u64 {
228 12
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct UpdateConfigResponse {
234 pub success: bool,
235 pub config: RuntimeConfig,
236 pub message: String,
237 #[serde(default, skip_serializing_if = "Vec::is_empty")]
238 pub warnings: Vec<String>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct QuotaConfig {
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub max_vectors: Option<u64>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub max_storage_bytes: Option<u64>,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 pub max_queries_per_minute: Option<u64>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub max_writes_per_minute: Option<u64>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct QuotaUsage {
261 #[serde(default)]
262 pub current_vectors: u64,
263 #[serde(default)]
264 pub current_storage_bytes: u64,
265 #[serde(default)]
266 pub queries_this_minute: u64,
267 #[serde(default)]
268 pub writes_this_minute: u64,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct QuotaStatus {
274 pub namespace: String,
275 pub config: QuotaConfig,
276 pub usage: QuotaUsage,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct QuotaListResponse {
282 pub quotas: Vec<QuotaStatus>,
283 pub total: u64,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub default_config: Option<QuotaConfig>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct SlowQueryEntry {
295 pub id: String,
296 pub timestamp: u64,
297 pub namespace: String,
298 pub query_type: String,
299 pub duration_ms: f64,
300 #[serde(default)]
301 pub parameters: Option<serde_json::Value>,
302 #[serde(default)]
303 pub results_count: u64,
304 #[serde(default)]
305 pub vectors_scanned: u64,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct SlowQueryListResponse {
311 pub queries: Vec<SlowQueryEntry>,
312 pub total: u64,
313 pub threshold_ms: f64,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct BackupInfo {
323 pub backup_id: String,
324 pub name: String,
325 pub backup_type: String,
326 pub status: String,
327 pub namespaces: Vec<String>,
328 pub vector_count: u64,
329 pub size_bytes: u64,
330 pub created_at: u64,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 pub completed_at: Option<u64>,
333 #[serde(skip_serializing_if = "Option::is_none")]
334 pub duration_seconds: Option<u64>,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub storage_path: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub error: Option<String>,
339 pub encrypted: bool,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub compression: Option<String>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct BackupListResponse {
347 pub backups: Vec<BackupInfo>,
348 pub total: u64,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct CreateBackupRequest {
354 pub name: String,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 pub backup_type: Option<String>,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub namespaces: Option<Vec<String>>,
359 #[serde(skip_serializing_if = "Option::is_none")]
360 pub encrypt: Option<bool>,
361 #[serde(skip_serializing_if = "Option::is_none")]
362 pub compression: Option<String>,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct CreateBackupResponse {
368 pub backup: BackupInfo,
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub estimated_completion: Option<u64>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct RestoreBackupRequest {
376 pub backup_id: String,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub target_namespaces: Option<Vec<String>>,
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub overwrite: Option<bool>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 pub point_in_time: Option<u64>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct RestoreBackupResponse {
388 pub restore_id: String,
389 pub status: String,
390 pub backup_id: String,
391 pub namespaces: Vec<String>,
392 pub started_at: u64,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub estimated_completion: Option<u64>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub progress_percent: Option<u8>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub vectors_restored: Option<u64>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub completed_at: Option<u64>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub duration_seconds: Option<u64>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub error: Option<String>,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct AutoPilotConfig {
414 pub enabled: bool,
415 pub dedup_threshold: f32,
416 pub dedup_interval_hours: u64,
417 pub consolidation_interval_hours: u64,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct DedupResultSnapshot {
423 pub namespaces_processed: usize,
424 pub memories_scanned: usize,
425 pub duplicates_removed: usize,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ConsolidationResultSnapshot {
431 pub namespaces_processed: usize,
432 pub memories_scanned: usize,
433 pub clusters_merged: usize,
434 pub memories_consolidated: usize,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct AutoPilotStatusResponse {
440 pub config: AutoPilotConfig,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub last_dedup_at: Option<u64>,
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub last_consolidation_at: Option<u64>,
445 #[serde(skip_serializing_if = "Option::is_none")]
446 pub last_dedup: Option<DedupResultSnapshot>,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub last_consolidation: Option<ConsolidationResultSnapshot>,
449 pub total_dedup_removed: u64,
450 pub total_consolidated: u64,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, Default)]
455pub struct AutoPilotConfigRequest {
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub enabled: Option<bool>,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 pub dedup_threshold: Option<f32>,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub dedup_interval_hours: Option<u64>,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub consolidation_interval_hours: Option<u64>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct AutoPilotConfigResponse {
469 pub success: bool,
470 pub config: AutoPilotConfig,
471 pub message: String,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "lowercase")]
477pub enum AutoPilotTriggerAction {
478 Dedup,
479 Consolidate,
480 All,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct AutoPilotTriggerRequest {
486 pub action: AutoPilotTriggerAction,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct AutoPilotDedupResult {
492 pub namespaces_processed: usize,
493 pub memories_scanned: usize,
494 pub duplicates_removed: usize,
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct AutoPilotConsolidationResult {
500 pub namespaces_processed: usize,
501 pub memories_scanned: usize,
502 pub clusters_merged: usize,
503 pub memories_consolidated: usize,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct AutoPilotTriggerResponse {
509 pub success: bool,
510 pub action: AutoPilotTriggerAction,
511 #[serde(skip_serializing_if = "Option::is_none")]
512 pub dedup: Option<AutoPilotDedupResult>,
513 #[serde(skip_serializing_if = "Option::is_none")]
514 pub consolidation: Option<AutoPilotConsolidationResult>,
515 pub message: String,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct DecayConfigResponse {
525 pub strategy: String,
527 pub half_life_hours: f64,
529 pub min_importance: f32,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize, Default)]
535pub struct DecayConfigUpdateRequest {
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub strategy: Option<String>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub half_life_hours: Option<f64>,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub min_importance: Option<f32>,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct DecayConfigUpdateResponse {
550 pub success: bool,
551 pub config: DecayConfigResponse,
552 pub message: String,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct LastDecayCycleStats {
558 pub namespaces_processed: usize,
559 pub memories_processed: usize,
560 pub memories_decayed: usize,
561 pub memories_deleted: usize,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct DecayStatsResponse {
567 pub total_decayed: u64,
569 pub total_deleted: u64,
571 #[serde(skip_serializing_if = "Option::is_none")]
573 pub last_run_at: Option<u64>,
574 pub cycles_run: u64,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 pub last_cycle: Option<LastDecayCycleStats>,
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct TtlCleanupRequest {
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub namespace: Option<String>,
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct TtlCleanupResponse {
595 pub success: bool,
596 pub vectors_removed: u64,
597 pub namespaces_cleaned: Vec<String>,
598 pub message: String,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct TtlStats {
604 pub namespace: String,
605 pub vectors_with_ttl: u64,
606 pub expiring_within_hour: u64,
607 pub expiring_within_day: u64,
608 pub expired_pending_cleanup: u64,
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct TtlStatsResponse {
614 pub namespaces: Vec<TtlStats>,
615 pub total_with_ttl: u64,
616 pub total_expired: u64,
617}
618
619impl DakeraClient {
624 pub async fn ops_stats(&self) -> Result<OpsStats> {
632 let url = format!("{}/v1/ops/stats", self.base_url);
633 let response = self.client.get(&url).send().await?;
634 self.handle_response(response).await
635 }
636
637 pub async fn ops_metrics(&self) -> Result<String> {
642 let url = format!("{}/v1/ops/metrics", self.base_url);
643 let response = self.client.get(&url).send().await?;
644 self.handle_text_response(response).await
645 }
646
647 pub async fn cluster_status(&self) -> Result<ClusterStatus> {
649 let url = format!("{}/admin/cluster/status", self.base_url);
650 let response = self.client.get(&url).send().await?;
651 self.handle_response(response).await
652 }
653
654 pub async fn cluster_nodes(&self) -> Result<NodeListResponse> {
656 let url = format!("{}/admin/cluster/nodes", self.base_url);
657 let response = self.client.get(&url).send().await?;
658 self.handle_response(response).await
659 }
660
661 pub async fn list_namespaces_admin(&self) -> Result<NamespaceListResponse> {
667 let url = format!("{}/admin/namespaces", self.base_url);
668 let response = self.client.get(&url).send().await?;
669 self.handle_response(response).await
670 }
671
672 pub async fn delete_namespace_admin(&self, namespace: &str) -> Result<serde_json::Value> {
674 let url = format!("{}/admin/namespaces/{}", self.base_url, namespace);
675 let response = self.client.delete(&url).send().await?;
676 self.handle_response(response).await
677 }
678
679 pub async fn optimize_namespace(
681 &self,
682 namespace: &str,
683 request: OptimizeRequest,
684 ) -> Result<OptimizeResponse> {
685 let url = format!("{}/admin/namespaces/{}/optimize", self.base_url, namespace);
686 let response = self.client.post(&url).json(&request).send().await?;
687 self.handle_response(response).await
688 }
689
690 pub async fn index_stats(&self) -> Result<IndexStatsResponse> {
696 let url = format!("{}/admin/indexes/stats", self.base_url);
697 let response = self.client.get(&url).send().await?;
698 self.handle_response(response).await
699 }
700
701 pub async fn rebuild_indexes(
703 &self,
704 request: RebuildIndexRequest,
705 ) -> Result<RebuildIndexResponse> {
706 let url = format!("{}/admin/indexes/rebuild", self.base_url);
707 let response = self.client.post(&url).json(&request).send().await?;
708 self.handle_response(response).await
709 }
710
711 pub async fn cache_stats(&self) -> Result<CacheStats> {
717 let url = format!("{}/admin/cache/stats", self.base_url);
718 let response = self.client.get(&url).send().await?;
719 self.handle_response(response).await
720 }
721
722 pub async fn cache_clear(&self, namespace: Option<&str>) -> Result<ClearCacheResponse> {
724 let url = format!("{}/admin/cache/clear", self.base_url);
725 let request = ClearCacheRequest {
726 namespace: namespace.map(|s| s.to_string()),
727 };
728 let response = self.client.post(&url).json(&request).send().await?;
729 self.handle_response(response).await
730 }
731
732 pub async fn get_config(&self) -> Result<RuntimeConfig> {
738 let url = format!("{}/admin/config", self.base_url);
739 let response = self.client.get(&url).send().await?;
740 self.handle_response(response).await
741 }
742
743 pub async fn update_config(
745 &self,
746 updates: HashMap<String, serde_json::Value>,
747 ) -> Result<UpdateConfigResponse> {
748 let url = format!("{}/admin/config", self.base_url);
749 let response = self.client.put(&url).json(&updates).send().await?;
750 self.handle_response(response).await
751 }
752
753 pub async fn get_quotas(&self) -> Result<QuotaListResponse> {
759 let url = format!("{}/admin/quotas", self.base_url);
760 let response = self.client.get(&url).send().await?;
761 self.handle_response(response).await
762 }
763
764 pub async fn get_quota(&self, namespace: &str) -> Result<QuotaStatus> {
766 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
767 let response = self.client.get(&url).send().await?;
768 self.handle_response(response).await
769 }
770
771 pub async fn set_quota(
773 &self,
774 namespace: &str,
775 config: QuotaConfig,
776 ) -> Result<serde_json::Value> {
777 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
778 let request = serde_json::json!({ "config": config });
779 let response = self.client.put(&url).json(&request).send().await?;
780 self.handle_response(response).await
781 }
782
783 pub async fn delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
785 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
786 let response = self.client.delete(&url).send().await?;
787 self.handle_response(response).await
788 }
789
790 pub async fn update_quotas(&self, config: Option<QuotaConfig>) -> Result<serde_json::Value> {
792 let url = format!("{}/admin/quotas/default", self.base_url);
793 let request = serde_json::json!({ "config": config });
794 let response = self.client.put(&url).json(&request).send().await?;
795 self.handle_response(response).await
796 }
797
798 pub async fn slow_queries(
804 &self,
805 limit: Option<usize>,
806 namespace: Option<&str>,
807 query_type: Option<&str>,
808 ) -> Result<SlowQueryListResponse> {
809 let mut url = format!("{}/admin/slow-queries", self.base_url);
810 let mut params = Vec::new();
811 if let Some(l) = limit {
812 params.push(format!("limit={}", l));
813 }
814 if let Some(ns) = namespace {
815 params.push(format!("namespace={}", ns));
816 }
817 if let Some(qt) = query_type {
818 params.push(format!("query_type={}", qt));
819 }
820 if !params.is_empty() {
821 url.push('?');
822 url.push_str(¶ms.join("&"));
823 }
824 let response = self.client.get(&url).send().await?;
825 self.handle_response(response).await
826 }
827
828 pub async fn slow_query_summary(&self) -> Result<serde_json::Value> {
830 let url = format!("{}/admin/slow-queries/summary", self.base_url);
831 let response = self.client.get(&url).send().await?;
832 self.handle_response(response).await
833 }
834
835 pub async fn clear_slow_queries(&self) -> Result<serde_json::Value> {
837 let url = format!("{}/admin/slow-queries", self.base_url);
838 let response = self.client.delete(&url).send().await?;
839 self.handle_response(response).await
840 }
841
842 pub async fn create_backup(
848 &self,
849 request: CreateBackupRequest,
850 ) -> Result<CreateBackupResponse> {
851 let url = format!("{}/admin/backups", self.base_url);
852 let response = self.client.post(&url).json(&request).send().await?;
853 self.handle_response(response).await
854 }
855
856 pub async fn list_backups(&self) -> Result<BackupListResponse> {
858 let url = format!("{}/admin/backups", self.base_url);
859 let response = self.client.get(&url).send().await?;
860 self.handle_response(response).await
861 }
862
863 pub async fn get_backup(&self, backup_id: &str) -> Result<BackupInfo> {
865 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
866 let response = self.client.get(&url).send().await?;
867 self.handle_response(response).await
868 }
869
870 pub async fn restore_backup(
872 &self,
873 request: RestoreBackupRequest,
874 ) -> Result<RestoreBackupResponse> {
875 let url = format!("{}/admin/backups/restore", self.base_url);
876 let response = self.client.post(&url).json(&request).send().await?;
877 self.handle_response(response).await
878 }
879
880 pub async fn delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
882 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
883 let response = self.client.delete(&url).send().await?;
884 self.handle_response(response).await
885 }
886
887 pub async fn ttl_cleanup(&self, namespace: Option<&str>) -> Result<TtlCleanupResponse> {
893 let url = format!("{}/admin/ttl/cleanup", self.base_url);
894 let request = TtlCleanupRequest {
895 namespace: namespace.map(|s| s.to_string()),
896 };
897 let response = self.client.post(&url).json(&request).send().await?;
898 self.handle_response(response).await
899 }
900
901 pub async fn ttl_stats(&self) -> Result<TtlStatsResponse> {
903 let url = format!("{}/admin/ttl/stats", self.base_url);
904 let response = self.client.get(&url).send().await?;
905 self.handle_response(response).await
906 }
907
908 pub async fn autopilot_status(&self) -> Result<AutoPilotStatusResponse> {
914 let url = format!("{}/admin/autopilot/status", self.base_url);
915 let response = self.client.get(&url).send().await?;
916 self.handle_response(response).await
917 }
918
919 pub async fn autopilot_update_config(
923 &self,
924 request: AutoPilotConfigRequest,
925 ) -> Result<AutoPilotConfigResponse> {
926 let url = format!("{}/admin/autopilot/config", self.base_url);
927 let response = self.client.put(&url).json(&request).send().await?;
928 self.handle_response(response).await
929 }
930
931 pub async fn autopilot_trigger(
936 &self,
937 action: AutoPilotTriggerAction,
938 ) -> Result<AutoPilotTriggerResponse> {
939 let url = format!("{}/admin/autopilot/trigger", self.base_url);
940 let request = AutoPilotTriggerRequest { action };
941 let response = self.client.post(&url).json(&request).send().await?;
942 self.handle_response(response).await
943 }
944
945 pub async fn decay_config(&self) -> Result<DecayConfigResponse> {
954 let url = format!("{}/admin/decay/config", self.base_url);
955 let response = self.client.get(&url).send().await?;
956 self.handle_response(response).await
957 }
958
959 pub async fn decay_update_config(
965 &self,
966 request: DecayConfigUpdateRequest,
967 ) -> Result<DecayConfigUpdateResponse> {
968 let url = format!("{}/admin/decay/config", self.base_url);
969 let response = self.client.put(&url).json(&request).send().await?;
970 self.handle_response(response).await
971 }
972
973 pub async fn decay_stats(&self) -> Result<DecayStatsResponse> {
978 let url = format!("{}/admin/decay/stats", self.base_url);
979 let response = self.client.get(&url).send().await?;
980 self.handle_response(response).await
981 }
982
983 pub async fn get_kpis(&self) -> Result<KpiSnapshot> {
993 let url = format!("{}/kpis", self.base_url);
994 let response = self.client.get(&url).send().await?;
995 self.handle_response(response).await
996 }
997
998 pub async fn admin_fulltext_reindex(
1010 &self,
1011 namespace: Option<&str>,
1012 ) -> Result<FulltextReindexResponse> {
1013 let url = format!("{}/admin/fulltext/reindex", self.base_url);
1014 let body = serde_json::json!({ "namespace": namespace });
1015 let response = self.client.post(&url).json(&body).send().await?;
1016 self.handle_response(response).await
1017 }
1018}
1019
1020#[derive(Debug, Clone, Serialize, Deserialize)]
1031pub struct KpiSnapshot {
1032 pub recall_latency_p50_ms: f64,
1034 pub recall_latency_p99_ms: f64,
1036 pub store_latency_p50_ms: f64,
1038 pub api_error_rate_5xx_pct: f64,
1040 pub active_agents_count: u64,
1042 pub session_count_week: u64,
1044 pub cross_agent_network_node_count: u64,
1046 pub memory_retention_7d_pct: f64,
1048}
1049
1050#[derive(Debug, Clone, Serialize, Deserialize)]
1056pub struct FulltextReindexNamespaceResult {
1057 pub namespace: String,
1059 pub vectors_scanned: usize,
1061 pub newly_indexed: usize,
1063 pub already_indexed: usize,
1065 pub parse_failures: usize,
1067}
1068
1069#[derive(Debug, Clone, Serialize, Deserialize)]
1073pub struct FulltextReindexResponse {
1074 pub namespaces_processed: usize,
1076 pub total_indexed: usize,
1078 pub total_skipped: usize,
1080 pub details: Vec<FulltextReindexNamespaceResult>,
1082}