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 configure_ttl(
893 &self,
894 namespace: &str,
895 ttl_seconds: u64,
896 strategy: Option<&str>,
897 ) -> Result<serde_json::Value> {
898 let url = format!("{}/v1/admin/namespaces/{}/ttl", self.base_url, namespace);
899 let mut body = serde_json::json!({ "ttl_seconds": ttl_seconds });
900 if let Some(s) = strategy {
901 body["strategy"] = serde_json::Value::String(s.to_string());
902 }
903 let response = self.client.post(&url).json(&body).send().await?;
904 self.handle_response(response).await
905 }
906
907 pub async fn ttl_cleanup(&self, namespace: Option<&str>) -> Result<TtlCleanupResponse> {
909 let url = format!("{}/admin/ttl/cleanup", self.base_url);
910 let request = TtlCleanupRequest {
911 namespace: namespace.map(|s| s.to_string()),
912 };
913 let response = self.client.post(&url).json(&request).send().await?;
914 self.handle_response(response).await
915 }
916
917 pub async fn ttl_stats(&self) -> Result<TtlStatsResponse> {
919 let url = format!("{}/admin/ttl/stats", self.base_url);
920 let response = self.client.get(&url).send().await?;
921 self.handle_response(response).await
922 }
923
924 pub async fn autopilot_status(&self) -> Result<AutoPilotStatusResponse> {
930 let url = format!("{}/admin/autopilot/status", self.base_url);
931 let response = self.client.get(&url).send().await?;
932 self.handle_response(response).await
933 }
934
935 pub async fn autopilot_update_config(
939 &self,
940 request: AutoPilotConfigRequest,
941 ) -> Result<AutoPilotConfigResponse> {
942 let url = format!("{}/admin/autopilot/config", self.base_url);
943 let response = self.client.put(&url).json(&request).send().await?;
944 self.handle_response(response).await
945 }
946
947 pub async fn autopilot_trigger(
952 &self,
953 action: AutoPilotTriggerAction,
954 ) -> Result<AutoPilotTriggerResponse> {
955 let url = format!("{}/admin/autopilot/trigger", self.base_url);
956 let request = AutoPilotTriggerRequest { action };
957 let response = self.client.post(&url).json(&request).send().await?;
958 self.handle_response(response).await
959 }
960
961 pub async fn decay_config(&self) -> Result<DecayConfigResponse> {
970 let url = format!("{}/admin/decay/config", self.base_url);
971 let response = self.client.get(&url).send().await?;
972 self.handle_response(response).await
973 }
974
975 pub async fn decay_update_config(
981 &self,
982 request: DecayConfigUpdateRequest,
983 ) -> Result<DecayConfigUpdateResponse> {
984 let url = format!("{}/admin/decay/config", self.base_url);
985 let response = self.client.put(&url).json(&request).send().await?;
986 self.handle_response(response).await
987 }
988
989 pub async fn decay_stats(&self) -> Result<DecayStatsResponse> {
994 let url = format!("{}/admin/decay/stats", self.base_url);
995 let response = self.client.get(&url).send().await?;
996 self.handle_response(response).await
997 }
998
999 pub async fn get_kpis(&self) -> Result<KpiSnapshot> {
1009 let url = format!("{}/kpis", self.base_url);
1010 let response = self.client.get(&url).send().await?;
1011 self.handle_response(response).await
1012 }
1013
1014 pub async fn admin_fulltext_reindex(
1026 &self,
1027 namespace: Option<&str>,
1028 ) -> Result<FulltextReindexResponse> {
1029 let url = format!("{}/admin/fulltext/reindex", self.base_url);
1030 let body = serde_json::json!({ "namespace": namespace });
1031 let response = self.client.post(&url).json(&body).send().await?;
1032 self.handle_response(response).await
1033 }
1034
1035 pub async fn admin_cluster_replication(&self) -> Result<crate::types::ReplicationStatus> {
1041 let url = format!("{}/admin/cluster/replication", self.base_url);
1042 let response = self.client.get(&url).send().await?;
1043 self.handle_response(response).await
1044 }
1045
1046 pub async fn admin_list_shards(&self) -> Result<crate::types::ShardListResponse> {
1048 let url = format!("{}/admin/cluster/shards", self.base_url);
1049 let response = self.client.get(&url).send().await?;
1050 self.handle_response(response).await
1051 }
1052
1053 pub async fn admin_rebalance_shards(
1055 &self,
1056 request: crate::types::ShardRebalanceRequest,
1057 ) -> Result<crate::types::ShardRebalanceResponse> {
1058 let url = format!("{}/admin/cluster/shards/rebalance", self.base_url);
1059 let response = self.client.post(&url).json(&request).send().await?;
1060 self.handle_response(response).await
1061 }
1062
1063 pub async fn admin_maintenance_status(&self) -> Result<crate::types::MaintenanceStatus> {
1065 let url = format!("{}/admin/cluster/maintenance", self.base_url);
1066 let response = self.client.get(&url).send().await?;
1067 self.handle_response(response).await
1068 }
1069
1070 pub async fn admin_enable_maintenance(
1072 &self,
1073 request: crate::types::EnableMaintenanceRequest,
1074 ) -> Result<crate::types::MaintenanceStatus> {
1075 let url = format!("{}/admin/cluster/maintenance/enable", self.base_url);
1076 let response = self.client.post(&url).json(&request).send().await?;
1077 self.handle_response(response).await
1078 }
1079
1080 pub async fn admin_disable_maintenance(
1082 &self,
1083 request: crate::types::DisableMaintenanceRequest,
1084 ) -> Result<crate::types::MaintenanceStatus> {
1085 let url = format!("{}/admin/cluster/maintenance/disable", self.base_url);
1086 let response = self.client.post(&url).json(&request).send().await?;
1087 self.handle_response(response).await
1088 }
1089
1090 pub async fn admin_list_quotas(&self) -> Result<crate::types::QuotaListResponse> {
1096 let url = format!("{}/admin/quotas", self.base_url);
1097 let response = self.client.get(&url).send().await?;
1098 self.handle_response(response).await
1099 }
1100
1101 pub async fn admin_get_default_quota(&self) -> Result<crate::types::DefaultQuotaResponse> {
1103 let url = format!("{}/admin/quotas/default", self.base_url);
1104 let response = self.client.get(&url).send().await?;
1105 self.handle_response(response).await
1106 }
1107
1108 pub async fn admin_set_default_quota(
1110 &self,
1111 request: crate::types::SetDefaultQuotaRequest,
1112 ) -> Result<crate::types::SetQuotaResponse> {
1113 let url = format!("{}/admin/quotas/default", self.base_url);
1114 let response = self.client.put(&url).json(&request).send().await?;
1115 self.handle_response(response).await
1116 }
1117
1118 pub async fn admin_get_quota(&self, namespace: &str) -> Result<crate::types::QuotaStatus> {
1120 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
1121 let response = self.client.get(&url).send().await?;
1122 self.handle_response(response).await
1123 }
1124
1125 pub async fn admin_set_quota(
1127 &self,
1128 namespace: &str,
1129 request: crate::types::SetQuotaRequest,
1130 ) -> Result<crate::types::SetQuotaResponse> {
1131 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
1132 let response = self.client.put(&url).json(&request).send().await?;
1133 self.handle_response(response).await
1134 }
1135
1136 pub async fn admin_delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
1138 let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
1139 let response = self.client.delete(&url).send().await?;
1140 self.handle_response(response).await
1141 }
1142
1143 pub async fn admin_check_quota(
1145 &self,
1146 namespace: &str,
1147 request: crate::types::QuotaCheckRequest,
1148 ) -> Result<crate::types::QuotaCheckResult> {
1149 let url = format!("{}/admin/quotas/{}/check", self.base_url, namespace);
1150 let response = self.client.post(&url).json(&request).send().await?;
1151 self.handle_response(response).await
1152 }
1153
1154 pub async fn admin_list_slow_queries(
1160 &self,
1161 namespace: Option<&str>,
1162 query_type: Option<&str>,
1163 limit: Option<u32>,
1164 ) -> Result<Vec<serde_json::Value>> {
1165 let mut url = format!("{}/admin/slow-queries", self.base_url);
1166 let mut params = Vec::new();
1167 if let Some(ns) = namespace {
1168 params.push(format!("namespace={}", ns));
1169 }
1170 if let Some(qt) = query_type {
1171 params.push(format!("query_type={}", qt));
1172 }
1173 if let Some(l) = limit {
1174 params.push(format!("limit={}", l));
1175 }
1176 if !params.is_empty() {
1177 url.push('?');
1178 url.push_str(¶ms.join("&"));
1179 }
1180 let response = self.client.get(&url).send().await?;
1181 self.handle_response(response).await
1182 }
1183
1184 pub async fn admin_slow_query_summary(&self) -> Result<serde_json::Value> {
1186 let url = format!("{}/admin/slow-queries/summary", self.base_url);
1187 let response = self.client.get(&url).send().await?;
1188 self.handle_response(response).await
1189 }
1190
1191 pub async fn admin_clear_slow_queries(
1193 &self,
1194 namespace: Option<&str>,
1195 ) -> Result<serde_json::Value> {
1196 let mut url = format!("{}/admin/slow-queries", self.base_url);
1197 if let Some(ns) = namespace {
1198 url.push_str(&format!("?namespace={}", ns));
1199 }
1200 let response = self.client.delete(&url).send().await?;
1201 self.handle_response(response).await
1202 }
1203
1204 pub async fn admin_update_slow_query_config(
1206 &self,
1207 config: serde_json::Value,
1208 ) -> Result<serde_json::Value> {
1209 let url = format!("{}/admin/slow-queries/config", self.base_url);
1210 let response = self.client.patch(&url).json(&config).send().await?;
1211 self.handle_response(response).await
1212 }
1213
1214 pub async fn admin_list_backups(&self) -> Result<crate::types::BackupListResponse> {
1220 let url = format!("{}/admin/backups", self.base_url);
1221 let response = self.client.get(&url).send().await?;
1222 self.handle_response(response).await
1223 }
1224
1225 pub async fn admin_create_backup(
1227 &self,
1228 request: crate::types::CreateBackupRequest,
1229 ) -> Result<crate::types::CreateBackupResponse> {
1230 let url = format!("{}/admin/backups", self.base_url);
1231 let response = self.client.post(&url).json(&request).send().await?;
1232 self.handle_response(response).await
1233 }
1234
1235 pub async fn admin_get_backup(&self, backup_id: &str) -> Result<crate::types::AdminBackupInfo> {
1237 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
1238 let response = self.client.get(&url).send().await?;
1239 self.handle_response(response).await
1240 }
1241
1242 pub async fn admin_delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
1244 let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
1245 let response = self.client.delete(&url).send().await?;
1246 self.handle_response(response).await
1247 }
1248
1249 pub async fn admin_get_backup_schedule(&self) -> Result<crate::types::BackupSchedule> {
1251 let url = format!("{}/admin/backups/schedule", self.base_url);
1252 let response = self.client.get(&url).send().await?;
1253 self.handle_response(response).await
1254 }
1255
1256 pub async fn admin_update_backup_schedule(
1258 &self,
1259 request: crate::types::UpdateBackupScheduleRequest,
1260 ) -> Result<crate::types::BackupSchedule> {
1261 let url = format!("{}/admin/backups/schedule", self.base_url);
1262 let response = self.client.post(&url).json(&request).send().await?;
1263 self.handle_response(response).await
1264 }
1265
1266 pub async fn admin_restore_backup(
1268 &self,
1269 request: crate::types::RestoreBackupRequest,
1270 ) -> Result<crate::types::RestoreBackupResponse> {
1271 let url = format!("{}/admin/backups/restore", self.base_url);
1272 let response = self.client.post(&url).json(&request).send().await?;
1273 self.handle_response(response).await
1274 }
1275
1276 pub async fn admin_get_restore_status(
1278 &self,
1279 restore_id: &str,
1280 ) -> Result<crate::types::RestoreBackupResponse> {
1281 let url = format!("{}/admin/backups/restore/{}", self.base_url, restore_id);
1282 let response = self.client.get(&url).send().await?;
1283 self.handle_response(response).await
1284 }
1285
1286 pub async fn ops_diagnostics(&self) -> Result<serde_json::Value> {
1292 let url = format!("{}/ops/diagnostics", self.base_url);
1293 let response = self.client.get(&url).send().await?;
1294 self.handle_response(response).await
1295 }
1296
1297 pub async fn ops_list_jobs(&self) -> Result<Vec<crate::types::JobInfo>> {
1299 let url = format!("{}/ops/jobs", self.base_url);
1300 let response = self.client.get(&url).send().await?;
1301 self.handle_response(response).await
1302 }
1303
1304 pub async fn ops_get_job(&self, job_id: &str) -> Result<crate::types::JobInfo> {
1306 let url = format!("{}/ops/jobs/{}", self.base_url, job_id);
1307 let response = self.client.get(&url).send().await?;
1308 self.handle_response(response).await
1309 }
1310
1311 pub async fn ops_compact(
1313 &self,
1314 request: crate::types::CompactionRequest,
1315 ) -> Result<crate::types::CompactionResponse> {
1316 let url = format!("{}/ops/compact", self.base_url);
1317 let response = self.client.post(&url).json(&request).send().await?;
1318 self.handle_response(response).await
1319 }
1320
1321 pub async fn ops_shutdown(&self) -> Result<serde_json::Value> {
1323 let url = format!("{}/ops/shutdown", self.base_url);
1324 let response = self.client.post(&url).send().await?;
1325 self.handle_response(response).await
1326 }
1327
1328 pub async fn download_backup(&self, backup_id: &str) -> Result<Vec<u8>> {
1334 let url = format!("{}/admin/backups/{}/download", self.base_url, backup_id);
1335 let response = self.client.get(&url).send().await?;
1336 if !response.status().is_success() {
1337 let status = response.status();
1338 let body = response.text().await.unwrap_or_default();
1339 return Err(crate::error::ClientError::Server {
1340 status: status.as_u16(),
1341 message: body,
1342 code: None,
1343 });
1344 }
1345 Ok(response.bytes().await?.to_vec())
1346 }
1347
1348 pub async fn upload_backup(&self, data: Vec<u8>) -> Result<crate::types::CreateBackupResponse> {
1350 let url = format!("{}/admin/backups/upload", self.base_url);
1351 let response = self
1352 .client
1353 .post(&url)
1354 .header("Content-Type", "application/gzip")
1355 .body(data)
1356 .send()
1357 .await?;
1358 self.handle_response(response).await
1359 }
1360
1361 pub async fn storage_tier_overview(&self) -> Result<crate::types::StorageTierOverview> {
1367 let url = format!("{}/admin/storage/tiers", self.base_url);
1368 let response = self.client.get(&url).send().await?;
1369 self.handle_response(response).await
1370 }
1371
1372 pub async fn background_activity(&self) -> Result<serde_json::Value> {
1378 let url = format!("{}/admin/background-activity", self.base_url);
1379 let response = self.client.get(&url).send().await?;
1380 self.handle_response(response).await
1381 }
1382
1383 pub async fn memory_type_stats(&self) -> Result<crate::types::MemoryTypeStatsResponse> {
1389 let url = format!("{}/admin/memory-type-stats", self.base_url);
1390 let response = self.client.get(&url).send().await?;
1391 self.handle_response(response).await
1392 }
1393
1394 pub async fn migrate_namespace_dimensions(
1400 &self,
1401 request: crate::types::MigrateNamespaceDimensionsRequest,
1402 ) -> Result<crate::types::MigrateDimensionsResponse> {
1403 let url = format!("{}/admin/namespaces/migrate-dimensions", self.base_url);
1404 let response = self.client.post(&url).json(&request).send().await?;
1405 self.handle_response(response).await
1406 }
1407}
1408
1409#[derive(Debug, Clone, Serialize, Deserialize)]
1420pub struct KpiSnapshot {
1421 pub recall_latency_p50_ms: f64,
1423 pub recall_latency_p99_ms: f64,
1425 pub store_latency_p50_ms: f64,
1427 pub api_error_rate_5xx_pct: f64,
1429 pub active_agents_count: u64,
1431 pub session_count_week: u64,
1433 pub cross_agent_network_node_count: u64,
1435 pub memory_retention_7d_pct: f64,
1437}
1438
1439#[derive(Debug, Clone, Serialize, Deserialize)]
1445pub struct FulltextReindexNamespaceResult {
1446 pub namespace: String,
1448 pub vectors_scanned: usize,
1450 pub newly_indexed: usize,
1452 pub already_indexed: usize,
1454 pub parse_failures: usize,
1456}
1457
1458#[derive(Debug, Clone, Serialize, Deserialize)]
1462pub struct FulltextReindexResponse {
1463 pub namespaces_processed: usize,
1465 pub total_indexed: usize,
1467 pub total_skipped: usize,
1469 pub details: Vec<FulltextReindexNamespaceResult>,
1471}