Skip to main content

dakera_client/
types.rs

1//! Types for the Dakera client SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6// ============================================================================
7// Retry & Timeout Configuration
8// ============================================================================
9
10/// Configuration for request retry behavior with exponential backoff.
11#[derive(Debug, Clone)]
12pub struct RetryConfig {
13    /// Maximum number of retry attempts (default: 3).
14    pub max_retries: u32,
15    /// Base delay before the first retry (default: 100ms).
16    pub base_delay: std::time::Duration,
17    /// Maximum delay between retries (default: 60s).
18    pub max_delay: std::time::Duration,
19    /// Whether to add random jitter to backoff delay (default: true).
20    pub jitter: bool,
21}
22
23impl Default for RetryConfig {
24    fn default() -> Self {
25        Self {
26            max_retries: 3,
27            base_delay: std::time::Duration::from_millis(100),
28            max_delay: std::time::Duration::from_secs(60),
29            jitter: true,
30        }
31    }
32}
33
34// ============================================================================
35// OPS-1: Rate-Limit Headers
36// ============================================================================
37
38/// Rate-limit and quota headers present on every API response (OPS-1).
39///
40/// Fields are `None` when the server does not include the header (e.g.
41/// non-namespaced endpoints where quota does not apply).
42#[derive(Debug, Clone, Default)]
43pub struct RateLimitHeaders {
44    /// `X-RateLimit-Limit` — max requests allowed in the current window.
45    pub limit: Option<u64>,
46    /// `X-RateLimit-Remaining` — requests left in the current window.
47    pub remaining: Option<u64>,
48    /// `X-RateLimit-Reset` — Unix timestamp (seconds) when the window resets.
49    pub reset: Option<u64>,
50    /// `X-Quota-Used` — namespace vectors / storage consumed.
51    pub quota_used: Option<u64>,
52    /// `X-Quota-Limit` — namespace quota ceiling.
53    pub quota_limit: Option<u64>,
54}
55
56impl RateLimitHeaders {
57    /// Parse rate-limit headers from a `reqwest::Response`.
58    pub fn from_response(response: &reqwest::Response) -> Self {
59        let headers = response.headers();
60        fn parse(h: &reqwest::header::HeaderMap, name: &str) -> Option<u64> {
61            h.get(name)
62                .and_then(|v| v.to_str().ok())
63                .and_then(|s| s.parse().ok())
64        }
65        Self {
66            limit: parse(headers, "X-RateLimit-Limit"),
67            remaining: parse(headers, "X-RateLimit-Remaining"),
68            reset: parse(headers, "X-RateLimit-Reset"),
69            quota_used: parse(headers, "X-Quota-Used"),
70            quota_limit: parse(headers, "X-Quota-Limit"),
71        }
72    }
73}
74
75// ============================================================================
76// Health & Status Types
77// ============================================================================
78
79/// Health check response
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct HealthResponse {
82    /// Overall health status
83    pub healthy: bool,
84    /// Service version
85    pub version: Option<String>,
86    /// Uptime in seconds
87    pub uptime_seconds: Option<u64>,
88}
89
90/// Readiness check response
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ReadinessResponse {
93    /// Is the service ready to accept requests
94    pub ready: bool,
95    /// Component status details
96    pub components: Option<HashMap<String, bool>>,
97}
98
99// ============================================================================
100// Namespace Types
101// ============================================================================
102
103/// Namespace information
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct NamespaceInfo {
106    /// Namespace name
107    #[serde(alias = "namespace")]
108    pub name: String,
109    /// Number of vectors in the namespace
110    pub vector_count: u64,
111    /// Vector dimensions
112    #[serde(alias = "dimension")]
113    pub dimensions: Option<u32>,
114    /// Index type used
115    pub index_type: Option<String>,
116}
117
118/// List namespaces response
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ListNamespacesResponse {
121    /// List of namespace names
122    pub namespaces: Vec<String>,
123}
124
125// ============================================================================
126// Vector Types
127// ============================================================================
128
129/// A vector with ID and optional metadata
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Vector {
132    /// Unique vector identifier
133    pub id: String,
134    /// Vector values (embeddings)
135    pub values: Vec<f32>,
136    /// Optional metadata
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub metadata: Option<HashMap<String, serde_json::Value>>,
139}
140
141impl Vector {
142    /// Create a new vector with just ID and values
143    pub fn new(id: impl Into<String>, values: Vec<f32>) -> Self {
144        Self {
145            id: id.into(),
146            values,
147            metadata: None,
148        }
149    }
150
151    /// Create a new vector with metadata
152    pub fn with_metadata(
153        id: impl Into<String>,
154        values: Vec<f32>,
155        metadata: HashMap<String, serde_json::Value>,
156    ) -> Self {
157        Self {
158            id: id.into(),
159            values,
160            metadata: Some(metadata),
161        }
162    }
163}
164
165/// Upsert vectors request
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct UpsertRequest {
168    /// Vectors to upsert
169    pub vectors: Vec<Vector>,
170}
171
172impl UpsertRequest {
173    /// Create a new upsert request with a single vector
174    pub fn single(vector: Vector) -> Self {
175        Self {
176            vectors: vec![vector],
177        }
178    }
179
180    /// Create a new upsert request with multiple vectors
181    pub fn batch(vectors: Vec<Vector>) -> Self {
182        Self { vectors }
183    }
184}
185
186/// Upsert response
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct UpsertResponse {
189    /// Number of vectors upserted
190    pub upserted_count: u64,
191}
192
193/// Column-based upsert request (Turbopuffer-inspired)
194///
195/// This format is more efficient for bulk upserts as it avoids repeating
196/// field names for each vector. All arrays must have equal length.
197///
198/// # Example
199///
200/// ```rust
201/// use dakera_client::ColumnUpsertRequest;
202/// use std::collections::HashMap;
203///
204/// let request = ColumnUpsertRequest::new(
205///     vec!["id1".to_string(), "id2".to_string()],
206///     vec![vec![0.1, 0.2, 0.3], vec![0.4, 0.5, 0.6]],
207/// );
208/// ```
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ColumnUpsertRequest {
211    /// Array of vector IDs (required)
212    pub ids: Vec<String>,
213    /// Array of vectors (required for vector namespaces)
214    pub vectors: Vec<Vec<f32>>,
215    /// Additional attributes as columns (optional)
216    /// Each key is an attribute name, value is array of attribute values
217    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
218    pub attributes: HashMap<String, Vec<serde_json::Value>>,
219    /// TTL in seconds for all vectors (optional)
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub ttl_seconds: Option<u64>,
222    /// Expected dimension (optional, for validation)
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub dimension: Option<usize>,
225}
226
227impl ColumnUpsertRequest {
228    /// Create a new column upsert request
229    pub fn new(ids: Vec<String>, vectors: Vec<Vec<f32>>) -> Self {
230        Self {
231            ids,
232            vectors,
233            attributes: HashMap::new(),
234            ttl_seconds: None,
235            dimension: None,
236        }
237    }
238
239    /// Add an attribute column
240    pub fn with_attribute(
241        mut self,
242        name: impl Into<String>,
243        values: Vec<serde_json::Value>,
244    ) -> Self {
245        self.attributes.insert(name.into(), values);
246        self
247    }
248
249    /// Set TTL for all vectors
250    pub fn with_ttl(mut self, seconds: u64) -> Self {
251        self.ttl_seconds = Some(seconds);
252        self
253    }
254
255    /// Set expected dimension for validation
256    pub fn with_dimension(mut self, dim: usize) -> Self {
257        self.dimension = Some(dim);
258        self
259    }
260
261    /// Get the number of vectors in this request
262    pub fn len(&self) -> usize {
263        self.ids.len()
264    }
265
266    /// Check if the request is empty
267    pub fn is_empty(&self) -> bool {
268        self.ids.is_empty()
269    }
270}
271
272/// Delete vectors request
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DeleteRequest {
275    /// Vector IDs to delete
276    pub ids: Vec<String>,
277}
278
279impl DeleteRequest {
280    /// Create a delete request for a single ID
281    pub fn single(id: impl Into<String>) -> Self {
282        Self {
283            ids: vec![id.into()],
284        }
285    }
286
287    /// Create a delete request for multiple IDs
288    pub fn batch(ids: Vec<String>) -> Self {
289        Self { ids }
290    }
291}
292
293/// Delete response
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct DeleteResponse {
296    /// Number of vectors deleted
297    pub deleted_count: u64,
298}
299
300// ============================================================================
301// Query Types
302// ============================================================================
303
304/// Read consistency level for queries (Turbopuffer-inspired)
305#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
306#[serde(rename_all = "snake_case")]
307pub enum ReadConsistency {
308    /// Always read from primary/leader node - guarantees latest data
309    Strong,
310    /// Read from any replica - may return slightly stale data but faster
311    #[default]
312    Eventual,
313    /// Read from replicas within staleness bounds
314    #[serde(rename = "bounded_staleness")]
315    BoundedStaleness,
316}
317
318/// Configuration for bounded staleness reads
319#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
320pub struct StalenessConfig {
321    /// Maximum acceptable staleness in milliseconds
322    #[serde(default = "default_max_staleness_ms")]
323    pub max_staleness_ms: u64,
324}
325
326fn default_max_staleness_ms() -> u64 {
327    5000 // 5 seconds default
328}
329
330impl StalenessConfig {
331    /// Create a new staleness config with specified max staleness
332    pub fn new(max_staleness_ms: u64) -> Self {
333        Self { max_staleness_ms }
334    }
335}
336
337/// Distance metric for similarity search
338#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
339#[serde(rename_all = "snake_case")]
340pub enum DistanceMetric {
341    /// Cosine similarity (default)
342    #[default]
343    Cosine,
344    /// Euclidean distance
345    Euclidean,
346    /// Dot product
347    DotProduct,
348}
349
350/// Query request for vector similarity search
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct QueryRequest {
353    /// Query vector
354    pub vector: Vec<f32>,
355    /// Number of results to return
356    pub top_k: u32,
357    /// Distance metric to use
358    #[serde(default)]
359    pub distance_metric: DistanceMetric,
360    /// Optional filter expression
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub filter: Option<serde_json::Value>,
363    /// Whether to include metadata in results
364    #[serde(default = "default_true")]
365    pub include_metadata: bool,
366    /// Whether to include vector values in results
367    #[serde(default)]
368    pub include_vectors: bool,
369    /// Read consistency level
370    #[serde(default)]
371    pub consistency: ReadConsistency,
372    /// Staleness configuration for bounded staleness reads
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub staleness_config: Option<StalenessConfig>,
375}
376
377fn default_true() -> bool {
378    true
379}
380
381impl QueryRequest {
382    /// Create a new query request
383    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
384        Self {
385            vector,
386            top_k,
387            distance_metric: DistanceMetric::default(),
388            filter: None,
389            include_metadata: true,
390            include_vectors: false,
391            consistency: ReadConsistency::default(),
392            staleness_config: None,
393        }
394    }
395
396    /// Add a filter to the query
397    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
398        self.filter = Some(filter);
399        self
400    }
401
402    /// Set whether to include metadata
403    pub fn include_metadata(mut self, include: bool) -> Self {
404        self.include_metadata = include;
405        self
406    }
407
408    /// Set whether to include vector values
409    pub fn include_vectors(mut self, include: bool) -> Self {
410        self.include_vectors = include;
411        self
412    }
413
414    /// Set distance metric
415    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
416        self.distance_metric = metric;
417        self
418    }
419
420    /// Set read consistency level
421    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
422        self.consistency = consistency;
423        self
424    }
425
426    /// Set bounded staleness with max staleness in ms
427    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
428        self.consistency = ReadConsistency::BoundedStaleness;
429        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
430        self
431    }
432
433    /// Use strong consistency (always read from primary)
434    pub fn with_strong_consistency(mut self) -> Self {
435        self.consistency = ReadConsistency::Strong;
436        self
437    }
438}
439
440/// A match result from a query
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct Match {
443    /// Vector ID
444    pub id: String,
445    /// Similarity score
446    pub score: f32,
447    /// Optional metadata
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub metadata: Option<HashMap<String, serde_json::Value>>,
450}
451
452/// Query response
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct QueryResponse {
455    /// Matched vectors
456    pub matches: Vec<Match>,
457}
458
459// ============================================================================
460// Full-Text Search Types
461// ============================================================================
462
463/// A document for full-text indexing
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct Document {
466    /// Document ID
467    pub id: String,
468    /// Document text content
469    pub text: String,
470    /// Optional metadata
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub metadata: Option<HashMap<String, serde_json::Value>>,
473}
474
475impl Document {
476    /// Create a new document
477    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
478        Self {
479            id: id.into(),
480            text: text.into(),
481            metadata: None,
482        }
483    }
484
485    /// Create a new document with metadata
486    pub fn with_metadata(
487        id: impl Into<String>,
488        text: impl Into<String>,
489        metadata: HashMap<String, serde_json::Value>,
490    ) -> Self {
491        Self {
492            id: id.into(),
493            text: text.into(),
494            metadata: Some(metadata),
495        }
496    }
497}
498
499/// Index documents request
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct IndexDocumentsRequest {
502    /// Documents to index
503    pub documents: Vec<Document>,
504}
505
506/// Index documents response
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct IndexDocumentsResponse {
509    /// Number of documents indexed
510    pub indexed_count: u64,
511}
512
513/// Full-text search request
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct FullTextSearchRequest {
516    /// Search query
517    pub query: String,
518    /// Maximum number of results
519    pub top_k: u32,
520    /// Optional filter
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub filter: Option<serde_json::Value>,
523}
524
525impl FullTextSearchRequest {
526    /// Create a new full-text search request
527    pub fn new(query: impl Into<String>, top_k: u32) -> Self {
528        Self {
529            query: query.into(),
530            top_k,
531            filter: None,
532        }
533    }
534
535    /// Add a filter to the search
536    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
537        self.filter = Some(filter);
538        self
539    }
540}
541
542/// Full-text search result
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct FullTextMatch {
545    /// Document ID
546    pub id: String,
547    /// BM25 score
548    pub score: f32,
549    /// Document text
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub text: Option<String>,
552    /// Optional metadata
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub metadata: Option<HashMap<String, serde_json::Value>>,
555}
556
557/// Full-text search response
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct FullTextSearchResponse {
560    /// Matched documents
561    pub matches: Vec<FullTextMatch>,
562}
563
564/// Full-text index statistics
565#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct FullTextStats {
567    /// Number of documents indexed
568    pub document_count: u64,
569    /// Number of unique terms
570    pub term_count: u64,
571}
572
573// ============================================================================
574// Hybrid Search Types
575// ============================================================================
576
577/// Hybrid search request combining vector and full-text search.
578///
579/// When `vector` is `None` the server falls back to BM25-only full-text search.
580/// When provided, results are blended with vector similarity according to `vector_weight`.
581#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct HybridSearchRequest {
583    /// Optional query vector. Omit for BM25-only search.
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub vector: Option<Vec<f32>>,
586    /// Text query
587    pub text: String,
588    /// Number of results to return
589    pub top_k: u32,
590    /// Weight for vector search (0.0-1.0)
591    #[serde(default = "default_vector_weight")]
592    pub vector_weight: f32,
593    /// Optional filter
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub filter: Option<serde_json::Value>,
596}
597
598fn default_vector_weight() -> f32 {
599    0.5
600}
601
602impl HybridSearchRequest {
603    /// Create a new hybrid search request with a query vector (hybrid mode).
604    pub fn new(vector: Vec<f32>, text: impl Into<String>, top_k: u32) -> Self {
605        Self {
606            vector: Some(vector),
607            text: text.into(),
608            top_k,
609            vector_weight: 0.5,
610            filter: None,
611        }
612    }
613
614    /// Create a BM25-only full-text search request (no vector required).
615    pub fn text_only(text: impl Into<String>, top_k: u32) -> Self {
616        Self {
617            vector: None,
618            text: text.into(),
619            top_k,
620            vector_weight: 0.5,
621            filter: None,
622        }
623    }
624
625    /// Set the vector weight (text weight is 1.0 - vector_weight)
626    pub fn with_vector_weight(mut self, weight: f32) -> Self {
627        self.vector_weight = weight.clamp(0.0, 1.0);
628        self
629    }
630
631    /// Add a filter to the search
632    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
633        self.filter = Some(filter);
634        self
635    }
636}
637
638/// Hybrid search response
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct HybridSearchResponse {
641    /// Matched results
642    pub matches: Vec<Match>,
643}
644
645// ============================================================================
646// Operations Types
647// ============================================================================
648
649/// System diagnostics
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct SystemDiagnostics {
652    /// System information
653    pub system: SystemInfo,
654    /// Resource usage
655    pub resources: ResourceUsage,
656    /// Component health
657    pub components: ComponentHealth,
658    /// Number of active jobs
659    pub active_jobs: u64,
660}
661
662/// System information
663#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct SystemInfo {
665    /// Dakera version
666    pub version: String,
667    /// Rust version
668    pub rust_version: String,
669    /// Uptime in seconds
670    pub uptime_seconds: u64,
671    /// Process ID
672    pub pid: u32,
673}
674
675/// Resource usage metrics
676#[derive(Debug, Clone, Serialize, Deserialize)]
677pub struct ResourceUsage {
678    /// Memory usage in bytes
679    pub memory_bytes: u64,
680    /// Thread count
681    pub thread_count: u64,
682    /// Open file descriptors
683    pub open_fds: u64,
684    /// CPU usage percentage
685    pub cpu_percent: Option<f64>,
686}
687
688/// Component health status
689#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct ComponentHealth {
691    /// Storage health
692    pub storage: HealthStatus,
693    /// Search engine health
694    pub search_engine: HealthStatus,
695    /// Cache health
696    pub cache: HealthStatus,
697    /// gRPC health
698    pub grpc: HealthStatus,
699}
700
701/// Health status for a component
702#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct HealthStatus {
704    /// Is the component healthy
705    pub healthy: bool,
706    /// Status message
707    pub message: String,
708    /// Last check timestamp
709    pub last_check: u64,
710}
711
712/// Background job information
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct JobInfo {
715    /// Job ID
716    pub id: String,
717    /// Job type
718    pub job_type: String,
719    /// Current status
720    pub status: String,
721    /// Creation timestamp
722    pub created_at: u64,
723    /// Start timestamp
724    pub started_at: Option<u64>,
725    /// Completion timestamp
726    pub completed_at: Option<u64>,
727    /// Progress percentage
728    pub progress: u8,
729    /// Status message
730    pub message: Option<String>,
731}
732
733/// Compaction request
734#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct CompactionRequest {
736    /// Namespace to compact (None = all)
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub namespace: Option<String>,
739    /// Force compaction
740    #[serde(default)]
741    pub force: bool,
742}
743
744/// Compaction response
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct CompactionResponse {
747    /// Job ID for tracking
748    pub job_id: String,
749    /// Status message
750    pub message: String,
751}
752
753// ============================================================================
754// Cache Warming Types (Turbopuffer-inspired)
755// ============================================================================
756
757/// Priority level for cache warming operations
758#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
759#[serde(rename_all = "snake_case")]
760pub enum WarmingPriority {
761    /// Highest priority - warm immediately, preempt other operations
762    Critical,
763    /// High priority - warm soon
764    High,
765    /// Normal priority (default)
766    #[default]
767    Normal,
768    /// Low priority - warm when resources available
769    Low,
770    /// Background priority - warm during idle time only
771    Background,
772}
773
774/// Target cache tier for warming
775#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
776#[serde(rename_all = "snake_case")]
777pub enum WarmingTargetTier {
778    /// L1 in-memory cache (Moka) - fastest, limited size
779    L1,
780    /// L2 local disk cache (RocksDB) - larger, persistent
781    #[default]
782    L2,
783    /// Both L1 and L2 caches
784    Both,
785}
786
787/// Access pattern hint for cache optimization
788#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
789#[serde(rename_all = "snake_case")]
790pub enum AccessPatternHint {
791    /// Random access pattern
792    #[default]
793    Random,
794    /// Sequential access pattern
795    Sequential,
796    /// Temporal locality (recently accessed items accessed again)
797    Temporal,
798    /// Spatial locality (nearby items accessed together)
799    Spatial,
800}
801
802/// Cache warming request with priority hints
803#[derive(Debug, Clone, Serialize, Deserialize)]
804pub struct WarmCacheRequest {
805    /// Namespace to warm
806    pub namespace: String,
807    /// Specific vector IDs to warm (None = all)
808    #[serde(skip_serializing_if = "Option::is_none")]
809    pub vector_ids: Option<Vec<String>>,
810    /// Warming priority level
811    #[serde(default)]
812    pub priority: WarmingPriority,
813    /// Target cache tier
814    #[serde(default)]
815    pub target_tier: WarmingTargetTier,
816    /// Run warming in background (non-blocking)
817    #[serde(default)]
818    pub background: bool,
819    /// TTL hint in seconds
820    #[serde(skip_serializing_if = "Option::is_none")]
821    pub ttl_hint_seconds: Option<u64>,
822    /// Access pattern hint for optimization
823    #[serde(default)]
824    pub access_pattern: AccessPatternHint,
825    /// Maximum vectors to warm
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub max_vectors: Option<usize>,
828}
829
830impl WarmCacheRequest {
831    /// Create a new cache warming request for a namespace
832    pub fn new(namespace: impl Into<String>) -> Self {
833        Self {
834            namespace: namespace.into(),
835            vector_ids: None,
836            priority: WarmingPriority::default(),
837            target_tier: WarmingTargetTier::default(),
838            background: false,
839            ttl_hint_seconds: None,
840            access_pattern: AccessPatternHint::default(),
841            max_vectors: None,
842        }
843    }
844
845    /// Warm specific vector IDs
846    pub fn with_vector_ids(mut self, ids: Vec<String>) -> Self {
847        self.vector_ids = Some(ids);
848        self
849    }
850
851    /// Set warming priority
852    pub fn with_priority(mut self, priority: WarmingPriority) -> Self {
853        self.priority = priority;
854        self
855    }
856
857    /// Set target cache tier
858    pub fn with_target_tier(mut self, tier: WarmingTargetTier) -> Self {
859        self.target_tier = tier;
860        self
861    }
862
863    /// Run warming in background
864    pub fn in_background(mut self) -> Self {
865        self.background = true;
866        self
867    }
868
869    /// Set TTL hint
870    pub fn with_ttl(mut self, seconds: u64) -> Self {
871        self.ttl_hint_seconds = Some(seconds);
872        self
873    }
874
875    /// Set access pattern hint
876    pub fn with_access_pattern(mut self, pattern: AccessPatternHint) -> Self {
877        self.access_pattern = pattern;
878        self
879    }
880
881    /// Limit number of vectors to warm
882    pub fn with_max_vectors(mut self, max: usize) -> Self {
883        self.max_vectors = Some(max);
884        self
885    }
886}
887
888/// Cache warming response
889#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct WarmCacheResponse {
891    /// Operation success
892    pub success: bool,
893    /// Number of entries warmed
894    pub entries_warmed: u64,
895    /// Number of entries already warm (skipped)
896    pub entries_skipped: u64,
897    /// Job ID for tracking background operations
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub job_id: Option<String>,
900    /// Status message
901    pub message: String,
902    /// Estimated completion time for background jobs (ISO 8601)
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub estimated_completion: Option<String>,
905    /// Target tier that was warmed
906    pub target_tier: WarmingTargetTier,
907    /// Priority that was used
908    pub priority: WarmingPriority,
909    /// Bytes warmed (approximate)
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub bytes_warmed: Option<u64>,
912}
913
914// ============================================================================
915// Export Types (Turbopuffer-inspired)
916// ============================================================================
917
918/// Request to export vectors from a namespace with pagination
919#[derive(Debug, Clone, Serialize, Deserialize)]
920pub struct ExportRequest {
921    /// Maximum number of vectors to return per page (default: 1000, max: 10000)
922    #[serde(default = "default_export_top_k")]
923    pub top_k: usize,
924    /// Cursor for pagination - the last vector ID from previous page
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub cursor: Option<String>,
927    /// Whether to include vector values in the response (default: true)
928    #[serde(default = "default_true")]
929    pub include_vectors: bool,
930    /// Whether to include metadata in the response (default: true)
931    #[serde(default = "default_true")]
932    pub include_metadata: bool,
933}
934
935fn default_export_top_k() -> usize {
936    1000
937}
938
939impl Default for ExportRequest {
940    fn default() -> Self {
941        Self {
942            top_k: 1000,
943            cursor: None,
944            include_vectors: true,
945            include_metadata: true,
946        }
947    }
948}
949
950impl ExportRequest {
951    /// Create a new export request with default settings
952    pub fn new() -> Self {
953        Self::default()
954    }
955
956    /// Set the maximum number of vectors to return per page
957    pub fn with_top_k(mut self, top_k: usize) -> Self {
958        self.top_k = top_k;
959        self
960    }
961
962    /// Set the pagination cursor
963    pub fn with_cursor(mut self, cursor: impl Into<String>) -> Self {
964        self.cursor = Some(cursor.into());
965        self
966    }
967
968    /// Set whether to include vector values
969    pub fn include_vectors(mut self, include: bool) -> Self {
970        self.include_vectors = include;
971        self
972    }
973
974    /// Set whether to include metadata
975    pub fn include_metadata(mut self, include: bool) -> Self {
976        self.include_metadata = include;
977        self
978    }
979}
980
981/// A single exported vector record
982#[derive(Debug, Clone, Serialize, Deserialize)]
983pub struct ExportedVector {
984    /// Vector ID
985    pub id: String,
986    /// Vector values (optional based on include_vectors)
987    #[serde(skip_serializing_if = "Option::is_none")]
988    pub values: Option<Vec<f32>>,
989    /// Metadata (optional based on include_metadata)
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub metadata: Option<serde_json::Value>,
992    /// TTL in seconds if set
993    #[serde(skip_serializing_if = "Option::is_none")]
994    pub ttl_seconds: Option<u64>,
995}
996
997/// Response from export operation
998#[derive(Debug, Clone, Serialize, Deserialize)]
999pub struct ExportResponse {
1000    /// Exported vectors for this page
1001    pub vectors: Vec<ExportedVector>,
1002    /// Cursor for next page (None if this is the last page)
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub next_cursor: Option<String>,
1005    /// Total vectors in namespace (for progress tracking)
1006    pub total_count: usize,
1007    /// Number of vectors returned in this page
1008    pub returned_count: usize,
1009}
1010
1011// ============================================================================
1012// Batch Query Types
1013// ============================================================================
1014
1015/// A single query within a batch request
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017pub struct BatchQueryItem {
1018    /// Unique identifier for this query within the batch
1019    #[serde(skip_serializing_if = "Option::is_none")]
1020    pub id: Option<String>,
1021    /// The query vector
1022    pub vector: Vec<f32>,
1023    /// Number of results to return
1024    #[serde(default = "default_batch_top_k")]
1025    pub top_k: u32,
1026    /// Optional filter expression
1027    #[serde(skip_serializing_if = "Option::is_none")]
1028    pub filter: Option<serde_json::Value>,
1029    /// Whether to include metadata in results
1030    #[serde(default)]
1031    pub include_metadata: bool,
1032    /// Read consistency level
1033    #[serde(default)]
1034    pub consistency: ReadConsistency,
1035    /// Staleness configuration for bounded staleness reads
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub staleness_config: Option<StalenessConfig>,
1038}
1039
1040fn default_batch_top_k() -> u32 {
1041    10
1042}
1043
1044impl BatchQueryItem {
1045    /// Create a new batch query item
1046    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
1047        Self {
1048            id: None,
1049            vector,
1050            top_k,
1051            filter: None,
1052            include_metadata: true,
1053            consistency: ReadConsistency::default(),
1054            staleness_config: None,
1055        }
1056    }
1057
1058    /// Set a unique identifier for this query
1059    pub fn with_id(mut self, id: impl Into<String>) -> Self {
1060        self.id = Some(id.into());
1061        self
1062    }
1063
1064    /// Add a filter to the query
1065    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1066        self.filter = Some(filter);
1067        self
1068    }
1069
1070    /// Set whether to include metadata
1071    pub fn include_metadata(mut self, include: bool) -> Self {
1072        self.include_metadata = include;
1073        self
1074    }
1075
1076    /// Set read consistency level
1077    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1078        self.consistency = consistency;
1079        self
1080    }
1081
1082    /// Set bounded staleness with max staleness in ms
1083    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
1084        self.consistency = ReadConsistency::BoundedStaleness;
1085        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
1086        self
1087    }
1088}
1089
1090/// Batch query request - execute multiple queries in parallel
1091#[derive(Debug, Clone, Serialize, Deserialize)]
1092pub struct BatchQueryRequest {
1093    /// List of queries to execute
1094    pub queries: Vec<BatchQueryItem>,
1095}
1096
1097impl BatchQueryRequest {
1098    /// Create a new batch query request
1099    pub fn new(queries: Vec<BatchQueryItem>) -> Self {
1100        Self { queries }
1101    }
1102
1103    /// Create a batch query request from a single query
1104    pub fn single(query: BatchQueryItem) -> Self {
1105        Self {
1106            queries: vec![query],
1107        }
1108    }
1109}
1110
1111/// Results for a single query within a batch
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113pub struct BatchQueryResult {
1114    /// The query identifier (if provided in request)
1115    #[serde(skip_serializing_if = "Option::is_none")]
1116    pub id: Option<String>,
1117    /// Query results (empty if an error occurred)
1118    pub results: Vec<Match>,
1119    /// Query execution time in milliseconds
1120    pub latency_ms: f64,
1121    /// Error message if this individual query failed
1122    #[serde(skip_serializing_if = "Option::is_none")]
1123    pub error: Option<String>,
1124}
1125
1126/// Batch query response
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct BatchQueryResponse {
1129    /// Results for each query in the batch
1130    pub results: Vec<BatchQueryResult>,
1131    /// Total execution time in milliseconds
1132    pub total_latency_ms: f64,
1133    /// Number of queries executed
1134    pub query_count: usize,
1135}
1136
1137// ============================================================================
1138// Multi-Vector Search Types
1139// ============================================================================
1140
1141/// Request for multi-vector search with positive and negative vectors
1142#[derive(Debug, Clone, Serialize, Deserialize)]
1143pub struct MultiVectorSearchRequest {
1144    /// Positive vectors to search towards (required, at least one)
1145    pub positive_vectors: Vec<Vec<f32>>,
1146    /// Weights for positive vectors (optional, defaults to equal weights)
1147    #[serde(skip_serializing_if = "Option::is_none")]
1148    pub positive_weights: Option<Vec<f32>>,
1149    /// Negative vectors to search away from (optional)
1150    #[serde(skip_serializing_if = "Option::is_none")]
1151    pub negative_vectors: Option<Vec<Vec<f32>>>,
1152    /// Weights for negative vectors (optional, defaults to equal weights)
1153    #[serde(skip_serializing_if = "Option::is_none")]
1154    pub negative_weights: Option<Vec<f32>>,
1155    /// Number of results to return
1156    #[serde(default = "default_multi_vector_top_k")]
1157    pub top_k: u32,
1158    /// Distance metric to use
1159    #[serde(default)]
1160    pub distance_metric: DistanceMetric,
1161    /// Minimum score threshold
1162    #[serde(skip_serializing_if = "Option::is_none")]
1163    pub score_threshold: Option<f32>,
1164    /// Enable MMR (Maximal Marginal Relevance) for diversity
1165    #[serde(default)]
1166    pub enable_mmr: bool,
1167    /// Lambda parameter for MMR (0 = max diversity, 1 = max relevance)
1168    #[serde(default = "default_mmr_lambda")]
1169    pub mmr_lambda: f32,
1170    /// Include metadata in results
1171    #[serde(default = "default_true")]
1172    pub include_metadata: bool,
1173    /// Include vectors in results
1174    #[serde(default)]
1175    pub include_vectors: bool,
1176    /// Optional metadata filter
1177    #[serde(skip_serializing_if = "Option::is_none")]
1178    pub filter: Option<serde_json::Value>,
1179    /// Read consistency level
1180    #[serde(default)]
1181    pub consistency: ReadConsistency,
1182    /// Staleness configuration for bounded staleness reads
1183    #[serde(skip_serializing_if = "Option::is_none")]
1184    pub staleness_config: Option<StalenessConfig>,
1185}
1186
1187fn default_multi_vector_top_k() -> u32 {
1188    10
1189}
1190
1191fn default_mmr_lambda() -> f32 {
1192    0.5
1193}
1194
1195impl MultiVectorSearchRequest {
1196    /// Create a new multi-vector search request with positive vectors
1197    pub fn new(positive_vectors: Vec<Vec<f32>>) -> Self {
1198        Self {
1199            positive_vectors,
1200            positive_weights: None,
1201            negative_vectors: None,
1202            negative_weights: None,
1203            top_k: 10,
1204            distance_metric: DistanceMetric::default(),
1205            score_threshold: None,
1206            enable_mmr: false,
1207            mmr_lambda: 0.5,
1208            include_metadata: true,
1209            include_vectors: false,
1210            filter: None,
1211            consistency: ReadConsistency::default(),
1212            staleness_config: None,
1213        }
1214    }
1215
1216    /// Set the number of results to return
1217    pub fn with_top_k(mut self, top_k: u32) -> Self {
1218        self.top_k = top_k;
1219        self
1220    }
1221
1222    /// Add weights for positive vectors
1223    pub fn with_positive_weights(mut self, weights: Vec<f32>) -> Self {
1224        self.positive_weights = Some(weights);
1225        self
1226    }
1227
1228    /// Add negative vectors to search away from
1229    pub fn with_negative_vectors(mut self, vectors: Vec<Vec<f32>>) -> Self {
1230        self.negative_vectors = Some(vectors);
1231        self
1232    }
1233
1234    /// Add weights for negative vectors
1235    pub fn with_negative_weights(mut self, weights: Vec<f32>) -> Self {
1236        self.negative_weights = Some(weights);
1237        self
1238    }
1239
1240    /// Set distance metric
1241    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1242        self.distance_metric = metric;
1243        self
1244    }
1245
1246    /// Set minimum score threshold
1247    pub fn with_score_threshold(mut self, threshold: f32) -> Self {
1248        self.score_threshold = Some(threshold);
1249        self
1250    }
1251
1252    /// Enable MMR for diversity
1253    pub fn with_mmr(mut self, lambda: f32) -> Self {
1254        self.enable_mmr = true;
1255        self.mmr_lambda = lambda.clamp(0.0, 1.0);
1256        self
1257    }
1258
1259    /// Set whether to include metadata
1260    pub fn include_metadata(mut self, include: bool) -> Self {
1261        self.include_metadata = include;
1262        self
1263    }
1264
1265    /// Set whether to include vectors
1266    pub fn include_vectors(mut self, include: bool) -> Self {
1267        self.include_vectors = include;
1268        self
1269    }
1270
1271    /// Add a filter
1272    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1273        self.filter = Some(filter);
1274        self
1275    }
1276
1277    /// Set read consistency level
1278    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1279        self.consistency = consistency;
1280        self
1281    }
1282}
1283
1284/// Single result from multi-vector search
1285#[derive(Debug, Clone, Serialize, Deserialize)]
1286pub struct MultiVectorSearchResult {
1287    /// Vector ID
1288    pub id: String,
1289    /// Similarity score
1290    pub score: f32,
1291    /// MMR score (if MMR enabled)
1292    #[serde(skip_serializing_if = "Option::is_none")]
1293    pub mmr_score: Option<f32>,
1294    /// Original rank before reranking
1295    #[serde(skip_serializing_if = "Option::is_none")]
1296    pub original_rank: Option<usize>,
1297    /// Optional metadata
1298    #[serde(skip_serializing_if = "Option::is_none")]
1299    pub metadata: Option<HashMap<String, serde_json::Value>>,
1300    /// Optional vector values
1301    #[serde(skip_serializing_if = "Option::is_none")]
1302    pub vector: Option<Vec<f32>>,
1303}
1304
1305/// Response from multi-vector search
1306#[derive(Debug, Clone, Serialize, Deserialize)]
1307pub struct MultiVectorSearchResponse {
1308    /// Search results
1309    pub results: Vec<MultiVectorSearchResult>,
1310    /// The computed query vector (weighted combination of positive - negative)
1311    #[serde(skip_serializing_if = "Option::is_none")]
1312    pub computed_query_vector: Option<Vec<f32>>,
1313}
1314
1315// ============================================================================
1316// Aggregation Types (Turbopuffer-inspired)
1317// ============================================================================
1318
1319/// Aggregate function for computing values across documents
1320#[derive(Debug, Clone, Serialize, Deserialize)]
1321#[serde(untagged)]
1322pub enum AggregateFunction {
1323    /// Count matching documents
1324    Count,
1325    /// Sum numeric attribute values
1326    Sum { field: String },
1327    /// Average numeric attribute values
1328    Avg { field: String },
1329    /// Minimum numeric attribute value
1330    Min { field: String },
1331    /// Maximum numeric attribute value
1332    Max { field: String },
1333}
1334
1335/// Request for aggregation query (Turbopuffer-inspired)
1336#[derive(Debug, Clone, Serialize, Deserialize)]
1337pub struct AggregationRequest {
1338    /// Named aggregations to compute
1339    /// Example: {"my_count": ["Count"], "total_score": ["Sum", "score"]}
1340    pub aggregate_by: HashMap<String, serde_json::Value>,
1341    /// Fields to group results by (optional)
1342    /// Example: ["category", "status"]
1343    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1344    pub group_by: Vec<String>,
1345    /// Filter to apply before aggregation
1346    #[serde(skip_serializing_if = "Option::is_none")]
1347    pub filter: Option<serde_json::Value>,
1348    /// Maximum number of groups to return (default: 100)
1349    #[serde(default = "default_agg_limit")]
1350    pub limit: usize,
1351}
1352
1353fn default_agg_limit() -> usize {
1354    100
1355}
1356
1357impl AggregationRequest {
1358    /// Create a new aggregation request with a single aggregation
1359    pub fn new() -> Self {
1360        Self {
1361            aggregate_by: HashMap::new(),
1362            group_by: Vec::new(),
1363            filter: None,
1364            limit: 100,
1365        }
1366    }
1367
1368    /// Add a count aggregation
1369    pub fn with_count(mut self, name: impl Into<String>) -> Self {
1370        self.aggregate_by
1371            .insert(name.into(), serde_json::json!(["Count"]));
1372        self
1373    }
1374
1375    /// Add a sum aggregation
1376    pub fn with_sum(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1377        self.aggregate_by
1378            .insert(name.into(), serde_json::json!(["Sum", field.into()]));
1379        self
1380    }
1381
1382    /// Add an average aggregation
1383    pub fn with_avg(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1384        self.aggregate_by
1385            .insert(name.into(), serde_json::json!(["Avg", field.into()]));
1386        self
1387    }
1388
1389    /// Add a min aggregation
1390    pub fn with_min(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1391        self.aggregate_by
1392            .insert(name.into(), serde_json::json!(["Min", field.into()]));
1393        self
1394    }
1395
1396    /// Add a max aggregation
1397    pub fn with_max(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1398        self.aggregate_by
1399            .insert(name.into(), serde_json::json!(["Max", field.into()]));
1400        self
1401    }
1402
1403    /// Set group by fields
1404    pub fn group_by(mut self, fields: Vec<String>) -> Self {
1405        self.group_by = fields;
1406        self
1407    }
1408
1409    /// Add a single group by field
1410    pub fn with_group_by(mut self, field: impl Into<String>) -> Self {
1411        self.group_by.push(field.into());
1412        self
1413    }
1414
1415    /// Set filter for aggregation
1416    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1417        self.filter = Some(filter);
1418        self
1419    }
1420
1421    /// Set maximum number of groups to return
1422    pub fn with_limit(mut self, limit: usize) -> Self {
1423        self.limit = limit;
1424        self
1425    }
1426}
1427
1428impl Default for AggregationRequest {
1429    fn default() -> Self {
1430        Self::new()
1431    }
1432}
1433
1434/// Response for aggregation query
1435#[derive(Debug, Clone, Serialize, Deserialize)]
1436pub struct AggregationResponse {
1437    /// Aggregation results (without grouping)
1438    #[serde(skip_serializing_if = "Option::is_none")]
1439    pub aggregations: Option<HashMap<String, serde_json::Value>>,
1440    /// Grouped aggregation results (with group_by)
1441    #[serde(skip_serializing_if = "Option::is_none")]
1442    pub aggregation_groups: Option<Vec<AggregationGroup>>,
1443}
1444
1445/// Single group in aggregation results
1446#[derive(Debug, Clone, Serialize, Deserialize)]
1447pub struct AggregationGroup {
1448    /// Group key values (flattened into object)
1449    #[serde(flatten)]
1450    pub group_key: HashMap<String, serde_json::Value>,
1451    /// Aggregation results for this group
1452    #[serde(flatten)]
1453    pub aggregations: HashMap<String, serde_json::Value>,
1454}
1455
1456// ============================================================================
1457// Unified Query Types (Turbopuffer-inspired)
1458// ============================================================================
1459
1460/// Vector search method for unified query
1461#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1462pub enum VectorSearchMethod {
1463    /// Approximate Nearest Neighbor (fast, default)
1464    #[default]
1465    ANN,
1466    /// Exact k-Nearest Neighbor (exhaustive, requires filters)
1467    #[serde(rename = "kNN")]
1468    KNN,
1469}
1470
1471/// Sort direction for attribute ordering
1472#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1473#[serde(rename_all = "lowercase")]
1474pub enum SortDirection {
1475    /// Ascending order
1476    Asc,
1477    /// Descending order
1478    #[default]
1479    Desc,
1480}
1481
1482/// Ranking function for unified query API
1483/// Supports vector search (ANN/kNN), full-text BM25, and attribute ordering
1484#[derive(Debug, Clone, Serialize, Deserialize)]
1485#[serde(untagged)]
1486pub enum RankBy {
1487    /// Vector search: uses field, method, and query_vector
1488    VectorSearch {
1489        field: String,
1490        method: VectorSearchMethod,
1491        query_vector: Vec<f32>,
1492    },
1493    /// Full-text BM25 search
1494    FullTextSearch {
1495        field: String,
1496        method: String, // Always "BM25"
1497        query: String,
1498    },
1499    /// Attribute ordering
1500    AttributeOrder {
1501        field: String,
1502        direction: SortDirection,
1503    },
1504    /// Sum of multiple ranking functions
1505    Sum(Vec<RankBy>),
1506    /// Max of multiple ranking functions
1507    Max(Vec<RankBy>),
1508    /// Product with weight
1509    Product { weight: f32, ranking: Box<RankBy> },
1510}
1511
1512impl RankBy {
1513    /// Create a vector search ranking using ANN
1514    pub fn vector_ann(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1515        RankBy::VectorSearch {
1516            field: field.into(),
1517            method: VectorSearchMethod::ANN,
1518            query_vector,
1519        }
1520    }
1521
1522    /// Create a vector search ranking using ANN on the default "vector" field
1523    pub fn ann(query_vector: Vec<f32>) -> Self {
1524        Self::vector_ann("vector", query_vector)
1525    }
1526
1527    /// Create a vector search ranking using exact kNN
1528    pub fn vector_knn(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1529        RankBy::VectorSearch {
1530            field: field.into(),
1531            method: VectorSearchMethod::KNN,
1532            query_vector,
1533        }
1534    }
1535
1536    /// Create a vector search ranking using kNN on the default "vector" field
1537    pub fn knn(query_vector: Vec<f32>) -> Self {
1538        Self::vector_knn("vector", query_vector)
1539    }
1540
1541    /// Create a BM25 full-text search ranking
1542    pub fn bm25(field: impl Into<String>, query: impl Into<String>) -> Self {
1543        RankBy::FullTextSearch {
1544            field: field.into(),
1545            method: "BM25".to_string(),
1546            query: query.into(),
1547        }
1548    }
1549
1550    /// Create an attribute ordering ranking (ascending)
1551    pub fn asc(field: impl Into<String>) -> Self {
1552        RankBy::AttributeOrder {
1553            field: field.into(),
1554            direction: SortDirection::Asc,
1555        }
1556    }
1557
1558    /// Create an attribute ordering ranking (descending)
1559    pub fn desc(field: impl Into<String>) -> Self {
1560        RankBy::AttributeOrder {
1561            field: field.into(),
1562            direction: SortDirection::Desc,
1563        }
1564    }
1565
1566    /// Sum multiple ranking functions together
1567    pub fn sum(rankings: Vec<RankBy>) -> Self {
1568        RankBy::Sum(rankings)
1569    }
1570
1571    /// Take the max of multiple ranking functions
1572    pub fn max(rankings: Vec<RankBy>) -> Self {
1573        RankBy::Max(rankings)
1574    }
1575
1576    /// Apply a weight multiplier to a ranking function
1577    pub fn product(weight: f32, ranking: RankBy) -> Self {
1578        RankBy::Product {
1579            weight,
1580            ranking: Box::new(ranking),
1581        }
1582    }
1583}
1584
1585/// Unified query request with flexible ranking options (Turbopuffer-inspired)
1586///
1587/// # Example
1588///
1589/// ```rust
1590/// use dakera_client::UnifiedQueryRequest;
1591///
1592/// // Vector ANN search
1593/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10);
1594///
1595/// // Full-text BM25 search
1596/// let request = UnifiedQueryRequest::fulltext_search("content", "hello world", 10);
1597///
1598/// // Custom rank_by with filters
1599/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10)
1600///     .with_filter(serde_json::json!({"category": {"$eq": "science"}}));
1601/// ```
1602#[derive(Debug, Clone, Serialize, Deserialize)]
1603pub struct UnifiedQueryRequest {
1604    /// How to rank documents (required)
1605    pub rank_by: serde_json::Value,
1606    /// Number of results to return
1607    #[serde(default = "default_unified_top_k")]
1608    pub top_k: usize,
1609    /// Optional metadata filter
1610    #[serde(skip_serializing_if = "Option::is_none")]
1611    pub filter: Option<serde_json::Value>,
1612    /// Include metadata in results
1613    #[serde(default = "default_true")]
1614    pub include_metadata: bool,
1615    /// Include vectors in results
1616    #[serde(default)]
1617    pub include_vectors: bool,
1618    /// Distance metric for vector search (default: cosine)
1619    #[serde(default)]
1620    pub distance_metric: DistanceMetric,
1621}
1622
1623fn default_unified_top_k() -> usize {
1624    10
1625}
1626
1627impl UnifiedQueryRequest {
1628    /// Create a new unified query request with vector ANN search
1629    pub fn vector_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1630        Self {
1631            rank_by: serde_json::json!(["ANN", query_vector]),
1632            top_k,
1633            filter: None,
1634            include_metadata: true,
1635            include_vectors: false,
1636            distance_metric: DistanceMetric::default(),
1637        }
1638    }
1639
1640    /// Create a new unified query request with vector kNN search
1641    pub fn vector_knn_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1642        Self {
1643            rank_by: serde_json::json!(["kNN", query_vector]),
1644            top_k,
1645            filter: None,
1646            include_metadata: true,
1647            include_vectors: false,
1648            distance_metric: DistanceMetric::default(),
1649        }
1650    }
1651
1652    /// Create a new unified query request with full-text BM25 search
1653    pub fn fulltext_search(
1654        field: impl Into<String>,
1655        query: impl Into<String>,
1656        top_k: usize,
1657    ) -> Self {
1658        Self {
1659            rank_by: serde_json::json!([field.into(), "BM25", query.into()]),
1660            top_k,
1661            filter: None,
1662            include_metadata: true,
1663            include_vectors: false,
1664            distance_metric: DistanceMetric::default(),
1665        }
1666    }
1667
1668    /// Create a new unified query request with attribute ordering
1669    pub fn attribute_order(
1670        field: impl Into<String>,
1671        direction: SortDirection,
1672        top_k: usize,
1673    ) -> Self {
1674        let dir = match direction {
1675            SortDirection::Asc => "asc",
1676            SortDirection::Desc => "desc",
1677        };
1678        Self {
1679            rank_by: serde_json::json!([field.into(), dir]),
1680            top_k,
1681            filter: None,
1682            include_metadata: true,
1683            include_vectors: false,
1684            distance_metric: DistanceMetric::default(),
1685        }
1686    }
1687
1688    /// Create a unified query with a raw rank_by JSON value
1689    pub fn with_rank_by(rank_by: serde_json::Value, top_k: usize) -> Self {
1690        Self {
1691            rank_by,
1692            top_k,
1693            filter: None,
1694            include_metadata: true,
1695            include_vectors: false,
1696            distance_metric: DistanceMetric::default(),
1697        }
1698    }
1699
1700    /// Add a filter to the query
1701    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1702        self.filter = Some(filter);
1703        self
1704    }
1705
1706    /// Set whether to include metadata
1707    pub fn include_metadata(mut self, include: bool) -> Self {
1708        self.include_metadata = include;
1709        self
1710    }
1711
1712    /// Set whether to include vector values
1713    pub fn include_vectors(mut self, include: bool) -> Self {
1714        self.include_vectors = include;
1715        self
1716    }
1717
1718    /// Set the distance metric
1719    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1720        self.distance_metric = metric;
1721        self
1722    }
1723
1724    /// Set the number of results to return
1725    pub fn with_top_k(mut self, top_k: usize) -> Self {
1726        self.top_k = top_k;
1727        self
1728    }
1729}
1730
1731/// Single result from unified query
1732#[derive(Debug, Clone, Serialize, Deserialize)]
1733pub struct UnifiedSearchResult {
1734    /// Vector/document ID
1735    pub id: String,
1736    /// Ranking score (distance for vector search, BM25 score for text)
1737    /// Named $dist for Turbopuffer compatibility
1738    #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1739    pub dist: Option<f32>,
1740    /// Metadata if requested
1741    #[serde(skip_serializing_if = "Option::is_none")]
1742    pub metadata: Option<serde_json::Value>,
1743    /// Vector values if requested
1744    #[serde(skip_serializing_if = "Option::is_none")]
1745    pub vector: Option<Vec<f32>>,
1746}
1747
1748/// Unified query response
1749#[derive(Debug, Clone, Serialize, Deserialize)]
1750pub struct UnifiedQueryResponse {
1751    /// Search results ordered by rank_by score
1752    pub results: Vec<UnifiedSearchResult>,
1753    /// Cursor for pagination (if more results available)
1754    #[serde(skip_serializing_if = "Option::is_none")]
1755    pub next_cursor: Option<String>,
1756}
1757
1758// ============================================================================
1759// Query Explain Types
1760// ============================================================================
1761
1762fn default_explain_top_k() -> usize {
1763    10
1764}
1765
1766/// Query type for explain
1767#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1768#[serde(rename_all = "snake_case")]
1769#[derive(Default)]
1770pub enum ExplainQueryType {
1771    /// Vector similarity search
1772    #[default]
1773    VectorSearch,
1774    /// Full-text search
1775    FullTextSearch,
1776    /// Hybrid search combining vector and text
1777    HybridSearch,
1778    /// Multi-vector search with positive/negative vectors
1779    MultiVector,
1780    /// Batch query execution
1781    BatchQuery,
1782}
1783
1784/// Query explain request
1785#[derive(Debug, Clone, Serialize, Deserialize)]
1786pub struct QueryExplainRequest {
1787    /// Type of query to explain
1788    #[serde(default)]
1789    pub query_type: ExplainQueryType,
1790    /// Query vector (for vector searches)
1791    #[serde(skip_serializing_if = "Option::is_none")]
1792    pub vector: Option<Vec<f32>>,
1793    /// Number of results to return
1794    #[serde(default = "default_explain_top_k")]
1795    pub top_k: usize,
1796    /// Optional metadata filter
1797    #[serde(skip_serializing_if = "Option::is_none")]
1798    pub filter: Option<serde_json::Value>,
1799    /// Optional text query for hybrid/fulltext search
1800    #[serde(skip_serializing_if = "Option::is_none")]
1801    pub text_query: Option<String>,
1802    /// Distance metric
1803    #[serde(default = "default_distance_metric")]
1804    pub distance_metric: String,
1805    /// Whether to actually execute the query for actual stats
1806    #[serde(default)]
1807    pub execute: bool,
1808    /// Include verbose output
1809    #[serde(default)]
1810    pub verbose: bool,
1811}
1812
1813fn default_distance_metric() -> String {
1814    "cosine".to_string()
1815}
1816
1817impl QueryExplainRequest {
1818    /// Create a new explain request for a vector search
1819    pub fn vector_search(vector: Vec<f32>, top_k: usize) -> Self {
1820        Self {
1821            query_type: ExplainQueryType::VectorSearch,
1822            vector: Some(vector),
1823            top_k,
1824            filter: None,
1825            text_query: None,
1826            distance_metric: "cosine".to_string(),
1827            execute: false,
1828            verbose: false,
1829        }
1830    }
1831
1832    /// Create a new explain request for a full-text search
1833    pub fn fulltext_search(text_query: impl Into<String>, top_k: usize) -> Self {
1834        Self {
1835            query_type: ExplainQueryType::FullTextSearch,
1836            vector: None,
1837            top_k,
1838            filter: None,
1839            text_query: Some(text_query.into()),
1840            distance_metric: "bm25".to_string(),
1841            execute: false,
1842            verbose: false,
1843        }
1844    }
1845
1846    /// Create a new explain request for a hybrid search
1847    pub fn hybrid_search(vector: Vec<f32>, text_query: impl Into<String>, top_k: usize) -> Self {
1848        Self {
1849            query_type: ExplainQueryType::HybridSearch,
1850            vector: Some(vector),
1851            top_k,
1852            filter: None,
1853            text_query: Some(text_query.into()),
1854            distance_metric: "hybrid".to_string(),
1855            execute: false,
1856            verbose: false,
1857        }
1858    }
1859
1860    /// Add a filter to the explain request
1861    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1862        self.filter = Some(filter);
1863        self
1864    }
1865
1866    /// Set the distance metric
1867    pub fn with_distance_metric(mut self, metric: impl Into<String>) -> Self {
1868        self.distance_metric = metric.into();
1869        self
1870    }
1871
1872    /// Execute the query to get actual stats
1873    pub fn with_execution(mut self) -> Self {
1874        self.execute = true;
1875        self
1876    }
1877
1878    /// Enable verbose output
1879    pub fn with_verbose(mut self) -> Self {
1880        self.verbose = true;
1881        self
1882    }
1883}
1884
1885/// A stage in query execution
1886#[derive(Debug, Clone, Serialize, Deserialize)]
1887pub struct ExecutionStage {
1888    /// Stage name
1889    pub name: String,
1890    /// Stage description
1891    pub description: String,
1892    /// Stage order (1-based)
1893    pub order: u32,
1894    /// Estimated input rows
1895    pub estimated_input: u64,
1896    /// Estimated output rows
1897    pub estimated_output: u64,
1898    /// Estimated cost for this stage
1899    pub estimated_cost: f64,
1900    /// Stage-specific details
1901    #[serde(default)]
1902    pub details: HashMap<String, serde_json::Value>,
1903}
1904
1905/// Cost estimation
1906#[derive(Debug, Clone, Serialize, Deserialize)]
1907pub struct CostEstimate {
1908    /// Total estimated cost (abstract units)
1909    pub total_cost: f64,
1910    /// Estimated execution time in milliseconds
1911    pub estimated_time_ms: u64,
1912    /// Estimated memory usage in bytes
1913    pub estimated_memory_bytes: u64,
1914    /// Estimated I/O operations
1915    pub estimated_io_ops: u64,
1916    /// Cost breakdown by component
1917    #[serde(default)]
1918    pub cost_breakdown: HashMap<String, f64>,
1919    /// Confidence level (0.0-1.0)
1920    pub confidence: f64,
1921}
1922
1923/// Actual execution statistics (when execute=true)
1924#[derive(Debug, Clone, Serialize, Deserialize)]
1925pub struct ActualStats {
1926    /// Actual execution time in milliseconds
1927    pub execution_time_ms: u64,
1928    /// Actual results returned
1929    pub results_returned: usize,
1930    /// Vectors scanned
1931    pub vectors_scanned: u64,
1932    /// Vectors after filter
1933    pub vectors_after_filter: u64,
1934    /// Index lookups performed
1935    pub index_lookups: u64,
1936    /// Cache hits
1937    pub cache_hits: u64,
1938    /// Cache misses
1939    pub cache_misses: u64,
1940    /// Memory used in bytes
1941    pub memory_used_bytes: u64,
1942}
1943
1944/// Performance recommendation
1945#[derive(Debug, Clone, Serialize, Deserialize)]
1946pub struct Recommendation {
1947    /// Recommendation type
1948    pub recommendation_type: String,
1949    /// Priority (high, medium, low)
1950    pub priority: String,
1951    /// Recommendation description
1952    pub description: String,
1953    /// Expected improvement
1954    pub expected_improvement: String,
1955    /// How to implement
1956    pub implementation: String,
1957}
1958
1959/// Index selection details
1960#[derive(Debug, Clone, Serialize, Deserialize)]
1961pub struct IndexSelection {
1962    /// Index type that will be used
1963    pub index_type: String,
1964    /// Why this index was selected
1965    pub selection_reason: String,
1966    /// Alternative indexes considered
1967    #[serde(default)]
1968    pub alternatives_considered: Vec<IndexAlternative>,
1969    /// Index configuration
1970    #[serde(default)]
1971    pub index_config: HashMap<String, serde_json::Value>,
1972    /// Index statistics
1973    pub index_stats: IndexStatistics,
1974}
1975
1976/// Alternative index that was considered
1977#[derive(Debug, Clone, Serialize, Deserialize)]
1978pub struct IndexAlternative {
1979    /// Index type
1980    pub index_type: String,
1981    /// Why it wasn't selected
1982    pub rejection_reason: String,
1983    /// Estimated cost if this index was used
1984    pub estimated_cost: f64,
1985}
1986
1987/// Index statistics
1988#[derive(Debug, Clone, Serialize, Deserialize)]
1989pub struct IndexStatistics {
1990    /// Total vectors in index
1991    pub vector_count: u64,
1992    /// Vector dimension
1993    pub dimension: usize,
1994    /// Index memory usage (estimated)
1995    pub memory_bytes: u64,
1996    /// Index build time (if available)
1997    #[serde(skip_serializing_if = "Option::is_none")]
1998    pub build_time_ms: Option<u64>,
1999    /// Last updated timestamp
2000    #[serde(skip_serializing_if = "Option::is_none")]
2001    pub last_updated: Option<u64>,
2002}
2003
2004/// Query parameters for reference
2005#[derive(Debug, Clone, Serialize, Deserialize)]
2006pub struct QueryParams {
2007    /// Number of results requested
2008    pub top_k: usize,
2009    /// Whether a filter was applied
2010    pub has_filter: bool,
2011    /// Filter complexity level
2012    pub filter_complexity: String,
2013    /// Vector dimension (if applicable)
2014    #[serde(skip_serializing_if = "Option::is_none")]
2015    pub vector_dimension: Option<usize>,
2016    /// Distance metric used
2017    pub distance_metric: String,
2018    /// Text query length (if applicable)
2019    #[serde(skip_serializing_if = "Option::is_none")]
2020    pub text_query_length: Option<usize>,
2021}
2022
2023/// Query explain response - detailed execution plan
2024#[derive(Debug, Clone, Serialize, Deserialize)]
2025pub struct QueryExplainResponse {
2026    /// Query type being explained
2027    pub query_type: ExplainQueryType,
2028    /// Namespace being queried
2029    pub namespace: String,
2030    /// Index selection information
2031    pub index_selection: IndexSelection,
2032    /// Query execution stages
2033    pub stages: Vec<ExecutionStage>,
2034    /// Cost estimates
2035    pub cost_estimate: CostEstimate,
2036    /// Actual execution stats (if execute=true)
2037    #[serde(skip_serializing_if = "Option::is_none")]
2038    pub actual_stats: Option<ActualStats>,
2039    /// Performance recommendations
2040    #[serde(default)]
2041    pub recommendations: Vec<Recommendation>,
2042    /// Query plan summary
2043    pub summary: String,
2044    /// Raw query parameters
2045    pub query_params: QueryParams,
2046}
2047
2048// ============================================================================
2049// Text Auto-Embedding Types
2050// ============================================================================
2051
2052/// Supported embedding models for text-based operations.
2053#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
2054#[serde(rename_all = "kebab-case")]
2055pub enum EmbeddingModel {
2056    /// BGE-large — Best quality, server default (1024 dimensions)
2057    #[default]
2058    BgeLarge,
2059    /// MiniLM-L6 — Fast, good quality (384 dimensions)
2060    Minilm,
2061    /// BGE-small — Balanced performance (384 dimensions)
2062    BgeSmall,
2063    /// E5-small — High quality (384 dimensions)
2064    E5Small,
2065}
2066
2067/// A text document to upsert with automatic embedding generation.
2068#[derive(Debug, Clone, Serialize, Deserialize)]
2069pub struct TextDocument {
2070    /// Unique identifier for the document.
2071    pub id: String,
2072    /// Raw text content to be embedded.
2073    pub text: String,
2074    /// Optional metadata for the document.
2075    #[serde(skip_serializing_if = "Option::is_none")]
2076    pub metadata: Option<HashMap<String, serde_json::Value>>,
2077    /// Optional TTL in seconds.
2078    #[serde(skip_serializing_if = "Option::is_none")]
2079    pub ttl_seconds: Option<u64>,
2080}
2081
2082impl TextDocument {
2083    /// Create a new text document with the given ID and text.
2084    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
2085        Self {
2086            id: id.into(),
2087            text: text.into(),
2088            metadata: None,
2089            ttl_seconds: None,
2090        }
2091    }
2092
2093    /// Add metadata to this document.
2094    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
2095        self.metadata = Some(metadata);
2096        self
2097    }
2098
2099    /// Set a TTL on this document.
2100    pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
2101        self.ttl_seconds = Some(ttl_seconds);
2102        self
2103    }
2104}
2105
2106/// Request to upsert text documents with automatic embedding.
2107#[derive(Debug, Clone, Serialize, Deserialize)]
2108pub struct UpsertTextRequest {
2109    /// Documents to upsert.
2110    pub documents: Vec<TextDocument>,
2111    /// Embedding model to use (default: minilm).
2112    #[serde(skip_serializing_if = "Option::is_none")]
2113    pub model: Option<EmbeddingModel>,
2114}
2115
2116impl UpsertTextRequest {
2117    /// Create a new upsert-text request.
2118    pub fn new(documents: Vec<TextDocument>) -> Self {
2119        Self {
2120            documents,
2121            model: None,
2122        }
2123    }
2124
2125    /// Set the embedding model.
2126    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2127        self.model = Some(model);
2128        self
2129    }
2130}
2131
2132/// Response from a text upsert operation.
2133#[derive(Debug, Clone, Serialize, Deserialize)]
2134pub struct TextUpsertResponse {
2135    /// Number of documents upserted.
2136    pub upserted_count: u64,
2137    /// Approximate number of tokens processed.
2138    pub tokens_processed: u64,
2139    /// Embedding model used.
2140    pub model: EmbeddingModel,
2141    /// Time spent generating embeddings in milliseconds.
2142    pub embedding_time_ms: u64,
2143}
2144
2145/// A single text search result.
2146#[derive(Debug, Clone, Serialize, Deserialize)]
2147pub struct TextSearchResult {
2148    /// Document ID.
2149    pub id: String,
2150    /// Similarity score.
2151    pub score: f32,
2152    /// Original text (if `include_text` was true).
2153    #[serde(skip_serializing_if = "Option::is_none")]
2154    pub text: Option<String>,
2155    /// Document metadata.
2156    #[serde(skip_serializing_if = "Option::is_none")]
2157    pub metadata: Option<HashMap<String, serde_json::Value>>,
2158    /// Vector values (if `include_vectors` was true).
2159    #[serde(skip_serializing_if = "Option::is_none")]
2160    pub vector: Option<Vec<f32>>,
2161}
2162
2163/// Request to query using natural language text with automatic embedding.
2164#[derive(Debug, Clone, Serialize, Deserialize)]
2165pub struct QueryTextRequest {
2166    /// Query text.
2167    pub text: String,
2168    /// Number of results to return.
2169    pub top_k: u32,
2170    /// Optional metadata filter.
2171    #[serde(skip_serializing_if = "Option::is_none")]
2172    pub filter: Option<serde_json::Value>,
2173    /// Whether to include the original text in results.
2174    pub include_text: bool,
2175    /// Whether to include vectors in results.
2176    pub include_vectors: bool,
2177    /// Embedding model to use (default: minilm).
2178    #[serde(skip_serializing_if = "Option::is_none")]
2179    pub model: Option<EmbeddingModel>,
2180}
2181
2182impl QueryTextRequest {
2183    /// Create a new text query request.
2184    pub fn new(text: impl Into<String>, top_k: u32) -> Self {
2185        Self {
2186            text: text.into(),
2187            top_k,
2188            filter: None,
2189            include_text: true,
2190            include_vectors: false,
2191            model: None,
2192        }
2193    }
2194
2195    /// Add a metadata filter.
2196    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
2197        self.filter = Some(filter);
2198        self
2199    }
2200
2201    /// Set whether to include the original text in results.
2202    pub fn include_text(mut self, include: bool) -> Self {
2203        self.include_text = include;
2204        self
2205    }
2206
2207    /// Set whether to include vectors in results.
2208    pub fn include_vectors(mut self, include: bool) -> Self {
2209        self.include_vectors = include;
2210        self
2211    }
2212
2213    /// Set the embedding model.
2214    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2215        self.model = Some(model);
2216        self
2217    }
2218}
2219
2220/// Response from a text query operation.
2221#[derive(Debug, Clone, Serialize, Deserialize)]
2222pub struct TextQueryResponse {
2223    /// Search results.
2224    pub results: Vec<TextSearchResult>,
2225    /// Embedding model used.
2226    pub model: EmbeddingModel,
2227    /// Time spent generating the query embedding in milliseconds.
2228    pub embedding_time_ms: u64,
2229    /// Time spent searching in milliseconds.
2230    pub search_time_ms: u64,
2231}
2232
2233/// Request to execute multiple text queries with automatic embedding in a single call.
2234#[derive(Debug, Clone, Serialize, Deserialize)]
2235pub struct BatchQueryTextRequest {
2236    /// Text queries.
2237    pub queries: Vec<String>,
2238    /// Number of results per query.
2239    pub top_k: u32,
2240    /// Optional metadata filter applied to all queries.
2241    #[serde(skip_serializing_if = "Option::is_none")]
2242    pub filter: Option<serde_json::Value>,
2243    /// Whether to include vectors in results.
2244    pub include_vectors: bool,
2245    /// Embedding model to use (default: minilm).
2246    #[serde(skip_serializing_if = "Option::is_none")]
2247    pub model: Option<EmbeddingModel>,
2248}
2249
2250impl BatchQueryTextRequest {
2251    /// Create a new batch text query request.
2252    pub fn new(queries: Vec<String>, top_k: u32) -> Self {
2253        Self {
2254            queries,
2255            top_k,
2256            filter: None,
2257            include_vectors: false,
2258            model: None,
2259        }
2260    }
2261}
2262
2263/// Response from a batch text query operation.
2264#[derive(Debug, Clone, Serialize, Deserialize)]
2265pub struct BatchQueryTextResponse {
2266    /// Results for each query (in the same order as the request).
2267    pub results: Vec<Vec<TextSearchResult>>,
2268    /// Embedding model used.
2269    pub model: EmbeddingModel,
2270    /// Time spent generating all embeddings in milliseconds.
2271    pub embedding_time_ms: u64,
2272    /// Time spent on all searches in milliseconds.
2273    pub search_time_ms: u64,
2274}
2275
2276// ============================================================================
2277// Fetch by ID Types
2278// ============================================================================
2279
2280/// Request to fetch vectors by their IDs.
2281#[derive(Debug, Clone, Serialize, Deserialize)]
2282pub struct FetchRequest {
2283    /// IDs of vectors to fetch.
2284    pub ids: Vec<String>,
2285    /// Whether to include vector values.
2286    pub include_values: bool,
2287    /// Whether to include metadata.
2288    pub include_metadata: bool,
2289}
2290
2291impl FetchRequest {
2292    /// Create a new fetch request.
2293    pub fn new(ids: Vec<String>) -> Self {
2294        Self {
2295            ids,
2296            include_values: true,
2297            include_metadata: true,
2298        }
2299    }
2300}
2301
2302/// Response from a fetch-by-ID operation.
2303#[derive(Debug, Clone, Serialize, Deserialize)]
2304pub struct FetchResponse {
2305    /// Fetched vectors.
2306    pub vectors: Vec<Vector>,
2307}
2308
2309// ============================================================================
2310// Namespace Management Types
2311// ============================================================================
2312
2313/// Request to create a new namespace.
2314#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2315pub struct CreateNamespaceRequest {
2316    /// Vector dimensions (inferred from first upsert if omitted).
2317    #[serde(skip_serializing_if = "Option::is_none")]
2318    pub dimensions: Option<u32>,
2319    /// Index type (e.g. "hnsw", "flat").
2320    #[serde(skip_serializing_if = "Option::is_none")]
2321    pub index_type: Option<String>,
2322    /// Arbitrary namespace metadata.
2323    #[serde(skip_serializing_if = "Option::is_none")]
2324    pub metadata: Option<HashMap<String, serde_json::Value>>,
2325}
2326
2327impl CreateNamespaceRequest {
2328    /// Create a minimal request (server picks sensible defaults).
2329    pub fn new() -> Self {
2330        Self::default()
2331    }
2332
2333    /// Set the vector dimensions.
2334    pub fn with_dimensions(mut self, dimensions: u32) -> Self {
2335        self.dimensions = Some(dimensions);
2336        self
2337    }
2338
2339    /// Set the index type.
2340    pub fn with_index_type(mut self, index_type: impl Into<String>) -> Self {
2341        self.index_type = Some(index_type.into());
2342        self
2343    }
2344}
2345
2346/// Request body for `PUT /v1/namespaces/:namespace` — upsert semantics (v0.6.0).
2347///
2348/// Creates the namespace if it does not exist, or updates its configuration
2349/// if it already exists.  Requires `Scope::Write`.
2350#[derive(Debug, Clone, Serialize, Deserialize)]
2351pub struct ConfigureNamespaceRequest {
2352    /// Vector dimension.  Required on first creation; must match on subsequent calls.
2353    pub dimension: usize,
2354    /// Distance metric (defaults to cosine when omitted).
2355    #[serde(skip_serializing_if = "Option::is_none")]
2356    pub distance: Option<DistanceMetric>,
2357}
2358
2359impl ConfigureNamespaceRequest {
2360    /// Create a new configure-namespace request with the given dimension.
2361    pub fn new(dimension: usize) -> Self {
2362        Self {
2363            dimension,
2364            distance: None,
2365        }
2366    }
2367
2368    /// Set the distance metric.
2369    pub fn with_distance(mut self, distance: DistanceMetric) -> Self {
2370        self.distance = Some(distance);
2371        self
2372    }
2373}
2374
2375/// Response from `PUT /v1/namespaces/:namespace`.
2376#[derive(Debug, Clone, Serialize, Deserialize)]
2377pub struct ConfigureNamespaceResponse {
2378    /// Namespace name.
2379    pub namespace: String,
2380    /// Vector dimension.
2381    pub dimension: usize,
2382    /// Distance metric in use.
2383    pub distance: DistanceMetric,
2384    /// `true` if the namespace was newly created; `false` if it already existed.
2385    pub created: bool,
2386}
2387
2388// ============================================================================
2389// Memory Knowledge Graph Types (CE-5 / SDK-9)
2390// ============================================================================
2391
2392/// Edge type for memory knowledge graph relationships (CE-5).
2393#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
2394#[serde(rename_all = "snake_case")]
2395pub enum EdgeType {
2396    /// Cosine similarity ≥ 0.85 — two memories are semantically similar.
2397    RelatedTo,
2398    /// Both memories reference the same named entity (CE-4 tags).
2399    SharesEntity,
2400    /// Temporal ordering — source was created before target.
2401    Precedes,
2402    /// Explicit user/agent-created link.
2403    #[default]
2404    LinkedBy,
2405}
2406
2407/// A directed edge in the memory knowledge graph.
2408#[derive(Debug, Clone, Serialize, Deserialize)]
2409pub struct GraphEdge {
2410    /// Unique edge identifier.
2411    pub id: String,
2412    /// Source memory ID.
2413    pub source_id: String,
2414    /// Target memory ID.
2415    pub target_id: String,
2416    /// Relationship type between the two memories.
2417    pub edge_type: EdgeType,
2418    /// Edge weight (0.0–1.0). For `RelatedTo` this is the cosine similarity score.
2419    pub weight: f64,
2420    /// Unix timestamp of edge creation.
2421    pub created_at: i64,
2422}
2423
2424/// A node (memory) in the knowledge graph traversal result.
2425#[derive(Debug, Clone, Serialize, Deserialize)]
2426pub struct GraphNode {
2427    /// Memory identifier.
2428    pub memory_id: String,
2429    /// First 200 characters of memory content.
2430    pub content_preview: String,
2431    /// Memory importance score.
2432    pub importance: f64,
2433    /// Traversal depth from the root node (root = 0).
2434    pub depth: u32,
2435}
2436
2437/// Graph traversal result from `GET /v1/memories/{id}/graph`.
2438#[derive(Debug, Clone, Serialize, Deserialize)]
2439pub struct MemoryGraph {
2440    /// The root memory ID from which traversal started.
2441    pub root_id: String,
2442    /// Maximum traversal depth used.
2443    pub depth: u32,
2444    /// All memory nodes reachable within the requested depth.
2445    pub nodes: Vec<GraphNode>,
2446    /// All edges connecting the returned nodes.
2447    pub edges: Vec<GraphEdge>,
2448}
2449
2450/// Shortest path between two memories from `GET /v1/memories/{id}/path`.
2451#[derive(Debug, Clone, Serialize, Deserialize)]
2452pub struct GraphPath {
2453    /// Starting memory ID.
2454    pub source_id: String,
2455    /// Destination memory ID.
2456    pub target_id: String,
2457    /// Ordered list of memory IDs from source to target (inclusive).
2458    pub path: Vec<String>,
2459    /// Number of edges traversed (`path.len() - 1`). `-1` if no path exists.
2460    pub hops: i32,
2461    /// Edges along the path, in traversal order.
2462    pub edges: Vec<GraphEdge>,
2463}
2464
2465/// Request body for `POST /v1/memories/{id}/links`.
2466#[derive(Debug, Clone, Serialize, Deserialize)]
2467pub struct GraphLinkRequest {
2468    /// Target memory ID to link to.
2469    pub target_id: String,
2470    /// Edge type — must be `LinkedBy` for explicit links.
2471    pub edge_type: EdgeType,
2472}
2473
2474/// Response from `POST /v1/memories/{id}/links`.
2475#[derive(Debug, Clone, Serialize, Deserialize)]
2476pub struct GraphLinkResponse {
2477    /// The newly created edge.
2478    pub edge: GraphEdge,
2479}
2480
2481/// Agent graph export from `GET /v1/agents/{id}/graph/export`.
2482#[derive(Debug, Clone, Serialize, Deserialize)]
2483pub struct GraphExport {
2484    /// Agent whose graph was exported.
2485    pub agent_id: String,
2486    /// Export format: `json`, `graphml`, or `csv`.
2487    pub format: String,
2488    /// Serialised graph in the requested format.
2489    pub data: String,
2490    /// Total number of memory nodes in the export.
2491    pub node_count: u64,
2492    /// Total number of edges in the export.
2493    pub edge_count: u64,
2494}
2495
2496/// Options for [`DakeraClient::memory_graph`].
2497#[derive(Debug, Clone, Default)]
2498pub struct GraphOptions {
2499    /// Maximum traversal depth (default: 1, max: 3).
2500    pub depth: Option<u32>,
2501    /// Filter by edge types. `None` returns all types.
2502    pub types: Option<Vec<EdgeType>>,
2503}
2504
2505impl GraphOptions {
2506    /// Create default options.
2507    pub fn new() -> Self {
2508        Self::default()
2509    }
2510
2511    /// Set traversal depth.
2512    pub fn depth(mut self, depth: u32) -> Self {
2513        self.depth = Some(depth);
2514        self
2515    }
2516
2517    /// Filter by edge types.
2518    pub fn types(mut self, types: Vec<EdgeType>) -> Self {
2519        self.types = Some(types);
2520        self
2521    }
2522}
2523
2524// ============================================================================
2525// CE-4: GLiNER Entity Extraction Types
2526// ============================================================================
2527
2528/// Configuration for namespace-level entity extraction (CE-4).
2529#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2530pub struct NamespaceNerConfig {
2531    pub extract_entities: bool,
2532    #[serde(skip_serializing_if = "Option::is_none")]
2533    pub entity_types: Option<Vec<String>>,
2534}
2535
2536/// A single extracted entity from GLiNER or rule-based pipeline.
2537#[derive(Debug, Clone, Serialize, Deserialize)]
2538pub struct ExtractedEntity {
2539    pub entity_type: String,
2540    pub value: String,
2541    pub score: f64,
2542}
2543
2544/// Response from POST /v1/memories/extract
2545#[derive(Debug, Clone, Serialize, Deserialize)]
2546pub struct EntityExtractionResponse {
2547    pub entities: Vec<ExtractedEntity>,
2548}
2549
2550/// Response from GET /v1/memory/entities/:id
2551#[derive(Debug, Clone, Serialize, Deserialize)]
2552pub struct MemoryEntitiesResponse {
2553    pub memory_id: String,
2554    pub entities: Vec<ExtractedEntity>,
2555}
2556
2557// ============================================================================
2558// Memory Feedback Loop (INT-1)
2559// ============================================================================
2560
2561/// Feedback signal for memory active learning (INT-1).
2562///
2563/// - `upvote`: Boost importance ×1.15, capped at 1.0.
2564/// - `downvote`: Penalise importance ×0.85, floor 0.0.
2565/// - `flag`: Mark as irrelevant — sets `decay_flag=true`, no immediate importance change.
2566/// - `positive`: Backward-compatible alias for `upvote`.
2567/// - `negative`: Backward-compatible alias for `downvote`.
2568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2569#[serde(rename_all = "lowercase")]
2570pub enum FeedbackSignal {
2571    Upvote,
2572    Downvote,
2573    Flag,
2574    Positive,
2575    Negative,
2576}
2577
2578/// A single recorded feedback event stored in memory metadata (INT-1).
2579#[derive(Debug, Clone, Serialize, Deserialize)]
2580pub struct FeedbackHistoryEntry {
2581    pub signal: FeedbackSignal,
2582    /// Unix timestamp (seconds) when feedback was submitted.
2583    pub timestamp: u64,
2584    pub old_importance: f32,
2585    pub new_importance: f32,
2586}
2587
2588/// Request body for `POST /v1/memories/:id/feedback` (INT-1).
2589#[derive(Debug, Clone, Serialize, Deserialize)]
2590pub struct MemoryFeedbackBody {
2591    pub agent_id: String,
2592    pub signal: FeedbackSignal,
2593}
2594
2595/// Request body for `PATCH /v1/memories/:id/importance` (INT-1).
2596#[derive(Debug, Clone, Serialize, Deserialize)]
2597pub struct MemoryImportancePatch {
2598    pub agent_id: String,
2599    pub importance: f32,
2600}
2601
2602/// Response from `POST /v1/memories/:id/feedback` and `PATCH /v1/memories/:id/importance` (INT-1).
2603#[derive(Debug, Clone, Serialize, Deserialize)]
2604pub struct FeedbackResponse {
2605    pub memory_id: String,
2606    /// New importance score after the feedback was applied (0.0–1.0).
2607    pub new_importance: f32,
2608    pub signal: FeedbackSignal,
2609}
2610
2611/// Response from `GET /v1/memories/:id/feedback` (INT-1).
2612#[derive(Debug, Clone, Serialize, Deserialize)]
2613pub struct FeedbackHistoryResponse {
2614    pub memory_id: String,
2615    /// Ordered list of feedback events (oldest first, capped at 100).
2616    pub entries: Vec<FeedbackHistoryEntry>,
2617}
2618
2619/// Response from `GET /v1/agents/:id/feedback/summary` (INT-1).
2620#[derive(Debug, Clone, Serialize, Deserialize)]
2621pub struct AgentFeedbackSummary {
2622    pub agent_id: String,
2623    pub upvotes: u64,
2624    pub downvotes: u64,
2625    pub flags: u64,
2626    pub total_feedback: u64,
2627    /// Weighted-average importance across all non-expired memories (0.0–1.0).
2628    pub health_score: f32,
2629}
2630
2631/// Response from `GET /v1/feedback/health` (INT-1).
2632#[derive(Debug, Clone, Serialize, Deserialize)]
2633pub struct FeedbackHealthResponse {
2634    pub agent_id: String,
2635    /// Mean importance of all non-expired memories (0.0–1.0). Higher = healthier.
2636    pub health_score: f32,
2637    pub memory_count: usize,
2638    pub avg_importance: f32,
2639}
2640
2641// ============================================================================
2642// ODE-2: GLiNER Entity Extraction (dakera-ode sidecar)
2643// ============================================================================
2644
2645/// A single entity extracted by the GLiNER model (ODE-2).
2646#[derive(Debug, Clone, Serialize, Deserialize)]
2647pub struct OdeEntity {
2648    /// Span text as it appears in the input.
2649    pub text: String,
2650    /// Entity type label (e.g. `"person"`, `"organization"`).
2651    pub label: String,
2652    /// Start character offset (inclusive) within the input text.
2653    pub start: usize,
2654    /// End character offset (exclusive) within the input text.
2655    pub end: usize,
2656    /// Confidence score in the range [0, 1].
2657    pub score: f32,
2658}
2659
2660/// Request body for `POST /ode/extract` (ODE-2).
2661#[derive(Debug, Clone, Serialize, Deserialize)]
2662pub struct ExtractEntitiesRequest {
2663    /// The text to extract entities from.
2664    pub content: String,
2665    /// Agent context for the extraction.
2666    pub agent_id: String,
2667    /// Optional memory ID to associate with the extraction.
2668    #[serde(skip_serializing_if = "Option::is_none")]
2669    pub memory_id: Option<String>,
2670    /// Optional list of entity type labels to extract.
2671    /// When omitted the ODE sidecar uses its default set.
2672    #[serde(skip_serializing_if = "Option::is_none")]
2673    pub entity_types: Option<Vec<String>>,
2674}
2675
2676/// Response from `POST /ode/extract` on the ODE sidecar (ODE-2).
2677#[derive(Debug, Clone, Serialize, Deserialize)]
2678pub struct ExtractEntitiesResponse {
2679    /// Extracted entities ordered by their start offset.
2680    pub entities: Vec<OdeEntity>,
2681    /// GLiNER model variant used for extraction.
2682    pub model: String,
2683    /// Wall-clock time taken by the ODE sidecar in milliseconds.
2684    pub processing_time_ms: u64,
2685}
2686
2687// ============================================================================
2688// KG-2: Graph Query & Export — response types
2689// ============================================================================
2690
2691/// Response from `GET /v1/knowledge/query` (KG-2).
2692#[derive(Debug, Clone, Serialize, Deserialize)]
2693pub struct KgQueryResponse {
2694    /// Agent whose graph was queried.
2695    pub agent_id: String,
2696    /// Number of unique memory node IDs referenced by the returned edges.
2697    pub node_count: usize,
2698    /// Number of edges returned.
2699    pub edge_count: usize,
2700    /// Matching edges, up to `limit`.
2701    pub edges: Vec<GraphEdge>,
2702}
2703
2704/// Response from `GET /v1/knowledge/path` (KG-2).
2705#[derive(Debug, Clone, Serialize, Deserialize)]
2706pub struct KgPathResponse {
2707    /// Agent whose graph was traversed.
2708    pub agent_id: String,
2709    /// Source memory ID.
2710    pub from_id: String,
2711    /// Target memory ID.
2712    pub to_id: String,
2713    /// Number of edges in the shortest path (0 if source == target).
2714    pub hop_count: usize,
2715    /// Ordered list of memory IDs from source to target (inclusive).
2716    pub path: Vec<String>,
2717}
2718
2719/// Response from `GET /v1/knowledge/export` with `format=json` (KG-2).
2720#[derive(Debug, Clone, Serialize, Deserialize)]
2721pub struct KgExportResponse {
2722    /// Agent whose graph was exported.
2723    pub agent_id: String,
2724    /// Export format used (`"json"` when this struct is deserialized).
2725    pub format: String,
2726    /// Total number of unique memory node IDs in the export.
2727    pub node_count: usize,
2728    /// Total number of edges in the export.
2729    pub edge_count: usize,
2730    /// All graph edges for the agent.
2731    pub edges: Vec<GraphEdge>,
2732}
2733
2734// ============================================================================
2735// COG-1: Cognitive Memory Lifecycle — per-namespace memory policy
2736// ============================================================================
2737
2738/// Per-namespace memory lifecycle policy (COG-1).
2739///
2740/// Controls type-specific TTLs, decay curves, and spaced repetition behaviour.
2741/// All fields have sensible defaults; only override what you need.
2742///
2743/// Used by [`DakeraClient::get_memory_policy`] and
2744/// [`DakeraClient::set_memory_policy`].
2745#[derive(Debug, Clone, Serialize, Deserialize)]
2746pub struct MemoryPolicy {
2747    // Differential TTLs ------------------------------------------------------
2748    /// Default TTL for `working` memories in seconds (default: 14 400 = 4 h).
2749    #[serde(skip_serializing_if = "Option::is_none")]
2750    pub working_ttl_seconds: Option<u64>,
2751    /// Default TTL for `episodic` memories in seconds (default: 2 592 000 = 30 d).
2752    #[serde(skip_serializing_if = "Option::is_none")]
2753    pub episodic_ttl_seconds: Option<u64>,
2754    /// Default TTL for `semantic` memories in seconds (default: 31 536 000 = 365 d).
2755    #[serde(skip_serializing_if = "Option::is_none")]
2756    pub semantic_ttl_seconds: Option<u64>,
2757    /// Default TTL for `procedural` memories in seconds (default: 63 072 000 = 730 d).
2758    #[serde(skip_serializing_if = "Option::is_none")]
2759    pub procedural_ttl_seconds: Option<u64>,
2760
2761    // Decay curves ------------------------------------------------------------
2762    /// Decay strategy for `working` memories (default: `"exponential"`).
2763    #[serde(skip_serializing_if = "Option::is_none")]
2764    pub working_decay: Option<String>,
2765    /// Decay strategy for `episodic` memories (default: `"power_law"`).
2766    #[serde(skip_serializing_if = "Option::is_none")]
2767    pub episodic_decay: Option<String>,
2768    /// Decay strategy for `semantic` memories (default: `"logarithmic"`).
2769    #[serde(skip_serializing_if = "Option::is_none")]
2770    pub semantic_decay: Option<String>,
2771    /// Decay strategy for `procedural` memories (default: `"flat"` — no decay).
2772    #[serde(skip_serializing_if = "Option::is_none")]
2773    pub procedural_decay: Option<String>,
2774
2775    // Spaced repetition -------------------------------------------------------
2776    /// TTL extension multiplier per recall hit (default: 1.0; set to 0.0 to disable).
2777    /// Extension = `access_count × sr_factor × sr_base_interval_seconds`.
2778    #[serde(skip_serializing_if = "Option::is_none")]
2779    pub spaced_repetition_factor: Option<f64>,
2780    /// Base interval in seconds for spaced repetition TTL extension (default: 86 400 = 1 d).
2781    #[serde(skip_serializing_if = "Option::is_none")]
2782    pub spaced_repetition_base_interval_seconds: Option<u64>,
2783
2784    // Proactive consolidation (COG-3) -----------------------------------------
2785    /// Enable background DBSCAN deduplication for this namespace (default: `false`).
2786    /// When `true` the server merges semantically near-duplicate memories every
2787    /// [`consolidation_interval_hours`](Self::consolidation_interval_hours) hours.
2788    #[serde(skip_serializing_if = "Option::is_none")]
2789    pub consolidation_enabled: Option<bool>,
2790    /// DBSCAN epsilon — cosine-similarity threshold to consider two memories
2791    /// duplicates (default: `0.92`; higher = only merge very close neighbours).
2792    #[serde(skip_serializing_if = "Option::is_none")]
2793    pub consolidation_threshold: Option<f32>,
2794    /// How often (in hours) the background consolidation job runs (default: `24`).
2795    #[serde(skip_serializing_if = "Option::is_none")]
2796    pub consolidation_interval_hours: Option<u32>,
2797    /// **Read-only.** Lifetime count of memories merged by the consolidation engine.
2798    /// The server manages this field; any value sent via [`set_memory_policy`] is ignored.
2799    ///
2800    /// [`set_memory_policy`]: crate::DakeraClient::set_memory_policy
2801    #[serde(skip_serializing_if = "Option::is_none")]
2802    pub consolidated_count: Option<u64>,
2803
2804    // Per-namespace rate limiting (SEC-5) -----------------------------------------
2805    /// Enable per-namespace store/recall rate limiting (default: `false`).
2806    #[serde(skip_serializing_if = "Option::is_none")]
2807    pub rate_limit_enabled: Option<bool>,
2808    /// Max store operations per minute for this namespace. `None` = unlimited (default).
2809    #[serde(skip_serializing_if = "Option::is_none")]
2810    pub rate_limit_stores_per_minute: Option<u32>,
2811    /// Max recall operations per minute for this namespace. `None` = unlimited (default).
2812    #[serde(skip_serializing_if = "Option::is_none")]
2813    pub rate_limit_recalls_per_minute: Option<u32>,
2814
2815    // Store-time deduplication (CE-10) -----------------------------------------
2816    /// Deduplicate against existing memories at store time (CE-10, default: `false`).
2817    ///
2818    /// When `true` the server computes a similarity check before persisting a new
2819    /// memory and drops it if a near-duplicate already exists (threshold controlled
2820    /// by [`dedup_threshold`](Self::dedup_threshold)).
2821    #[serde(skip_serializing_if = "Option::is_none")]
2822    pub dedup_on_store: Option<bool>,
2823    /// Cosine-similarity threshold for store-time deduplication (default: `0.92`).
2824    ///
2825    /// Memories with similarity ≥ this value are considered duplicates and the
2826    /// incoming memory is dropped. Only active when `dedup_on_store` is `true`.
2827    #[serde(skip_serializing_if = "Option::is_none")]
2828    pub dedup_threshold: Option<f32>,
2829}
2830
2831impl Default for MemoryPolicy {
2832    fn default() -> Self {
2833        Self {
2834            working_ttl_seconds: Some(14_400),
2835            episodic_ttl_seconds: Some(2_592_000),
2836            semantic_ttl_seconds: Some(31_536_000),
2837            procedural_ttl_seconds: Some(63_072_000),
2838            working_decay: Some("exponential".to_string()),
2839            episodic_decay: Some("power_law".to_string()),
2840            semantic_decay: Some("logarithmic".to_string()),
2841            procedural_decay: Some("flat".to_string()),
2842            spaced_repetition_factor: Some(1.0),
2843            spaced_repetition_base_interval_seconds: Some(86_400),
2844            consolidation_enabled: Some(false),
2845            consolidation_threshold: Some(0.92),
2846            consolidation_interval_hours: Some(24),
2847            consolidated_count: Some(0),
2848            rate_limit_enabled: Some(false),
2849            rate_limit_stores_per_minute: None,
2850            rate_limit_recalls_per_minute: None,
2851            dedup_on_store: Some(false),
2852            dedup_threshold: Some(0.92),
2853        }
2854    }
2855}