Skip to main content

common/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Unique identifier for a vector
4pub type VectorId = String;
5
6/// Namespace identifier
7pub type NamespaceId = String;
8
9/// A vector with associated metadata
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Vector {
12    pub id: VectorId,
13    pub values: Vec<f32>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub metadata: Option<serde_json::Value>,
16    /// TTL in seconds (optional, for upsert requests)
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub ttl_seconds: Option<u64>,
19    /// Unix timestamp when this vector expires (internal use)
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub expires_at: Option<u64>,
22}
23
24impl Vector {
25    /// Check if this vector has expired
26    pub fn is_expired(&self) -> bool {
27        if let Some(expires_at) = self.expires_at {
28            let now = std::time::SystemTime::now()
29                .duration_since(std::time::UNIX_EPOCH)
30                .unwrap_or_default()
31                .as_secs();
32            now >= expires_at
33        } else {
34            false
35        }
36    }
37
38    /// Check if this vector has expired against a pre-captured timestamp.
39    /// Prefer this over `is_expired()` inside loops to avoid N syscalls.
40    #[inline]
41    pub fn is_expired_at(&self, now_secs: u64) -> bool {
42        self.expires_at.is_some_and(|exp| now_secs >= exp)
43    }
44
45    /// Calculate and set expires_at from ttl_seconds
46    pub fn apply_ttl(&mut self) {
47        if let Some(ttl) = self.ttl_seconds {
48            let now = std::time::SystemTime::now()
49                .duration_since(std::time::UNIX_EPOCH)
50                .unwrap_or_default()
51                .as_secs();
52            self.expires_at = Some(now + ttl);
53        }
54    }
55
56    /// Get remaining TTL in seconds (None if no expiration or expired)
57    pub fn remaining_ttl(&self) -> Option<u64> {
58        self.expires_at.and_then(|expires_at| {
59            let now = std::time::SystemTime::now()
60                .duration_since(std::time::UNIX_EPOCH)
61                .unwrap_or_default()
62                .as_secs();
63            if now < expires_at {
64                Some(expires_at - now)
65            } else {
66                None
67            }
68        })
69    }
70}
71
72/// Request to upsert vectors
73#[derive(Debug, Deserialize)]
74pub struct UpsertRequest {
75    pub vectors: Vec<Vector>,
76}
77
78/// Response from upsert operation
79#[derive(Debug, Serialize, Deserialize)]
80pub struct UpsertResponse {
81    pub upserted_count: usize,
82}
83
84/// Column-based upsert request (Turbopuffer-inspired)
85/// All arrays must have equal length. Use null for missing values.
86#[derive(Debug, Deserialize)]
87pub struct ColumnUpsertRequest {
88    /// Array of document IDs (required)
89    pub ids: Vec<VectorId>,
90    /// Array of vectors (required for vector namespaces)
91    pub vectors: Vec<Vec<f32>>,
92    /// Additional attributes as columns (optional)
93    /// Each key is an attribute name, value is array of attribute values
94    #[serde(default)]
95    pub attributes: std::collections::HashMap<String, Vec<serde_json::Value>>,
96    /// TTL in seconds for all vectors (optional)
97    #[serde(default)]
98    pub ttl_seconds: Option<u64>,
99    /// Expected dimension (optional, for validation)
100    #[serde(default)]
101    pub dimension: Option<usize>,
102}
103
104impl ColumnUpsertRequest {
105    /// Convert column format to row format (Vec<Vector>)
106    pub fn to_vectors(&self) -> Result<Vec<Vector>, String> {
107        let count = self.ids.len();
108
109        // Validate all arrays have same length
110        if self.vectors.len() != count {
111            return Err(format!(
112                "vectors array length ({}) doesn't match ids array length ({})",
113                self.vectors.len(),
114                count
115            ));
116        }
117
118        for (attr_name, attr_values) in &self.attributes {
119            if attr_values.len() != count {
120                return Err(format!(
121                    "attribute '{}' array length ({}) doesn't match ids array length ({})",
122                    attr_name,
123                    attr_values.len(),
124                    count
125                ));
126            }
127        }
128
129        // Validate vector dimensions
130        // Use explicit dimension if provided, otherwise derive from first vector
131        let expected_dim = if let Some(dim) = self.dimension {
132            Some(dim)
133        } else {
134            self.vectors.first().map(|v| v.len())
135        };
136
137        if let Some(expected) = expected_dim {
138            for (i, vec) in self.vectors.iter().enumerate() {
139                if vec.len() != expected {
140                    return Err(format!(
141                        "vectors[{}] has dimension {} but expected {}",
142                        i,
143                        vec.len(),
144                        expected
145                    ));
146                }
147            }
148        }
149
150        // Convert to row format
151        let mut vectors = Vec::with_capacity(count);
152        for i in 0..count {
153            // Build metadata from attributes
154            let metadata = if self.attributes.is_empty() {
155                None
156            } else {
157                let mut meta = serde_json::Map::new();
158                for (attr_name, attr_values) in &self.attributes {
159                    let value = &attr_values[i];
160                    if !value.is_null() {
161                        meta.insert(attr_name.clone(), value.clone());
162                    }
163                }
164                if meta.is_empty() {
165                    None
166                } else {
167                    Some(serde_json::Value::Object(meta))
168                }
169            };
170
171            let mut vector = Vector {
172                id: self.ids[i].clone(),
173                values: self.vectors[i].clone(),
174                metadata,
175                ttl_seconds: self.ttl_seconds,
176                expires_at: None,
177            };
178            vector.apply_ttl();
179            vectors.push(vector);
180        }
181
182        Ok(vectors)
183    }
184}
185
186/// Distance metric for vector comparison
187#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
188#[serde(rename_all = "snake_case")]
189pub enum DistanceMetric {
190    #[default]
191    Cosine,
192    Euclidean,
193    DotProduct,
194}
195
196/// Read consistency level for queries (Turbopuffer-inspired)
197///
198/// Controls the trade-off between read latency and data freshness.
199/// - `Strong`: Read from primary only, ensures latest data (higher latency)
200/// - `Eventual`: Read from any replica, may return stale data (lower latency)
201/// - `BoundedStaleness`: Allow reads from replicas within staleness threshold
202#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
203#[serde(rename_all = "snake_case")]
204pub enum ReadConsistency {
205    /// Read from primary replica only - ensures latest data
206    Strong,
207    /// Read from any available replica - faster but may be stale
208    #[default]
209    Eventual,
210    /// Allow staleness up to specified milliseconds
211    #[serde(rename = "bounded_staleness")]
212    BoundedStaleness,
213}
214
215/// Configuration for bounded staleness reads
216#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
217pub struct StalenessConfig {
218    /// Maximum acceptable staleness in milliseconds
219    #[serde(default = "default_max_staleness_ms")]
220    pub max_staleness_ms: u64,
221}
222
223fn default_max_staleness_ms() -> u64 {
224    5000 // 5 seconds default
225}
226
227/// Query request for vector search
228#[derive(Debug, Deserialize)]
229pub struct QueryRequest {
230    pub vector: Vec<f32>,
231    #[serde(default = "default_top_k")]
232    pub top_k: usize,
233    #[serde(default)]
234    pub distance_metric: DistanceMetric,
235    #[serde(default = "default_true")]
236    pub include_metadata: bool,
237    #[serde(default)]
238    pub include_vectors: bool,
239    /// Optional metadata filter
240    #[serde(default)]
241    pub filter: Option<FilterExpression>,
242    /// Cursor for pagination (from previous response's next_cursor)
243    #[serde(default)]
244    pub cursor: Option<String>,
245    /// Read consistency level (Turbopuffer-inspired)
246    /// Controls trade-off between latency and data freshness
247    #[serde(default)]
248    pub consistency: ReadConsistency,
249    /// Staleness configuration for bounded_staleness consistency
250    #[serde(default)]
251    pub staleness_config: Option<StalenessConfig>,
252}
253
254fn default_top_k() -> usize {
255    10
256}
257
258fn default_true() -> bool {
259    true
260}
261
262/// Single search result
263#[derive(Debug, Serialize, Deserialize)]
264pub struct SearchResult {
265    pub id: VectorId,
266    pub score: f32,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub metadata: Option<serde_json::Value>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub vector: Option<Vec<f32>>,
271}
272
273/// Query response
274#[derive(Debug, Serialize, Deserialize)]
275pub struct QueryResponse {
276    pub results: Vec<SearchResult>,
277    /// Cursor for fetching next page of results
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub next_cursor: Option<String>,
280    /// Whether there are more results available
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub has_more: Option<bool>,
283    /// Server-side search time in milliseconds
284    #[serde(default)]
285    pub search_time_ms: u64,
286}
287
288// ============================================================================
289// Cursor-based pagination types
290// ============================================================================
291
292/// Internal cursor state for pagination
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct PaginationCursor {
295    /// Last seen score for cursor-based pagination
296    pub last_score: f32,
297    /// Last seen ID for tie-breaking
298    pub last_id: String,
299}
300
301impl PaginationCursor {
302    /// Create a new pagination cursor
303    pub fn new(last_score: f32, last_id: String) -> Self {
304        Self {
305            last_score,
306            last_id,
307        }
308    }
309
310    /// Encode cursor to base64 string
311    pub fn encode(&self) -> String {
312        use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
313        let json = serde_json::to_string(self).unwrap_or_default();
314        URL_SAFE_NO_PAD.encode(json.as_bytes())
315    }
316
317    /// Decode cursor from base64 string
318    pub fn decode(cursor: &str) -> Option<Self> {
319        use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
320        let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
321        let json = String::from_utf8(bytes).ok()?;
322        serde_json::from_str(&json).ok()
323    }
324}
325
326/// Delete request
327#[derive(Debug, Deserialize)]
328pub struct DeleteRequest {
329    pub ids: Vec<VectorId>,
330}
331
332/// Delete response
333#[derive(Debug, Serialize)]
334pub struct DeleteResponse {
335    pub deleted_count: usize,
336}
337
338// ============================================================================
339// Batch query types
340// ============================================================================
341
342/// A single query within a batch request
343#[derive(Debug, Clone, Deserialize)]
344pub struct BatchQueryItem {
345    /// Unique identifier for this query within the batch
346    #[serde(default)]
347    pub id: Option<String>,
348    /// The query vector
349    pub vector: Vec<f32>,
350    /// Number of results to return
351    #[serde(default = "default_batch_top_k")]
352    pub top_k: u32,
353    /// Optional filter expression
354    #[serde(default)]
355    pub filter: Option<FilterExpression>,
356    /// Whether to include metadata in results
357    #[serde(default)]
358    pub include_metadata: bool,
359    /// Read consistency level (Turbopuffer-inspired)
360    #[serde(default)]
361    pub consistency: ReadConsistency,
362    /// Staleness configuration for bounded_staleness consistency
363    #[serde(default)]
364    pub staleness_config: Option<StalenessConfig>,
365}
366
367fn default_batch_top_k() -> u32 {
368    10
369}
370
371/// Batch query request - execute multiple queries in parallel
372#[derive(Debug, Deserialize)]
373pub struct BatchQueryRequest {
374    /// List of queries to execute
375    pub queries: Vec<BatchQueryItem>,
376}
377
378/// Results for a single query within a batch
379#[derive(Debug, Serialize)]
380pub struct BatchQueryResult {
381    /// The query identifier (if provided in request)
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub id: Option<String>,
384    /// Query results (empty if an error occurred)
385    pub results: Vec<SearchResult>,
386    /// Query execution time in milliseconds
387    pub latency_ms: f64,
388    /// Error message if this individual query failed
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub error: Option<String>,
391}
392
393/// Batch query response
394#[derive(Debug, Serialize)]
395pub struct BatchQueryResponse {
396    /// Results for each query in the batch
397    pub results: Vec<BatchQueryResult>,
398    /// Total execution time in milliseconds
399    pub total_latency_ms: f64,
400    /// Number of queries executed
401    pub query_count: usize,
402}
403
404// ============================================================================
405// Multi-vector search types
406// ============================================================================
407
408/// Request for multi-vector search with positive and negative vectors
409#[derive(Debug, Deserialize)]
410pub struct MultiVectorSearchRequest {
411    /// Positive vectors to search towards (required, at least one)
412    pub positive_vectors: Vec<Vec<f32>>,
413    /// Weights for positive vectors (optional, defaults to equal weights)
414    #[serde(default)]
415    pub positive_weights: Option<Vec<f32>>,
416    /// Negative vectors to search away from (optional)
417    #[serde(default)]
418    pub negative_vectors: Option<Vec<Vec<f32>>>,
419    /// Weights for negative vectors (optional, defaults to equal weights)
420    #[serde(default)]
421    pub negative_weights: Option<Vec<f32>>,
422    /// Number of results to return
423    #[serde(default = "default_top_k")]
424    pub top_k: usize,
425    /// Distance metric to use
426    #[serde(default)]
427    pub distance_metric: DistanceMetric,
428    /// Minimum score threshold
429    #[serde(default)]
430    pub score_threshold: Option<f32>,
431    /// Enable MMR (Maximal Marginal Relevance) for diversity
432    #[serde(default)]
433    pub enable_mmr: bool,
434    /// Lambda parameter for MMR (0 = max diversity, 1 = max relevance)
435    #[serde(default = "default_mmr_lambda")]
436    pub mmr_lambda: f32,
437    /// Include metadata in results
438    #[serde(default = "default_true")]
439    pub include_metadata: bool,
440    /// Include vectors in results
441    #[serde(default)]
442    pub include_vectors: bool,
443    /// Optional metadata filter
444    #[serde(default)]
445    pub filter: Option<FilterExpression>,
446    /// Read consistency level (Turbopuffer-inspired)
447    #[serde(default)]
448    pub consistency: ReadConsistency,
449    /// Staleness configuration for bounded_staleness consistency
450    #[serde(default)]
451    pub staleness_config: Option<StalenessConfig>,
452}
453
454fn default_mmr_lambda() -> f32 {
455    0.5
456}
457
458/// Single result from multi-vector search
459#[derive(Debug, Serialize, Deserialize)]
460pub struct MultiVectorSearchResult {
461    pub id: VectorId,
462    /// Similarity score
463    pub score: f32,
464    /// MMR score (if MMR enabled)
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub mmr_score: Option<f32>,
467    /// Original rank before reranking
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub original_rank: Option<usize>,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub metadata: Option<serde_json::Value>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub vector: Option<Vec<f32>>,
474}
475
476/// Response from multi-vector search
477#[derive(Debug, Serialize, Deserialize)]
478pub struct MultiVectorSearchResponse {
479    pub results: Vec<MultiVectorSearchResult>,
480    /// The computed query vector (weighted combination of positive - negative)
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub computed_query_vector: Option<Vec<f32>>,
483}
484
485// ============================================================================
486// Full-text search types
487// ============================================================================
488
489/// Request to index a document for full-text search
490#[derive(Debug, Serialize, Deserialize)]
491pub struct IndexDocumentRequest {
492    pub id: String,
493    pub text: String,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub metadata: Option<serde_json::Value>,
496}
497
498/// Request to index multiple documents
499#[derive(Debug, Deserialize)]
500pub struct IndexDocumentsRequest {
501    pub documents: Vec<IndexDocumentRequest>,
502}
503
504/// Response from indexing operation
505#[derive(Debug, Serialize, Deserialize)]
506pub struct IndexDocumentsResponse {
507    pub indexed_count: usize,
508}
509
510/// Request to search for documents
511#[derive(Debug, Deserialize)]
512pub struct FullTextSearchRequest {
513    pub query: String,
514    #[serde(default = "default_top_k")]
515    pub top_k: usize,
516    /// Optional metadata filter
517    #[serde(default)]
518    pub filter: Option<FilterExpression>,
519}
520
521/// Single full-text search result
522#[derive(Debug, Serialize, Deserialize)]
523pub struct FullTextSearchResult {
524    pub id: String,
525    pub score: f32,
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub metadata: Option<serde_json::Value>,
528}
529
530/// Full-text search response
531#[derive(Debug, Serialize, Deserialize)]
532pub struct FullTextSearchResponse {
533    pub results: Vec<FullTextSearchResult>,
534    /// Server-side search time in milliseconds
535    #[serde(default)]
536    pub search_time_ms: u64,
537}
538
539/// Request to delete documents from full-text index
540#[derive(Debug, Deserialize)]
541pub struct DeleteDocumentsRequest {
542    pub ids: Vec<String>,
543}
544
545/// Response from deleting documents
546#[derive(Debug, Serialize)]
547pub struct DeleteDocumentsResponse {
548    pub deleted_count: usize,
549}
550
551/// Full-text index statistics
552#[derive(Debug, Serialize)]
553pub struct FullTextIndexStats {
554    pub document_count: u32,
555    pub unique_terms: usize,
556    pub avg_doc_length: f32,
557}
558
559// ============================================================================
560// Hybrid search types (vector + full-text)
561// ============================================================================
562
563/// Hybrid search request combining vector similarity and full-text search
564#[derive(Debug, Deserialize)]
565pub struct HybridSearchRequest {
566    /// Query vector for similarity search
567    pub vector: Vec<f32>,
568    /// Text query for full-text search
569    pub text: String,
570    /// Number of results to return
571    #[serde(default = "default_top_k")]
572    pub top_k: usize,
573    /// Weight for vector search score (0.0 to 1.0)
574    /// Text search weight is (1.0 - vector_weight)
575    #[serde(default = "default_vector_weight")]
576    pub vector_weight: f32,
577    /// Distance metric for vector search
578    #[serde(default)]
579    pub distance_metric: DistanceMetric,
580    /// Include metadata in results
581    #[serde(default = "default_true")]
582    pub include_metadata: bool,
583    /// Include vectors in results
584    #[serde(default)]
585    pub include_vectors: bool,
586    /// Optional metadata filter
587    #[serde(default)]
588    pub filter: Option<FilterExpression>,
589}
590
591fn default_vector_weight() -> f32 {
592    0.5 // Equal weight by default
593}
594
595/// Single hybrid search result
596#[derive(Debug, Serialize, Deserialize)]
597pub struct HybridSearchResult {
598    pub id: String,
599    /// Combined score
600    pub score: f32,
601    /// Vector similarity score (normalized 0-1)
602    pub vector_score: f32,
603    /// Text search BM25 score (normalized 0-1)
604    pub text_score: f32,
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub metadata: Option<serde_json::Value>,
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub vector: Option<Vec<f32>>,
609}
610
611/// Hybrid search response
612#[derive(Debug, Serialize, Deserialize)]
613pub struct HybridSearchResponse {
614    pub results: Vec<HybridSearchResult>,
615    /// Server-side search time in milliseconds
616    #[serde(default)]
617    pub search_time_ms: u64,
618}
619
620// ============================================================================
621// Filter types for metadata filtering
622// ============================================================================
623
624/// A filter value that can be compared against metadata fields
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
626#[serde(untagged)]
627pub enum FilterValue {
628    String(String),
629    Number(f64),
630    Integer(i64),
631    Boolean(bool),
632    StringArray(Vec<String>),
633    NumberArray(Vec<f64>),
634}
635
636impl FilterValue {
637    /// Try to get as f64 for numeric comparisons
638    pub fn as_f64(&self) -> Option<f64> {
639        match self {
640            FilterValue::Number(n) => Some(*n),
641            FilterValue::Integer(i) => Some(*i as f64),
642            _ => None,
643        }
644    }
645
646    /// Try to get as string
647    pub fn as_str(&self) -> Option<&str> {
648        match self {
649            FilterValue::String(s) => Some(s.as_str()),
650            _ => None,
651        }
652    }
653
654    /// Try to get as bool
655    pub fn as_bool(&self) -> Option<bool> {
656        match self {
657            FilterValue::Boolean(b) => Some(*b),
658            _ => None,
659        }
660    }
661}
662
663/// Comparison operators for filter conditions
664#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
665#[serde(rename_all = "snake_case")]
666pub enum FilterCondition {
667    /// Equal to
668    #[serde(rename = "$eq")]
669    Eq(FilterValue),
670    /// Not equal to
671    #[serde(rename = "$ne")]
672    Ne(FilterValue),
673    /// Greater than
674    #[serde(rename = "$gt")]
675    Gt(FilterValue),
676    /// Greater than or equal to
677    #[serde(rename = "$gte")]
678    Gte(FilterValue),
679    /// Less than
680    #[serde(rename = "$lt")]
681    Lt(FilterValue),
682    /// Less than or equal to
683    #[serde(rename = "$lte")]
684    Lte(FilterValue),
685    /// In array
686    #[serde(rename = "$in")]
687    In(Vec<FilterValue>),
688    /// Not in array
689    #[serde(rename = "$nin")]
690    NotIn(Vec<FilterValue>),
691    /// Field exists
692    #[serde(rename = "$exists")]
693    Exists(bool),
694    // =========================================================================
695    // Enhanced string operators (Turbopuffer-inspired)
696    // =========================================================================
697    /// Contains substring (case-sensitive)
698    #[serde(rename = "$contains")]
699    Contains(String),
700    /// Contains substring (case-insensitive)
701    #[serde(rename = "$icontains")]
702    IContains(String),
703    /// Starts with prefix
704    #[serde(rename = "$startsWith")]
705    StartsWith(String),
706    /// Ends with suffix
707    #[serde(rename = "$endsWith")]
708    EndsWith(String),
709    /// Glob pattern matching (supports * and ? wildcards)
710    #[serde(rename = "$glob")]
711    Glob(String),
712    /// Regular expression matching
713    #[serde(rename = "$regex")]
714    Regex(String),
715}
716
717/// A filter expression that can be a single field condition or a logical combinator
718#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
719#[serde(untagged)]
720pub enum FilterExpression {
721    /// Logical AND of multiple expressions
722    And {
723        #[serde(rename = "$and")]
724        conditions: Vec<FilterExpression>,
725    },
726    /// Logical OR of multiple expressions
727    Or {
728        #[serde(rename = "$or")]
729        conditions: Vec<FilterExpression>,
730    },
731    /// Single field condition
732    Field {
733        #[serde(flatten)]
734        field: std::collections::HashMap<String, FilterCondition>,
735    },
736}
737
738// ============================================================================
739// Namespace quota types
740// ============================================================================
741
742/// Quota configuration for a namespace
743#[derive(Debug, Clone, Serialize, Deserialize, Default)]
744pub struct QuotaConfig {
745    /// Maximum number of vectors allowed (None = unlimited)
746    #[serde(skip_serializing_if = "Option::is_none")]
747    pub max_vectors: Option<u64>,
748    /// Maximum storage size in bytes (None = unlimited)
749    #[serde(skip_serializing_if = "Option::is_none")]
750    pub max_storage_bytes: Option<u64>,
751    /// Maximum dimensions per vector (None = unlimited)
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub max_dimensions: Option<usize>,
754    /// Maximum metadata size per vector in bytes (None = unlimited)
755    #[serde(skip_serializing_if = "Option::is_none")]
756    pub max_metadata_bytes: Option<usize>,
757    /// Whether to enforce quotas (soft limit = warn only, hard = reject)
758    #[serde(default)]
759    pub enforcement: QuotaEnforcement,
760}
761
762/// Quota enforcement mode
763#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
764#[serde(rename_all = "snake_case")]
765pub enum QuotaEnforcement {
766    /// No enforcement, just tracking
767    None,
768    /// Log warnings when quota exceeded but allow operations
769    Soft,
770    /// Reject operations that would exceed quota
771    #[default]
772    Hard,
773}
774
775/// Current quota usage for a namespace
776#[derive(Debug, Clone, Serialize, Deserialize, Default)]
777pub struct QuotaUsage {
778    /// Current number of vectors
779    pub vector_count: u64,
780    /// Current storage size in bytes (estimated)
781    pub storage_bytes: u64,
782    /// Average vector dimensions
783    pub avg_dimensions: Option<usize>,
784    /// Average metadata size in bytes
785    pub avg_metadata_bytes: Option<usize>,
786    /// Last updated timestamp (Unix epoch)
787    pub last_updated: u64,
788}
789
790impl QuotaUsage {
791    /// Create new usage with current timestamp
792    pub fn new(vector_count: u64, storage_bytes: u64) -> Self {
793        let now = std::time::SystemTime::now()
794            .duration_since(std::time::UNIX_EPOCH)
795            .unwrap_or_default()
796            .as_secs();
797        Self {
798            vector_count,
799            storage_bytes,
800            avg_dimensions: None,
801            avg_metadata_bytes: None,
802            last_updated: now,
803        }
804    }
805
806    /// Update the timestamp to now
807    pub fn touch(&mut self) {
808        self.last_updated = std::time::SystemTime::now()
809            .duration_since(std::time::UNIX_EPOCH)
810            .unwrap_or_default()
811            .as_secs();
812    }
813}
814
815/// Combined quota status showing config and current usage
816#[derive(Debug, Clone, Serialize, Deserialize)]
817pub struct QuotaStatus {
818    /// Namespace name
819    pub namespace: String,
820    /// Quota configuration
821    pub config: QuotaConfig,
822    /// Current usage
823    pub usage: QuotaUsage,
824    /// Percentage of vector quota used (0-100, None if unlimited)
825    #[serde(skip_serializing_if = "Option::is_none")]
826    pub vector_usage_percent: Option<f32>,
827    /// Percentage of storage quota used (0-100, None if unlimited)
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub storage_usage_percent: Option<f32>,
830    /// Whether any quota is exceeded
831    pub is_exceeded: bool,
832    /// List of exceeded quota types
833    #[serde(skip_serializing_if = "Vec::is_empty")]
834    pub exceeded_quotas: Vec<String>,
835}
836
837impl QuotaStatus {
838    /// Create a new quota status from config and usage
839    pub fn new(namespace: String, config: QuotaConfig, usage: QuotaUsage) -> Self {
840        let vector_usage_percent = config
841            .max_vectors
842            .map(|max| (usage.vector_count as f32 / max as f32) * 100.0);
843
844        let storage_usage_percent = config
845            .max_storage_bytes
846            .map(|max| (usage.storage_bytes as f32 / max as f32) * 100.0);
847
848        let mut exceeded_quotas = Vec::new();
849
850        if let Some(max) = config.max_vectors {
851            if usage.vector_count > max {
852                exceeded_quotas.push("max_vectors".to_string());
853            }
854        }
855
856        if let Some(max) = config.max_storage_bytes {
857            if usage.storage_bytes > max {
858                exceeded_quotas.push("max_storage_bytes".to_string());
859            }
860        }
861
862        let is_exceeded = !exceeded_quotas.is_empty();
863
864        Self {
865            namespace,
866            config,
867            usage,
868            vector_usage_percent,
869            storage_usage_percent,
870            is_exceeded,
871            exceeded_quotas,
872        }
873    }
874}
875
876/// Request to set quota for a namespace
877#[derive(Debug, Deserialize)]
878pub struct SetQuotaRequest {
879    /// Quota configuration to apply
880    pub config: QuotaConfig,
881}
882
883/// Response from setting quota
884#[derive(Debug, Serialize)]
885pub struct SetQuotaResponse {
886    /// Whether the operation succeeded
887    pub success: bool,
888    /// Namespace name
889    pub namespace: String,
890    /// Applied quota configuration
891    pub config: QuotaConfig,
892    /// Status message
893    pub message: String,
894}
895
896/// Quota check result
897#[derive(Debug, Clone, Serialize)]
898pub struct QuotaCheckResult {
899    /// Whether the operation is allowed
900    pub allowed: bool,
901    /// Reason if not allowed
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub reason: Option<String>,
904    /// Current usage
905    pub usage: QuotaUsage,
906    /// Quota that would be exceeded
907    #[serde(skip_serializing_if = "Option::is_none")]
908    pub exceeded_quota: Option<String>,
909}
910
911/// Response listing all namespace quotas
912#[derive(Debug, Serialize)]
913pub struct QuotaListResponse {
914    /// List of quota statuses per namespace
915    pub quotas: Vec<QuotaStatus>,
916    /// Total number of namespaces with quotas
917    pub total: u64,
918    /// Default quota configuration (if set)
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub default_config: Option<QuotaConfig>,
921}
922
923/// Response for default quota query
924#[derive(Debug, Serialize)]
925pub struct DefaultQuotaResponse {
926    /// Default quota configuration (None if not set)
927    pub config: Option<QuotaConfig>,
928}
929
930/// Request to set default quota configuration
931#[derive(Debug, Deserialize)]
932pub struct SetDefaultQuotaRequest {
933    /// Default quota configuration (None to remove)
934    pub config: Option<QuotaConfig>,
935}
936
937/// Request to check if an operation would exceed quota
938#[derive(Debug, Deserialize)]
939pub struct QuotaCheckRequest {
940    /// Vector IDs to check (simulated vectors)
941    pub vector_ids: Vec<String>,
942    /// Dimension of vectors (for size estimation)
943    #[serde(default)]
944    pub dimensions: Option<usize>,
945    /// Estimated metadata size per vector
946    #[serde(default)]
947    pub metadata_bytes: Option<usize>,
948}
949
950// ============================================================================
951// Export API Types (Turbopuffer-inspired)
952// ============================================================================
953
954/// Request to export vectors from a namespace with pagination
955#[derive(Debug, Deserialize)]
956pub struct ExportRequest {
957    /// Maximum number of vectors to return per page (default: 1000, max: 10000)
958    #[serde(default = "default_export_top_k")]
959    pub top_k: usize,
960    /// Cursor for pagination - the last vector ID from previous page
961    #[serde(skip_serializing_if = "Option::is_none")]
962    pub cursor: Option<String>,
963    /// Whether to include vector values in the response (default: true)
964    #[serde(default = "default_true")]
965    pub include_vectors: bool,
966    /// Whether to include metadata in the response (default: true)
967    #[serde(default = "default_true")]
968    pub include_metadata: bool,
969}
970
971fn default_export_top_k() -> usize {
972    1000
973}
974
975impl Default for ExportRequest {
976    fn default() -> Self {
977        Self {
978            top_k: 1000,
979            cursor: None,
980            include_vectors: true,
981            include_metadata: true,
982        }
983    }
984}
985
986/// A single exported vector record
987#[derive(Debug, Clone, Serialize, Deserialize)]
988pub struct ExportedVector {
989    /// Vector ID
990    pub id: String,
991    /// Vector values (optional based on include_vectors)
992    #[serde(skip_serializing_if = "Option::is_none")]
993    pub values: Option<Vec<f32>>,
994    /// Metadata (optional based on include_metadata)
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub metadata: Option<serde_json::Value>,
997    /// TTL in seconds if set
998    #[serde(skip_serializing_if = "Option::is_none")]
999    pub ttl_seconds: Option<u64>,
1000}
1001
1002impl From<&Vector> for ExportedVector {
1003    fn from(v: &Vector) -> Self {
1004        Self {
1005            id: v.id.clone(),
1006            values: Some(v.values.clone()),
1007            metadata: v.metadata.clone(),
1008            ttl_seconds: v.ttl_seconds,
1009        }
1010    }
1011}
1012
1013/// Response from export operation
1014#[derive(Debug, Serialize)]
1015pub struct ExportResponse {
1016    /// Exported vectors for this page
1017    pub vectors: Vec<ExportedVector>,
1018    /// Cursor for next page (None if this is the last page)
1019    #[serde(skip_serializing_if = "Option::is_none")]
1020    pub next_cursor: Option<String>,
1021    /// Total vectors in namespace (for progress tracking)
1022    pub total_count: usize,
1023    /// Number of vectors returned in this page
1024    pub returned_count: usize,
1025}
1026
1027// ============================================================================
1028// Unified Query API with rank_by (Turbopuffer-inspired)
1029// ============================================================================
1030
1031/// Ranking function for unified query API
1032/// Supports vector search (ANN/kNN), full-text BM25, and attribute ordering
1033#[derive(Debug, Clone, Serialize, Deserialize)]
1034#[serde(untagged)]
1035pub enum RankBy {
1036    /// Vector search: ["vector_field", "ANN"|"kNN", [query_vector]]
1037    /// or simplified: ["ANN", [query_vector]] for default "vector" field
1038    VectorSearch {
1039        field: String,
1040        method: VectorSearchMethod,
1041        query_vector: Vec<f32>,
1042    },
1043    /// Full-text BM25 search: ["text_field", "BM25", "query string"]
1044    FullTextSearch {
1045        field: String,
1046        method: String, // Always "BM25"
1047        query: String,
1048    },
1049    /// Attribute ordering: ["field_name", "asc"|"desc"]
1050    AttributeOrder {
1051        field: String,
1052        direction: SortDirection,
1053    },
1054    /// Sum of multiple ranking functions: ["Sum", [...rankings]]
1055    Sum(Vec<RankBy>),
1056    /// Max of multiple ranking functions: ["Max", [...rankings]]
1057    Max(Vec<RankBy>),
1058    /// Product with weight: ["Product", weight, ranking]
1059    Product { weight: f32, ranking: Box<RankBy> },
1060}
1061
1062/// Vector search method
1063#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1064pub enum VectorSearchMethod {
1065    /// Approximate Nearest Neighbor (fast, default)
1066    #[default]
1067    ANN,
1068    /// Exact k-Nearest Neighbor (exhaustive, requires filters)
1069    #[serde(rename = "kNN")]
1070    KNN,
1071}
1072
1073/// Sort direction for attribute ordering
1074#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1075#[serde(rename_all = "lowercase")]
1076#[derive(Default)]
1077pub enum SortDirection {
1078    Asc,
1079    #[default]
1080    Desc,
1081}
1082
1083/// Unified query request with rank_by parameter (Turbopuffer-inspired)
1084#[derive(Debug, Deserialize)]
1085pub struct UnifiedQueryRequest {
1086    /// How to rank documents (required unless using aggregations)
1087    pub rank_by: RankByInput,
1088    /// Number of results to return
1089    #[serde(default = "default_top_k")]
1090    pub top_k: usize,
1091    /// Optional metadata filter
1092    #[serde(default)]
1093    pub filter: Option<FilterExpression>,
1094    /// Include metadata in results
1095    #[serde(default = "default_true")]
1096    pub include_metadata: bool,
1097    /// Include vectors in results
1098    #[serde(default)]
1099    pub include_vectors: bool,
1100    /// Distance metric for vector search (default: cosine)
1101    #[serde(default)]
1102    pub distance_metric: DistanceMetric,
1103}
1104
1105/// Input format for rank_by that handles JSON array syntax
1106/// Examples:
1107/// - ["vector", "ANN", [0.1, 0.2, 0.3]]
1108/// - ["text", "BM25", "search query"]
1109/// - ["timestamp", "desc"]
1110/// - ["Sum", [["title", "BM25", "query"], ["content", "BM25", "query"]]]
1111/// - ["Product", 2.0, ["title", "BM25", "query"]]
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[serde(from = "serde_json::Value")]
1114pub struct RankByInput(pub RankBy);
1115
1116impl From<serde_json::Value> for RankByInput {
1117    fn from(value: serde_json::Value) -> Self {
1118        RankByInput(parse_rank_by(&value).unwrap_or_else(|| {
1119            // Default fallback - shouldn't happen with valid input
1120            RankBy::AttributeOrder {
1121                field: "id".to_string(),
1122                direction: SortDirection::Asc,
1123            }
1124        }))
1125    }
1126}
1127
1128/// Parse rank_by JSON array into RankBy enum
1129fn parse_rank_by(value: &serde_json::Value) -> Option<RankBy> {
1130    let arr = value.as_array()?;
1131    if arr.is_empty() {
1132        return None;
1133    }
1134
1135    let first = arr.first()?.as_str()?;
1136
1137    match first {
1138        // Combination operators
1139        "Sum" => {
1140            let rankings = arr.get(1)?.as_array()?;
1141            let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1142            Some(RankBy::Sum(parsed?))
1143        }
1144        "Max" => {
1145            let rankings = arr.get(1)?.as_array()?;
1146            let parsed: Option<Vec<RankBy>> = rankings.iter().map(parse_rank_by).collect();
1147            Some(RankBy::Max(parsed?))
1148        }
1149        "Product" => {
1150            let weight = arr.get(1)?.as_f64()? as f32;
1151            let ranking = parse_rank_by(arr.get(2)?)?;
1152            Some(RankBy::Product {
1153                weight,
1154                ranking: Box::new(ranking),
1155            })
1156        }
1157        // Vector search shorthand: ["ANN", [vector]] or ["kNN", [vector]]
1158        "ANN" => {
1159            let query_vector = parse_vector_array(arr.get(1)?)?;
1160            Some(RankBy::VectorSearch {
1161                field: "vector".to_string(),
1162                method: VectorSearchMethod::ANN,
1163                query_vector,
1164            })
1165        }
1166        "kNN" => {
1167            let query_vector = parse_vector_array(arr.get(1)?)?;
1168            Some(RankBy::VectorSearch {
1169                field: "vector".to_string(),
1170                method: VectorSearchMethod::KNN,
1171                query_vector,
1172            })
1173        }
1174        // Field-based operations
1175        field => {
1176            let second = arr.get(1)?;
1177
1178            // Check if second element is a method string
1179            if let Some(method_str) = second.as_str() {
1180                match method_str {
1181                    "ANN" => {
1182                        let query_vector = parse_vector_array(arr.get(2)?)?;
1183                        Some(RankBy::VectorSearch {
1184                            field: field.to_string(),
1185                            method: VectorSearchMethod::ANN,
1186                            query_vector,
1187                        })
1188                    }
1189                    "kNN" => {
1190                        let query_vector = parse_vector_array(arr.get(2)?)?;
1191                        Some(RankBy::VectorSearch {
1192                            field: field.to_string(),
1193                            method: VectorSearchMethod::KNN,
1194                            query_vector,
1195                        })
1196                    }
1197                    "BM25" => {
1198                        let query = arr.get(2)?.as_str()?;
1199                        Some(RankBy::FullTextSearch {
1200                            field: field.to_string(),
1201                            method: "BM25".to_string(),
1202                            query: query.to_string(),
1203                        })
1204                    }
1205                    "asc" => Some(RankBy::AttributeOrder {
1206                        field: field.to_string(),
1207                        direction: SortDirection::Asc,
1208                    }),
1209                    "desc" => Some(RankBy::AttributeOrder {
1210                        field: field.to_string(),
1211                        direction: SortDirection::Desc,
1212                    }),
1213                    _ => None,
1214                }
1215            } else {
1216                None
1217            }
1218        }
1219    }
1220}
1221
1222/// Parse a JSON value into a vector of f32
1223fn parse_vector_array(value: &serde_json::Value) -> Option<Vec<f32>> {
1224    let arr = value.as_array()?;
1225    arr.iter().map(|v| v.as_f64().map(|n| n as f32)).collect()
1226}
1227
1228/// Unified query response with $dist scoring
1229#[derive(Debug, Serialize, Deserialize)]
1230pub struct UnifiedQueryResponse {
1231    /// Search results ordered by rank_by score
1232    pub results: Vec<UnifiedSearchResult>,
1233    /// Cursor for pagination (if more results available)
1234    #[serde(skip_serializing_if = "Option::is_none")]
1235    pub next_cursor: Option<String>,
1236}
1237
1238/// Single result from unified query
1239#[derive(Debug, Serialize, Deserialize)]
1240pub struct UnifiedSearchResult {
1241    /// Vector/document ID
1242    pub id: String,
1243    /// Ranking score (distance for vector search, BM25 score for text)
1244    /// Named $dist for Turbopuffer compatibility
1245    #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1246    pub dist: Option<f32>,
1247    /// Metadata if requested
1248    #[serde(skip_serializing_if = "Option::is_none")]
1249    pub metadata: Option<serde_json::Value>,
1250    /// Vector values if requested
1251    #[serde(skip_serializing_if = "Option::is_none")]
1252    pub vector: Option<Vec<f32>>,
1253}
1254
1255// ============================================================================
1256// Aggregation types (Turbopuffer-inspired)
1257// ============================================================================
1258
1259/// Aggregate function for computing values across documents
1260#[derive(Debug, Clone, Serialize, Deserialize)]
1261pub enum AggregateFunction {
1262    /// Count matching documents: ["Count"]
1263    Count,
1264    /// Sum numeric attribute values: ["Sum", "attribute_name"]
1265    Sum { field: String },
1266    /// Average numeric attribute values: ["Avg", "attribute_name"]
1267    Avg { field: String },
1268    /// Minimum numeric attribute value: ["Min", "attribute_name"]
1269    Min { field: String },
1270    /// Maximum numeric attribute value: ["Max", "attribute_name"]
1271    Max { field: String },
1272}
1273
1274/// Wrapper for parsing aggregate function from JSON array
1275#[derive(Debug, Clone, Serialize, Deserialize)]
1276#[serde(from = "serde_json::Value")]
1277pub struct AggregateFunctionInput(pub AggregateFunction);
1278
1279impl From<serde_json::Value> for AggregateFunctionInput {
1280    fn from(value: serde_json::Value) -> Self {
1281        parse_aggregate_function(&value)
1282            .map(AggregateFunctionInput)
1283            .unwrap_or_else(|| {
1284                // Default to count if parsing fails
1285                AggregateFunctionInput(AggregateFunction::Count)
1286            })
1287    }
1288}
1289
1290/// Parse aggregate function from JSON array
1291fn parse_aggregate_function(value: &serde_json::Value) -> Option<AggregateFunction> {
1292    let arr = value.as_array()?;
1293    if arr.is_empty() {
1294        return None;
1295    }
1296
1297    let func_name = arr.first()?.as_str()?;
1298
1299    match func_name {
1300        "Count" => Some(AggregateFunction::Count),
1301        "Sum" => {
1302            let field = arr.get(1)?.as_str()?;
1303            Some(AggregateFunction::Sum {
1304                field: field.to_string(),
1305            })
1306        }
1307        "Avg" => {
1308            let field = arr.get(1)?.as_str()?;
1309            Some(AggregateFunction::Avg {
1310                field: field.to_string(),
1311            })
1312        }
1313        "Min" => {
1314            let field = arr.get(1)?.as_str()?;
1315            Some(AggregateFunction::Min {
1316                field: field.to_string(),
1317            })
1318        }
1319        "Max" => {
1320            let field = arr.get(1)?.as_str()?;
1321            Some(AggregateFunction::Max {
1322                field: field.to_string(),
1323            })
1324        }
1325        _ => None,
1326    }
1327}
1328
1329/// Request for aggregation query (Turbopuffer-inspired)
1330#[derive(Debug, Deserialize)]
1331pub struct AggregationRequest {
1332    /// Named aggregations to compute
1333    /// Example: {"my_count": ["Count"], "total_score": ["Sum", "score"]}
1334    pub aggregate_by: std::collections::HashMap<String, AggregateFunctionInput>,
1335    /// Fields to group results by (optional)
1336    /// Example: ["category", "status"]
1337    #[serde(default)]
1338    pub group_by: Vec<String>,
1339    /// Filter to apply before aggregation
1340    #[serde(default)]
1341    pub filter: Option<FilterExpression>,
1342    /// Maximum number of groups to return (default: 100)
1343    #[serde(default = "default_agg_limit")]
1344    pub limit: usize,
1345}
1346
1347fn default_agg_limit() -> usize {
1348    100
1349}
1350
1351/// Response for aggregation query
1352#[derive(Debug, Serialize, Deserialize)]
1353pub struct AggregationResponse {
1354    /// Aggregation results (without grouping)
1355    #[serde(skip_serializing_if = "Option::is_none")]
1356    pub aggregations: Option<std::collections::HashMap<String, serde_json::Value>>,
1357    /// Grouped aggregation results (with group_by)
1358    #[serde(skip_serializing_if = "Option::is_none")]
1359    pub aggregation_groups: Option<Vec<AggregationGroup>>,
1360}
1361
1362/// Single group in aggregation results
1363#[derive(Debug, Serialize, Deserialize)]
1364pub struct AggregationGroup {
1365    /// Group key values (flattened into object)
1366    #[serde(flatten)]
1367    pub group_key: std::collections::HashMap<String, serde_json::Value>,
1368    /// Aggregation results for this group
1369    #[serde(flatten)]
1370    pub aggregations: std::collections::HashMap<String, serde_json::Value>,
1371}
1372
1373// =============================================================================
1374// TEXT-BASED API TYPES (Embedded Inference)
1375// =============================================================================
1376
1377/// A text document with metadata for text-based upsert
1378#[derive(Debug, Clone, Serialize, Deserialize)]
1379pub struct TextDocument {
1380    /// Unique identifier for this document
1381    pub id: VectorId,
1382    /// The text content to be embedded
1383    pub text: String,
1384    /// Optional metadata to store with the vector
1385    #[serde(skip_serializing_if = "Option::is_none")]
1386    pub metadata: Option<serde_json::Value>,
1387    /// TTL in seconds (optional)
1388    #[serde(skip_serializing_if = "Option::is_none")]
1389    pub ttl_seconds: Option<u64>,
1390}
1391
1392/// Request to upsert text documents (auto-embedded)
1393#[derive(Debug, Deserialize)]
1394pub struct TextUpsertRequest {
1395    /// Text documents to embed and store
1396    pub documents: Vec<TextDocument>,
1397    /// Embedding model to use (default: `minilm`).
1398    #[serde(default)]
1399    pub model: Option<EmbeddingModelType>,
1400}
1401
1402/// Response from text upsert operation
1403#[derive(Debug, Serialize, Deserialize)]
1404pub struct TextUpsertResponse {
1405    /// Number of documents successfully upserted
1406    pub upserted_count: usize,
1407    /// Number of tokens processed for embedding
1408    pub tokens_processed: usize,
1409    /// Embedding model used
1410    pub model: EmbeddingModelType,
1411    /// Time taken for embedding generation (ms)
1412    pub embedding_time_ms: u64,
1413}
1414
1415/// Request for text-based query (auto-embedded)
1416#[derive(Debug, Deserialize)]
1417pub struct TextQueryRequest {
1418    /// The query text to search for
1419    pub text: String,
1420    /// Number of results to return
1421    #[serde(default = "default_top_k")]
1422    pub top_k: usize,
1423    /// Optional filter to apply
1424    #[serde(default)]
1425    pub filter: Option<FilterExpression>,
1426    /// Whether to include vectors in response
1427    #[serde(default)]
1428    pub include_vectors: bool,
1429    /// Whether to include the original text in response (if stored in metadata)
1430    #[serde(default = "default_true")]
1431    pub include_text: bool,
1432    /// Embedding model to use (must match upsert model; default: `minilm`).
1433    #[serde(default)]
1434    pub model: Option<EmbeddingModelType>,
1435}
1436
1437/// Response from text-based query
1438#[derive(Debug, Serialize, Deserialize)]
1439pub struct TextQueryResponse {
1440    /// Search results with similarity scores
1441    pub results: Vec<TextSearchResult>,
1442    /// Embedding model used
1443    pub model: EmbeddingModelType,
1444    /// Time taken for embedding generation (ms)
1445    pub embedding_time_ms: u64,
1446    /// Time taken for search (ms)
1447    pub search_time_ms: u64,
1448}
1449
1450/// Single result from text search
1451#[derive(Debug, Serialize, Deserialize)]
1452pub struct TextSearchResult {
1453    /// Document ID
1454    pub id: VectorId,
1455    /// Similarity score (higher is better)
1456    pub score: f32,
1457    /// Original text (if include_text=true and stored in metadata)
1458    #[serde(skip_serializing_if = "Option::is_none")]
1459    pub text: Option<String>,
1460    /// Associated metadata
1461    #[serde(skip_serializing_if = "Option::is_none")]
1462    pub metadata: Option<serde_json::Value>,
1463    /// Vector values (if include_vectors=true)
1464    #[serde(skip_serializing_if = "Option::is_none")]
1465    pub vector: Option<Vec<f32>>,
1466}
1467
1468/// Batch text query request
1469#[derive(Debug, Deserialize)]
1470pub struct BatchTextQueryRequest {
1471    /// Multiple query texts
1472    pub queries: Vec<String>,
1473    /// Number of results per query
1474    #[serde(default = "default_top_k")]
1475    pub top_k: usize,
1476    /// Optional filter to apply to all queries
1477    #[serde(default)]
1478    pub filter: Option<FilterExpression>,
1479    /// Whether to include vectors in response
1480    #[serde(default)]
1481    pub include_vectors: bool,
1482    /// Embedding model to use (default: `minilm`).
1483    #[serde(default)]
1484    pub model: Option<EmbeddingModelType>,
1485}
1486
1487/// Response from batch text query
1488#[derive(Debug, Serialize, Deserialize)]
1489pub struct BatchTextQueryResponse {
1490    /// Results for each query
1491    pub results: Vec<Vec<TextSearchResult>>,
1492    /// Embedding model used
1493    pub model: EmbeddingModelType,
1494    /// Total time for embedding generation (ms)
1495    pub embedding_time_ms: u64,
1496    /// Total time for search (ms)
1497    pub search_time_ms: u64,
1498}
1499
1500/// Available embedding models.
1501///
1502/// Replaces the previous `model: String` field — callers now supply a
1503/// typed enum value, eliminating runtime string-mismatch bugs.
1504///
1505/// JSON serialisation uses lowercase identifiers:
1506/// `"minilm"`, `"bge-small"`, `"e5-small"`.
1507#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1508pub enum EmbeddingModelType {
1509    /// all-MiniLM-L6-v2 — fast and memory-efficient, 384 dimensions
1510    #[default]
1511    #[serde(rename = "minilm")]
1512    MiniLM,
1513    /// BAAI/bge-small-en-v1.5 — balanced quality and speed, 384 dimensions
1514    #[serde(rename = "bge-small")]
1515    BgeSmall,
1516    /// intfloat/e5-small-v2 — quality-focused, 384 dimensions
1517    #[serde(rename = "e5-small")]
1518    E5Small,
1519}
1520
1521impl EmbeddingModelType {
1522    /// Embedding vector dimension for this model.
1523    pub fn dimension(&self) -> usize {
1524        match self {
1525            EmbeddingModelType::MiniLM => 384,
1526            EmbeddingModelType::BgeSmall => 384,
1527            EmbeddingModelType::E5Small => 384,
1528        }
1529    }
1530}
1531
1532impl std::fmt::Display for EmbeddingModelType {
1533    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1534        match self {
1535            EmbeddingModelType::MiniLM => write!(f, "minilm"),
1536            EmbeddingModelType::BgeSmall => write!(f, "bge-small"),
1537            EmbeddingModelType::E5Small => write!(f, "e5-small"),
1538        }
1539    }
1540}
1541
1542// ============================================================================
1543// Dakera Memory Types — AI Agent Memory Platform
1544// ============================================================================
1545
1546/// Type of memory stored by an agent
1547#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1548#[serde(rename_all = "snake_case")]
1549#[derive(Default)]
1550pub enum MemoryType {
1551    /// Personal experiences and events
1552    #[default]
1553    Episodic,
1554    /// Facts and general knowledge
1555    Semantic,
1556    /// How-to knowledge and skills
1557    Procedural,
1558    /// Short-term, temporary context
1559    Working,
1560}
1561
1562impl std::fmt::Display for MemoryType {
1563    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1564        match self {
1565            MemoryType::Episodic => write!(f, "episodic"),
1566            MemoryType::Semantic => write!(f, "semantic"),
1567            MemoryType::Procedural => write!(f, "procedural"),
1568            MemoryType::Working => write!(f, "working"),
1569        }
1570    }
1571}
1572
1573/// A memory stored by an AI agent
1574#[derive(Debug, Clone, Serialize, Deserialize)]
1575pub struct Memory {
1576    pub id: String,
1577    #[serde(default)]
1578    pub memory_type: MemoryType,
1579    pub content: String,
1580    pub agent_id: String,
1581    #[serde(skip_serializing_if = "Option::is_none")]
1582    pub session_id: Option<String>,
1583    #[serde(default = "default_importance")]
1584    pub importance: f32,
1585    #[serde(default)]
1586    pub tags: Vec<String>,
1587    #[serde(skip_serializing_if = "Option::is_none")]
1588    pub metadata: Option<serde_json::Value>,
1589    pub created_at: u64,
1590    pub last_accessed_at: u64,
1591    #[serde(default)]
1592    pub access_count: u32,
1593    #[serde(skip_serializing_if = "Option::is_none")]
1594    pub ttl_seconds: Option<u64>,
1595    #[serde(skip_serializing_if = "Option::is_none")]
1596    pub expires_at: Option<u64>,
1597}
1598
1599fn default_importance() -> f32 {
1600    0.5
1601}
1602
1603impl Memory {
1604    /// Create a new memory with current timestamps
1605    pub fn new(id: String, content: String, agent_id: String, memory_type: MemoryType) -> Self {
1606        let now = std::time::SystemTime::now()
1607            .duration_since(std::time::UNIX_EPOCH)
1608            .unwrap_or_default()
1609            .as_secs();
1610        Self {
1611            id,
1612            memory_type,
1613            content,
1614            agent_id,
1615            session_id: None,
1616            importance: 0.5,
1617            tags: Vec::new(),
1618            metadata: None,
1619            created_at: now,
1620            last_accessed_at: now,
1621            access_count: 0,
1622            ttl_seconds: None,
1623            expires_at: None,
1624        }
1625    }
1626
1627    /// Check if this memory has expired
1628    pub fn is_expired(&self) -> bool {
1629        if let Some(expires_at) = self.expires_at {
1630            let now = std::time::SystemTime::now()
1631                .duration_since(std::time::UNIX_EPOCH)
1632                .unwrap_or_default()
1633                .as_secs();
1634            now >= expires_at
1635        } else {
1636            false
1637        }
1638    }
1639
1640    /// Pack memory fields into metadata for Vector storage
1641    pub fn to_vector_metadata(&self) -> serde_json::Value {
1642        let mut meta = serde_json::Map::new();
1643        meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
1644        meta.insert(
1645            "memory_type".to_string(),
1646            serde_json::json!(self.memory_type),
1647        );
1648        meta.insert("content".to_string(), serde_json::json!(self.content));
1649        meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1650        if let Some(ref sid) = self.session_id {
1651            meta.insert("session_id".to_string(), serde_json::json!(sid));
1652        }
1653        meta.insert("importance".to_string(), serde_json::json!(self.importance));
1654        meta.insert("tags".to_string(), serde_json::json!(self.tags));
1655        meta.insert("created_at".to_string(), serde_json::json!(self.created_at));
1656        meta.insert(
1657            "last_accessed_at".to_string(),
1658            serde_json::json!(self.last_accessed_at),
1659        );
1660        meta.insert(
1661            "access_count".to_string(),
1662            serde_json::json!(self.access_count),
1663        );
1664        if let Some(ref ttl) = self.ttl_seconds {
1665            meta.insert("ttl_seconds".to_string(), serde_json::json!(ttl));
1666        }
1667        if let Some(ref expires) = self.expires_at {
1668            meta.insert("expires_at".to_string(), serde_json::json!(expires));
1669        }
1670        if let Some(ref user_meta) = self.metadata {
1671            meta.insert("user_metadata".to_string(), user_meta.clone());
1672        }
1673        serde_json::Value::Object(meta)
1674    }
1675
1676    /// Convert a Memory to a Vector (for storage layer)
1677    pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1678        let mut v = Vector {
1679            id: self.id.clone(),
1680            values: embedding,
1681            metadata: Some(self.to_vector_metadata()),
1682            ttl_seconds: self.ttl_seconds,
1683            expires_at: self.expires_at,
1684        };
1685        v.apply_ttl();
1686        v
1687    }
1688
1689    /// Reconstruct a Memory from a Vector's metadata
1690    pub fn from_vector(vector: &Vector) -> Option<Self> {
1691        let meta = vector.metadata.as_ref()?.as_object()?;
1692        let entry_type = meta.get("_dakera_type")?.as_str()?;
1693        if entry_type != "memory" {
1694            return None;
1695        }
1696
1697        Some(Memory {
1698            id: vector.id.clone(),
1699            memory_type: serde_json::from_value(meta.get("memory_type")?.clone())
1700                .unwrap_or_default(),
1701            content: meta.get("content")?.as_str()?.to_string(),
1702            agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1703            session_id: meta
1704                .get("session_id")
1705                .and_then(|v| v.as_str())
1706                .map(String::from),
1707            importance: meta
1708                .get("importance")
1709                .and_then(|v| v.as_f64())
1710                .unwrap_or(0.5) as f32,
1711            tags: meta
1712                .get("tags")
1713                .and_then(|v| serde_json::from_value(v.clone()).ok())
1714                .unwrap_or_default(),
1715            metadata: meta.get("user_metadata").cloned(),
1716            created_at: meta.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
1717            last_accessed_at: meta
1718                .get("last_accessed_at")
1719                .and_then(|v| v.as_u64())
1720                .unwrap_or(0),
1721            access_count: meta
1722                .get("access_count")
1723                .and_then(|v| v.as_u64())
1724                .unwrap_or(0) as u32,
1725            ttl_seconds: vector.ttl_seconds,
1726            expires_at: vector.expires_at,
1727        })
1728    }
1729}
1730
1731/// An agent session
1732#[derive(Debug, Clone, Serialize, Deserialize)]
1733pub struct Session {
1734    pub id: String,
1735    pub agent_id: String,
1736    pub started_at: u64,
1737    #[serde(skip_serializing_if = "Option::is_none")]
1738    pub ended_at: Option<u64>,
1739    #[serde(skip_serializing_if = "Option::is_none")]
1740    pub summary: Option<String>,
1741    #[serde(skip_serializing_if = "Option::is_none")]
1742    pub metadata: Option<serde_json::Value>,
1743    /// Cached count of memories in this session (updated on store/forget)
1744    #[serde(default)]
1745    pub memory_count: usize,
1746}
1747
1748impl Session {
1749    pub fn new(id: String, agent_id: String) -> Self {
1750        let now = std::time::SystemTime::now()
1751            .duration_since(std::time::UNIX_EPOCH)
1752            .unwrap_or_default()
1753            .as_secs();
1754        Self {
1755            id,
1756            agent_id,
1757            started_at: now,
1758            ended_at: None,
1759            summary: None,
1760            metadata: None,
1761            memory_count: 0,
1762        }
1763    }
1764
1765    /// Pack session into metadata for Vector storage
1766    pub fn to_vector_metadata(&self) -> serde_json::Value {
1767        let mut meta = serde_json::Map::new();
1768        meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
1769        meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1770        meta.insert("started_at".to_string(), serde_json::json!(self.started_at));
1771        if let Some(ref ended) = self.ended_at {
1772            meta.insert("ended_at".to_string(), serde_json::json!(ended));
1773        }
1774        if let Some(ref summary) = self.summary {
1775            meta.insert("summary".to_string(), serde_json::json!(summary));
1776        }
1777        if let Some(ref user_meta) = self.metadata {
1778            meta.insert("user_metadata".to_string(), user_meta.clone());
1779        }
1780        meta.insert(
1781            "memory_count".to_string(),
1782            serde_json::json!(self.memory_count),
1783        );
1784        serde_json::Value::Object(meta)
1785    }
1786
1787    /// Convert to a Vector for storage (use summary or agent_id as embedding source)
1788    pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1789        Vector {
1790            id: self.id.clone(),
1791            values: embedding,
1792            metadata: Some(self.to_vector_metadata()),
1793            ttl_seconds: None,
1794            expires_at: None,
1795        }
1796    }
1797
1798    /// Reconstruct a Session from a Vector's metadata
1799    pub fn from_vector(vector: &Vector) -> Option<Self> {
1800        let meta = vector.metadata.as_ref()?.as_object()?;
1801        let entry_type = meta.get("_dakera_type")?.as_str()?;
1802        if entry_type != "session" {
1803            return None;
1804        }
1805
1806        Some(Session {
1807            id: vector.id.clone(),
1808            agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1809            started_at: meta.get("started_at").and_then(|v| v.as_u64()).unwrap_or(0),
1810            ended_at: meta.get("ended_at").and_then(|v| v.as_u64()),
1811            summary: meta
1812                .get("summary")
1813                .and_then(|v| v.as_str())
1814                .map(String::from),
1815            metadata: meta.get("user_metadata").cloned(),
1816            memory_count: meta
1817                .get("memory_count")
1818                .and_then(|v| v.as_u64())
1819                .unwrap_or(0) as usize,
1820        })
1821    }
1822}
1823
1824/// Strategy for importance decay
1825#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1826#[serde(rename_all = "snake_case")]
1827#[derive(Default)]
1828pub enum DecayStrategy {
1829    #[default]
1830    Exponential,
1831    Linear,
1832    StepFunction,
1833}
1834
1835/// Configuration for importance decay
1836#[derive(Debug, Clone, Serialize, Deserialize)]
1837pub struct DecayConfig {
1838    #[serde(default)]
1839    pub strategy: DecayStrategy,
1840    #[serde(default = "default_half_life_hours")]
1841    pub half_life_hours: f64,
1842    #[serde(default = "default_min_importance")]
1843    pub min_importance: f32,
1844}
1845
1846fn default_half_life_hours() -> f64 {
1847    168.0 // 1 week
1848}
1849
1850fn default_min_importance() -> f32 {
1851    0.01
1852}
1853
1854impl Default for DecayConfig {
1855    fn default() -> Self {
1856        Self {
1857            strategy: DecayStrategy::default(),
1858            half_life_hours: default_half_life_hours(),
1859            min_importance: default_min_importance(),
1860        }
1861    }
1862}
1863
1864// ============================================================================
1865// Dakera Memory Request/Response Types
1866// ============================================================================
1867
1868/// Request to store a memory
1869#[derive(Debug, Deserialize)]
1870pub struct StoreMemoryRequest {
1871    pub content: String,
1872    pub agent_id: String,
1873    #[serde(default)]
1874    pub memory_type: MemoryType,
1875    #[serde(skip_serializing_if = "Option::is_none")]
1876    pub session_id: Option<String>,
1877    #[serde(default = "default_importance")]
1878    pub importance: f32,
1879    #[serde(default)]
1880    pub tags: Vec<String>,
1881    #[serde(skip_serializing_if = "Option::is_none")]
1882    pub metadata: Option<serde_json::Value>,
1883    #[serde(skip_serializing_if = "Option::is_none")]
1884    pub ttl_seconds: Option<u64>,
1885    /// Optional custom ID (auto-generated if not provided)
1886    #[serde(skip_serializing_if = "Option::is_none")]
1887    pub id: Option<String>,
1888}
1889
1890/// Response from storing a memory
1891#[derive(Debug, Serialize)]
1892pub struct StoreMemoryResponse {
1893    pub memory: Memory,
1894    pub embedding_time_ms: u64,
1895}
1896
1897/// Request to recall memories by semantic query
1898#[derive(Debug, Deserialize)]
1899pub struct RecallRequest {
1900    pub query: String,
1901    pub agent_id: String,
1902    #[serde(default = "default_top_k")]
1903    pub top_k: usize,
1904    #[serde(default)]
1905    pub memory_type: Option<MemoryType>,
1906    #[serde(default)]
1907    pub session_id: Option<String>,
1908    #[serde(default)]
1909    pub tags: Option<Vec<String>>,
1910    #[serde(default)]
1911    pub min_importance: Option<f32>,
1912    /// Include importance-weighted re-ranking (default: true)
1913    #[serde(default = "default_true")]
1914    pub importance_weighted: bool,
1915}
1916
1917/// Single recall result
1918#[derive(Debug, Serialize, Deserialize)]
1919pub struct RecallResult {
1920    pub memory: Memory,
1921    pub score: f32,
1922    /// Score after importance-weighted re-ranking
1923    #[serde(skip_serializing_if = "Option::is_none")]
1924    pub weighted_score: Option<f32>,
1925    /// Always-on multi-signal smart score (vector + importance + recency + frequency)
1926    #[serde(skip_serializing_if = "Option::is_none")]
1927    pub smart_score: Option<f32>,
1928}
1929
1930/// Response from recall
1931#[derive(Debug, Serialize)]
1932pub struct RecallResponse {
1933    pub memories: Vec<RecallResult>,
1934    pub query_embedding_time_ms: u64,
1935    pub search_time_ms: u64,
1936}
1937
1938/// Request to forget (delete) memories
1939#[derive(Debug, Deserialize)]
1940pub struct ForgetRequest {
1941    pub agent_id: String,
1942    #[serde(default)]
1943    pub memory_ids: Option<Vec<String>>,
1944    #[serde(default)]
1945    pub memory_type: Option<MemoryType>,
1946    #[serde(default)]
1947    pub session_id: Option<String>,
1948    #[serde(default)]
1949    pub tags: Option<Vec<String>>,
1950    /// Delete memories below this importance threshold
1951    #[serde(default)]
1952    pub below_importance: Option<f32>,
1953}
1954
1955/// Response from forget
1956#[derive(Debug, Serialize)]
1957pub struct ForgetResponse {
1958    pub deleted_count: usize,
1959}
1960
1961/// Request to update a memory
1962#[derive(Debug, Deserialize)]
1963pub struct UpdateMemoryRequest {
1964    #[serde(default)]
1965    pub content: Option<String>,
1966    #[serde(default)]
1967    pub importance: Option<f32>,
1968    #[serde(default)]
1969    pub tags: Option<Vec<String>>,
1970    #[serde(default)]
1971    pub metadata: Option<serde_json::Value>,
1972    #[serde(default)]
1973    pub memory_type: Option<MemoryType>,
1974}
1975
1976/// Request to update importance of a memory
1977#[derive(Debug, Deserialize)]
1978pub struct UpdateImportanceRequest {
1979    pub memory_id: String,
1980    pub importance: f32,
1981    pub agent_id: String,
1982}
1983
1984/// Request to consolidate related memories
1985#[derive(Debug, Deserialize)]
1986pub struct ConsolidateRequest {
1987    pub agent_id: String,
1988    /// Memory IDs to consolidate (if empty, auto-detect similar memories)
1989    #[serde(default)]
1990    pub memory_ids: Option<Vec<String>>,
1991    /// Similarity threshold for auto-detection (default: 0.85)
1992    #[serde(default = "default_consolidation_threshold")]
1993    pub threshold: f32,
1994    /// Type for the consolidated memory
1995    #[serde(default)]
1996    pub target_type: Option<MemoryType>,
1997}
1998
1999fn default_consolidation_threshold() -> f32 {
2000    0.85
2001}
2002
2003/// Response from consolidation
2004#[derive(Debug, Serialize)]
2005pub struct ConsolidateResponse {
2006    pub consolidated_memory: Memory,
2007    pub source_memory_ids: Vec<String>,
2008    pub memories_removed: usize,
2009}
2010
2011/// Feedback signal for active learning
2012#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2013#[serde(rename_all = "lowercase")]
2014pub enum FeedbackSignal {
2015    Positive,
2016    Negative,
2017}
2018
2019/// Request to provide feedback on a recalled memory (active learning)
2020#[derive(Debug, Deserialize)]
2021pub struct FeedbackRequest {
2022    pub agent_id: String,
2023    pub memory_id: String,
2024    pub signal: FeedbackSignal,
2025}
2026
2027/// Response from feedback
2028#[derive(Debug, Serialize)]
2029pub struct FeedbackResponse {
2030    pub memory_id: String,
2031    pub new_importance: f32,
2032    pub signal: FeedbackSignal,
2033}
2034
2035/// Request for advanced memory search
2036#[derive(Debug, Deserialize)]
2037pub struct SearchMemoriesRequest {
2038    pub agent_id: String,
2039    #[serde(default)]
2040    pub query: Option<String>,
2041    #[serde(default)]
2042    pub memory_type: Option<MemoryType>,
2043    #[serde(default)]
2044    pub session_id: Option<String>,
2045    #[serde(default)]
2046    pub tags: Option<Vec<String>>,
2047    #[serde(default)]
2048    pub min_importance: Option<f32>,
2049    #[serde(default)]
2050    pub max_importance: Option<f32>,
2051    #[serde(default)]
2052    pub created_after: Option<u64>,
2053    #[serde(default)]
2054    pub created_before: Option<u64>,
2055    #[serde(default = "default_top_k")]
2056    pub top_k: usize,
2057    #[serde(default)]
2058    pub sort_by: Option<MemorySortField>,
2059}
2060
2061/// Fields to sort memories by
2062#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
2063#[serde(rename_all = "snake_case")]
2064pub enum MemorySortField {
2065    CreatedAt,
2066    LastAccessedAt,
2067    Importance,
2068    AccessCount,
2069}
2070
2071/// Response from memory search
2072#[derive(Debug, Serialize)]
2073pub struct SearchMemoriesResponse {
2074    pub memories: Vec<RecallResult>,
2075    pub total_count: usize,
2076}
2077
2078// ============================================================================
2079// Dakera Session Request/Response Types
2080// ============================================================================
2081
2082/// Request to start a session
2083#[derive(Debug, Deserialize)]
2084pub struct SessionStartRequest {
2085    pub agent_id: String,
2086    #[serde(skip_serializing_if = "Option::is_none")]
2087    pub metadata: Option<serde_json::Value>,
2088    /// Optional custom session ID
2089    #[serde(skip_serializing_if = "Option::is_none")]
2090    pub id: Option<String>,
2091}
2092
2093/// Response from starting a session
2094#[derive(Debug, Serialize)]
2095pub struct SessionStartResponse {
2096    pub session: Session,
2097}
2098
2099/// Request to end a session
2100#[derive(Debug, Deserialize)]
2101pub struct SessionEndRequest {
2102    #[serde(default)]
2103    pub summary: Option<String>,
2104    /// Auto-generate summary from session memories
2105    #[serde(default)]
2106    pub auto_summarize: bool,
2107}
2108
2109/// Response from ending a session
2110#[derive(Debug, Serialize)]
2111pub struct SessionEndResponse {
2112    pub session: Session,
2113    pub memory_count: usize,
2114}
2115
2116/// Response listing sessions
2117#[derive(Debug, Serialize)]
2118pub struct ListSessionsResponse {
2119    pub sessions: Vec<Session>,
2120    pub total: usize,
2121}
2122
2123/// Response for session memories
2124#[derive(Debug, Serialize)]
2125pub struct SessionMemoriesResponse {
2126    pub session: Session,
2127    pub memories: Vec<Memory>,
2128    /// Total number of memories in this session (before pagination)
2129    #[serde(skip_serializing_if = "Option::is_none")]
2130    pub total: Option<usize>,
2131}
2132
2133// ============================================================================
2134// Dakera Agent & Knowledge Types
2135// ============================================================================
2136
2137/// Lightweight agent summary for batch listing (uses count() not get_all)
2138#[derive(Debug, Serialize, Deserialize, Clone)]
2139pub struct AgentSummary {
2140    pub agent_id: String,
2141    pub memory_count: usize,
2142    pub session_count: usize,
2143    pub active_sessions: usize,
2144}
2145
2146/// Agent memory statistics
2147#[derive(Debug, Serialize)]
2148pub struct AgentStats {
2149    pub agent_id: String,
2150    pub total_memories: usize,
2151    pub memories_by_type: std::collections::HashMap<String, usize>,
2152    pub total_sessions: usize,
2153    pub active_sessions: usize,
2154    pub avg_importance: f32,
2155    pub oldest_memory_at: Option<u64>,
2156    pub newest_memory_at: Option<u64>,
2157}
2158
2159/// Request for knowledge graph traversal
2160#[derive(Debug, Deserialize)]
2161pub struct KnowledgeGraphRequest {
2162    pub agent_id: String,
2163    pub memory_id: String,
2164    #[serde(default = "default_graph_depth")]
2165    pub depth: usize,
2166    #[serde(default = "default_graph_min_similarity")]
2167    pub min_similarity: f32,
2168}
2169
2170fn default_graph_depth() -> usize {
2171    2
2172}
2173
2174fn default_graph_min_similarity() -> f32 {
2175    0.7
2176}
2177
2178/// Knowledge graph node
2179#[derive(Debug, Serialize)]
2180pub struct KnowledgeGraphNode {
2181    pub memory: Memory,
2182    pub similarity: f32,
2183    pub related: Vec<KnowledgeGraphEdge>,
2184}
2185
2186/// Knowledge graph edge
2187#[derive(Debug, Serialize)]
2188pub struct KnowledgeGraphEdge {
2189    pub memory_id: String,
2190    pub similarity: f32,
2191    pub shared_tags: Vec<String>,
2192}
2193
2194/// Response from knowledge graph query
2195#[derive(Debug, Serialize)]
2196pub struct KnowledgeGraphResponse {
2197    pub root: KnowledgeGraphNode,
2198    pub total_nodes: usize,
2199}
2200
2201// ============================================================================
2202// Full Knowledge Graph Types (Global Network Topology)
2203// ============================================================================
2204
2205fn default_full_graph_max_nodes() -> usize {
2206    200
2207}
2208
2209fn default_full_graph_min_similarity() -> f32 {
2210    0.50
2211}
2212
2213fn default_full_graph_cluster_threshold() -> f32 {
2214    0.60
2215}
2216
2217fn default_full_graph_max_edges_per_node() -> usize {
2218    8
2219}
2220
2221/// Request for full knowledge graph (all memories, pairwise similarity)
2222#[derive(Debug, Deserialize)]
2223pub struct FullKnowledgeGraphRequest {
2224    pub agent_id: String,
2225    #[serde(default = "default_full_graph_max_nodes")]
2226    pub max_nodes: usize,
2227    #[serde(default = "default_full_graph_min_similarity")]
2228    pub min_similarity: f32,
2229    #[serde(default = "default_full_graph_cluster_threshold")]
2230    pub cluster_threshold: f32,
2231    #[serde(default = "default_full_graph_max_edges_per_node")]
2232    pub max_edges_per_node: usize,
2233}
2234
2235/// A node in the full knowledge graph
2236#[derive(Debug, Serialize)]
2237pub struct FullGraphNode {
2238    pub id: String,
2239    pub content: String,
2240    pub memory_type: String,
2241    pub importance: f32,
2242    pub tags: Vec<String>,
2243    pub created_at: Option<String>,
2244    pub cluster_id: usize,
2245    pub centrality: f32,
2246}
2247
2248/// An edge in the full knowledge graph
2249#[derive(Debug, Serialize)]
2250pub struct FullGraphEdge {
2251    pub source: String,
2252    pub target: String,
2253    pub similarity: f32,
2254    pub shared_tags: Vec<String>,
2255}
2256
2257/// A cluster of related memories
2258#[derive(Debug, Serialize)]
2259pub struct GraphCluster {
2260    pub id: usize,
2261    pub node_count: usize,
2262    pub top_tags: Vec<String>,
2263    pub avg_importance: f32,
2264}
2265
2266/// Statistics about the full knowledge graph
2267#[derive(Debug, Serialize)]
2268pub struct GraphStats {
2269    pub total_memories: usize,
2270    pub included_memories: usize,
2271    pub total_edges: usize,
2272    pub cluster_count: usize,
2273    pub density: f32,
2274    pub hub_memory_id: Option<String>,
2275}
2276
2277/// Response from full knowledge graph query
2278#[derive(Debug, Serialize)]
2279pub struct FullKnowledgeGraphResponse {
2280    pub nodes: Vec<FullGraphNode>,
2281    pub edges: Vec<FullGraphEdge>,
2282    pub clusters: Vec<GraphCluster>,
2283    pub stats: GraphStats,
2284}
2285
2286/// Request to summarize memories
2287#[derive(Debug, Deserialize)]
2288pub struct SummarizeRequest {
2289    pub agent_id: String,
2290    pub memory_ids: Vec<String>,
2291    #[serde(default)]
2292    pub target_type: Option<MemoryType>,
2293}
2294
2295/// Response from summarization
2296#[derive(Debug, Serialize)]
2297pub struct SummarizeResponse {
2298    pub summary_memory: Memory,
2299    pub source_count: usize,
2300}
2301
2302/// Request to deduplicate memories
2303#[derive(Debug, Deserialize)]
2304pub struct DeduplicateRequest {
2305    pub agent_id: String,
2306    #[serde(default = "default_dedup_threshold")]
2307    pub threshold: f32,
2308    #[serde(default)]
2309    pub memory_type: Option<MemoryType>,
2310    /// Dry run — report duplicates without merging
2311    #[serde(default)]
2312    pub dry_run: bool,
2313}
2314
2315fn default_dedup_threshold() -> f32 {
2316    0.92
2317}
2318
2319/// A group of duplicate memories
2320#[derive(Debug, Serialize)]
2321pub struct DuplicateGroup {
2322    pub canonical_id: String,
2323    pub duplicate_ids: Vec<String>,
2324    pub avg_similarity: f32,
2325}
2326
2327/// Response from deduplication
2328#[derive(Debug, Serialize)]
2329pub struct DeduplicateResponse {
2330    pub groups: Vec<DuplicateGroup>,
2331    pub duplicates_found: usize,
2332    pub duplicates_merged: usize,
2333}
2334
2335// ============================================================================
2336// Cross-Agent Memory Network Types (DASH-A)
2337// ============================================================================
2338
2339fn default_cross_agent_min_similarity() -> f32 {
2340    0.3
2341}
2342
2343fn default_cross_agent_max_nodes_per_agent() -> usize {
2344    50
2345}
2346
2347fn default_cross_agent_max_cross_edges() -> usize {
2348    200
2349}
2350
2351/// Request for cross-agent memory network graph
2352#[derive(Debug, Deserialize)]
2353pub struct CrossAgentNetworkRequest {
2354    /// Specific agent IDs to include (None = all agents)
2355    #[serde(default)]
2356    pub agent_ids: Option<Vec<String>>,
2357    /// Minimum cosine similarity for a cross-agent edge (default 0.3)
2358    #[serde(default = "default_cross_agent_min_similarity")]
2359    pub min_similarity: f32,
2360    /// Maximum memories per agent to include (top N by importance, default 50)
2361    #[serde(default = "default_cross_agent_max_nodes_per_agent")]
2362    pub max_nodes_per_agent: usize,
2363    /// Minimum importance score for a memory to be included (default 0.0)
2364    #[serde(default)]
2365    pub min_importance: f32,
2366    /// Maximum cross-agent edges to return (default 200)
2367    #[serde(default = "default_cross_agent_max_cross_edges")]
2368    pub max_cross_edges: usize,
2369}
2370
2371/// Summary info for an agent in the cross-agent network
2372#[derive(Debug, Serialize)]
2373pub struct AgentNetworkInfo {
2374    pub agent_id: String,
2375    pub memory_count: usize,
2376    pub avg_importance: f32,
2377}
2378
2379/// A memory node in the cross-agent network (includes agent_id)
2380#[derive(Debug, Serialize)]
2381pub struct AgentNetworkNode {
2382    pub id: String,
2383    pub agent_id: String,
2384    pub content: String,
2385    pub importance: f32,
2386    pub tags: Vec<String>,
2387    pub memory_type: String,
2388    pub created_at: u64,
2389}
2390
2391/// An edge between memories from two different agents
2392#[derive(Debug, Serialize)]
2393pub struct AgentNetworkEdge {
2394    pub source: String,
2395    pub target: String,
2396    pub source_agent: String,
2397    pub target_agent: String,
2398    pub similarity: f32,
2399}
2400
2401/// Statistics for the cross-agent network
2402#[derive(Debug, Serialize)]
2403pub struct AgentNetworkStats {
2404    pub total_agents: usize,
2405    pub total_nodes: usize,
2406    pub total_cross_edges: usize,
2407    pub density: f32,
2408}
2409
2410/// Response from cross-agent network query
2411#[derive(Debug, Serialize)]
2412pub struct CrossAgentNetworkResponse {
2413    pub node_count: usize,
2414    pub agents: Vec<AgentNetworkInfo>,
2415    pub nodes: Vec<AgentNetworkNode>,
2416    pub edges: Vec<AgentNetworkEdge>,
2417    pub stats: AgentNetworkStats,
2418}