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 (optional, defaults to "minilm")
1398    #[serde(default)]
1399    pub model: Option<String>,
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: String,
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 (optional, must match upsert model)
1433    #[serde(default)]
1434    pub model: Option<String>,
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: String,
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
1483    #[serde(default)]
1484    pub model: Option<String>,
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: String,
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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1502#[serde(rename_all = "kebab-case")]
1503pub enum EmbeddingModelType {
1504    /// all-MiniLM-L6-v2 - Fast and efficient
1505    #[default]
1506    MiniLM,
1507    /// BAAI/bge-small-en-v1.5 - Balanced
1508    BgeSmall,
1509    /// intfloat/e5-small-v2 - Quality-focused
1510    E5Small,
1511}
1512
1513impl EmbeddingModelType {
1514    /// Get the dimension for this model
1515    pub fn dimension(&self) -> usize {
1516        match self {
1517            EmbeddingModelType::MiniLM => 384,
1518            EmbeddingModelType::BgeSmall => 384,
1519            EmbeddingModelType::E5Small => 384,
1520        }
1521    }
1522
1523    /// Parse from string
1524    pub fn parse(s: &str) -> Option<Self> {
1525        match s.to_lowercase().as_str() {
1526            "minilm" | "all-minilm-l6-v2" => Some(Self::MiniLM),
1527            "bge-small" | "bge" => Some(Self::BgeSmall),
1528            "e5-small" | "e5" => Some(Self::E5Small),
1529            _ => None,
1530        }
1531    }
1532}
1533
1534// ============================================================================
1535// Dakera Memory Types — AI Agent Memory Platform
1536// ============================================================================
1537
1538/// Type of memory stored by an agent
1539#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1540#[serde(rename_all = "snake_case")]
1541#[derive(Default)]
1542pub enum MemoryType {
1543    /// Personal experiences and events
1544    #[default]
1545    Episodic,
1546    /// Facts and general knowledge
1547    Semantic,
1548    /// How-to knowledge and skills
1549    Procedural,
1550    /// Short-term, temporary context
1551    Working,
1552}
1553
1554impl std::fmt::Display for MemoryType {
1555    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1556        match self {
1557            MemoryType::Episodic => write!(f, "episodic"),
1558            MemoryType::Semantic => write!(f, "semantic"),
1559            MemoryType::Procedural => write!(f, "procedural"),
1560            MemoryType::Working => write!(f, "working"),
1561        }
1562    }
1563}
1564
1565/// A memory stored by an AI agent
1566#[derive(Debug, Clone, Serialize, Deserialize)]
1567pub struct Memory {
1568    pub id: String,
1569    #[serde(default)]
1570    pub memory_type: MemoryType,
1571    pub content: String,
1572    pub agent_id: String,
1573    #[serde(skip_serializing_if = "Option::is_none")]
1574    pub session_id: Option<String>,
1575    #[serde(default = "default_importance")]
1576    pub importance: f32,
1577    #[serde(default)]
1578    pub tags: Vec<String>,
1579    #[serde(skip_serializing_if = "Option::is_none")]
1580    pub metadata: Option<serde_json::Value>,
1581    pub created_at: u64,
1582    pub last_accessed_at: u64,
1583    #[serde(default)]
1584    pub access_count: u32,
1585    #[serde(skip_serializing_if = "Option::is_none")]
1586    pub ttl_seconds: Option<u64>,
1587    #[serde(skip_serializing_if = "Option::is_none")]
1588    pub expires_at: Option<u64>,
1589}
1590
1591fn default_importance() -> f32 {
1592    0.5
1593}
1594
1595impl Memory {
1596    /// Create a new memory with current timestamps
1597    pub fn new(id: String, content: String, agent_id: String, memory_type: MemoryType) -> Self {
1598        let now = std::time::SystemTime::now()
1599            .duration_since(std::time::UNIX_EPOCH)
1600            .unwrap_or_default()
1601            .as_secs();
1602        Self {
1603            id,
1604            memory_type,
1605            content,
1606            agent_id,
1607            session_id: None,
1608            importance: 0.5,
1609            tags: Vec::new(),
1610            metadata: None,
1611            created_at: now,
1612            last_accessed_at: now,
1613            access_count: 0,
1614            ttl_seconds: None,
1615            expires_at: None,
1616        }
1617    }
1618
1619    /// Check if this memory has expired
1620    pub fn is_expired(&self) -> bool {
1621        if let Some(expires_at) = self.expires_at {
1622            let now = std::time::SystemTime::now()
1623                .duration_since(std::time::UNIX_EPOCH)
1624                .unwrap_or_default()
1625                .as_secs();
1626            now >= expires_at
1627        } else {
1628            false
1629        }
1630    }
1631
1632    /// Pack memory fields into metadata for Vector storage
1633    pub fn to_vector_metadata(&self) -> serde_json::Value {
1634        let mut meta = serde_json::Map::new();
1635        meta.insert("_dakera_type".to_string(), serde_json::json!("memory"));
1636        meta.insert(
1637            "memory_type".to_string(),
1638            serde_json::json!(self.memory_type),
1639        );
1640        meta.insert("content".to_string(), serde_json::json!(self.content));
1641        meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1642        if let Some(ref sid) = self.session_id {
1643            meta.insert("session_id".to_string(), serde_json::json!(sid));
1644        }
1645        meta.insert("importance".to_string(), serde_json::json!(self.importance));
1646        meta.insert("tags".to_string(), serde_json::json!(self.tags));
1647        meta.insert("created_at".to_string(), serde_json::json!(self.created_at));
1648        meta.insert(
1649            "last_accessed_at".to_string(),
1650            serde_json::json!(self.last_accessed_at),
1651        );
1652        meta.insert(
1653            "access_count".to_string(),
1654            serde_json::json!(self.access_count),
1655        );
1656        if let Some(ref ttl) = self.ttl_seconds {
1657            meta.insert("ttl_seconds".to_string(), serde_json::json!(ttl));
1658        }
1659        if let Some(ref expires) = self.expires_at {
1660            meta.insert("expires_at".to_string(), serde_json::json!(expires));
1661        }
1662        if let Some(ref user_meta) = self.metadata {
1663            meta.insert("user_metadata".to_string(), user_meta.clone());
1664        }
1665        serde_json::Value::Object(meta)
1666    }
1667
1668    /// Convert a Memory to a Vector (for storage layer)
1669    pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1670        let mut v = Vector {
1671            id: self.id.clone(),
1672            values: embedding,
1673            metadata: Some(self.to_vector_metadata()),
1674            ttl_seconds: self.ttl_seconds,
1675            expires_at: self.expires_at,
1676        };
1677        v.apply_ttl();
1678        v
1679    }
1680
1681    /// Reconstruct a Memory from a Vector's metadata
1682    pub fn from_vector(vector: &Vector) -> Option<Self> {
1683        let meta = vector.metadata.as_ref()?.as_object()?;
1684        let entry_type = meta.get("_dakera_type")?.as_str()?;
1685        if entry_type != "memory" {
1686            return None;
1687        }
1688
1689        Some(Memory {
1690            id: vector.id.clone(),
1691            memory_type: serde_json::from_value(meta.get("memory_type")?.clone())
1692                .unwrap_or_default(),
1693            content: meta.get("content")?.as_str()?.to_string(),
1694            agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1695            session_id: meta
1696                .get("session_id")
1697                .and_then(|v| v.as_str())
1698                .map(String::from),
1699            importance: meta
1700                .get("importance")
1701                .and_then(|v| v.as_f64())
1702                .unwrap_or(0.5) as f32,
1703            tags: meta
1704                .get("tags")
1705                .and_then(|v| serde_json::from_value(v.clone()).ok())
1706                .unwrap_or_default(),
1707            metadata: meta.get("user_metadata").cloned(),
1708            created_at: meta.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0),
1709            last_accessed_at: meta
1710                .get("last_accessed_at")
1711                .and_then(|v| v.as_u64())
1712                .unwrap_or(0),
1713            access_count: meta
1714                .get("access_count")
1715                .and_then(|v| v.as_u64())
1716                .unwrap_or(0) as u32,
1717            ttl_seconds: vector.ttl_seconds,
1718            expires_at: vector.expires_at,
1719        })
1720    }
1721}
1722
1723/// An agent session
1724#[derive(Debug, Clone, Serialize, Deserialize)]
1725pub struct Session {
1726    pub id: String,
1727    pub agent_id: String,
1728    pub started_at: u64,
1729    #[serde(skip_serializing_if = "Option::is_none")]
1730    pub ended_at: Option<u64>,
1731    #[serde(skip_serializing_if = "Option::is_none")]
1732    pub summary: Option<String>,
1733    #[serde(skip_serializing_if = "Option::is_none")]
1734    pub metadata: Option<serde_json::Value>,
1735    /// Cached count of memories in this session (updated on store/forget)
1736    #[serde(default)]
1737    pub memory_count: usize,
1738}
1739
1740impl Session {
1741    pub fn new(id: String, agent_id: String) -> Self {
1742        let now = std::time::SystemTime::now()
1743            .duration_since(std::time::UNIX_EPOCH)
1744            .unwrap_or_default()
1745            .as_secs();
1746        Self {
1747            id,
1748            agent_id,
1749            started_at: now,
1750            ended_at: None,
1751            summary: None,
1752            metadata: None,
1753            memory_count: 0,
1754        }
1755    }
1756
1757    /// Pack session into metadata for Vector storage
1758    pub fn to_vector_metadata(&self) -> serde_json::Value {
1759        let mut meta = serde_json::Map::new();
1760        meta.insert("_dakera_type".to_string(), serde_json::json!("session"));
1761        meta.insert("agent_id".to_string(), serde_json::json!(self.agent_id));
1762        meta.insert("started_at".to_string(), serde_json::json!(self.started_at));
1763        if let Some(ref ended) = self.ended_at {
1764            meta.insert("ended_at".to_string(), serde_json::json!(ended));
1765        }
1766        if let Some(ref summary) = self.summary {
1767            meta.insert("summary".to_string(), serde_json::json!(summary));
1768        }
1769        if let Some(ref user_meta) = self.metadata {
1770            meta.insert("user_metadata".to_string(), user_meta.clone());
1771        }
1772        meta.insert(
1773            "memory_count".to_string(),
1774            serde_json::json!(self.memory_count),
1775        );
1776        serde_json::Value::Object(meta)
1777    }
1778
1779    /// Convert to a Vector for storage (use summary or agent_id as embedding source)
1780    pub fn to_vector(&self, embedding: Vec<f32>) -> Vector {
1781        Vector {
1782            id: self.id.clone(),
1783            values: embedding,
1784            metadata: Some(self.to_vector_metadata()),
1785            ttl_seconds: None,
1786            expires_at: None,
1787        }
1788    }
1789
1790    /// Reconstruct a Session from a Vector's metadata
1791    pub fn from_vector(vector: &Vector) -> Option<Self> {
1792        let meta = vector.metadata.as_ref()?.as_object()?;
1793        let entry_type = meta.get("_dakera_type")?.as_str()?;
1794        if entry_type != "session" {
1795            return None;
1796        }
1797
1798        Some(Session {
1799            id: vector.id.clone(),
1800            agent_id: meta.get("agent_id")?.as_str()?.to_string(),
1801            started_at: meta.get("started_at").and_then(|v| v.as_u64()).unwrap_or(0),
1802            ended_at: meta.get("ended_at").and_then(|v| v.as_u64()),
1803            summary: meta
1804                .get("summary")
1805                .and_then(|v| v.as_str())
1806                .map(String::from),
1807            metadata: meta.get("user_metadata").cloned(),
1808            memory_count: meta
1809                .get("memory_count")
1810                .and_then(|v| v.as_u64())
1811                .unwrap_or(0) as usize,
1812        })
1813    }
1814}
1815
1816/// Strategy for importance decay
1817#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1818#[serde(rename_all = "snake_case")]
1819#[derive(Default)]
1820pub enum DecayStrategy {
1821    #[default]
1822    Exponential,
1823    Linear,
1824    StepFunction,
1825}
1826
1827/// Configuration for importance decay
1828#[derive(Debug, Clone, Serialize, Deserialize)]
1829pub struct DecayConfig {
1830    #[serde(default)]
1831    pub strategy: DecayStrategy,
1832    #[serde(default = "default_half_life_hours")]
1833    pub half_life_hours: f64,
1834    #[serde(default = "default_min_importance")]
1835    pub min_importance: f32,
1836}
1837
1838fn default_half_life_hours() -> f64 {
1839    168.0 // 1 week
1840}
1841
1842fn default_min_importance() -> f32 {
1843    0.01
1844}
1845
1846impl Default for DecayConfig {
1847    fn default() -> Self {
1848        Self {
1849            strategy: DecayStrategy::default(),
1850            half_life_hours: default_half_life_hours(),
1851            min_importance: default_min_importance(),
1852        }
1853    }
1854}
1855
1856// ============================================================================
1857// Dakera Memory Request/Response Types
1858// ============================================================================
1859
1860/// Request to store a memory
1861#[derive(Debug, Deserialize)]
1862pub struct StoreMemoryRequest {
1863    pub content: String,
1864    pub agent_id: String,
1865    #[serde(default)]
1866    pub memory_type: MemoryType,
1867    #[serde(skip_serializing_if = "Option::is_none")]
1868    pub session_id: Option<String>,
1869    #[serde(default = "default_importance")]
1870    pub importance: f32,
1871    #[serde(default)]
1872    pub tags: Vec<String>,
1873    #[serde(skip_serializing_if = "Option::is_none")]
1874    pub metadata: Option<serde_json::Value>,
1875    #[serde(skip_serializing_if = "Option::is_none")]
1876    pub ttl_seconds: Option<u64>,
1877    /// Optional custom ID (auto-generated if not provided)
1878    #[serde(skip_serializing_if = "Option::is_none")]
1879    pub id: Option<String>,
1880}
1881
1882/// Response from storing a memory
1883#[derive(Debug, Serialize)]
1884pub struct StoreMemoryResponse {
1885    pub memory: Memory,
1886    pub embedding_time_ms: u64,
1887}
1888
1889/// Request to recall memories by semantic query
1890#[derive(Debug, Deserialize)]
1891pub struct RecallRequest {
1892    pub query: String,
1893    pub agent_id: String,
1894    #[serde(default = "default_top_k")]
1895    pub top_k: usize,
1896    #[serde(default)]
1897    pub memory_type: Option<MemoryType>,
1898    #[serde(default)]
1899    pub session_id: Option<String>,
1900    #[serde(default)]
1901    pub tags: Option<Vec<String>>,
1902    #[serde(default)]
1903    pub min_importance: Option<f32>,
1904    /// Include importance-weighted re-ranking (default: true)
1905    #[serde(default = "default_true")]
1906    pub importance_weighted: bool,
1907}
1908
1909/// Single recall result
1910#[derive(Debug, Serialize, Deserialize)]
1911pub struct RecallResult {
1912    pub memory: Memory,
1913    pub score: f32,
1914    /// Score after importance-weighted re-ranking
1915    #[serde(skip_serializing_if = "Option::is_none")]
1916    pub weighted_score: Option<f32>,
1917    /// Always-on multi-signal smart score (vector + importance + recency + frequency)
1918    #[serde(skip_serializing_if = "Option::is_none")]
1919    pub smart_score: Option<f32>,
1920}
1921
1922/// Response from recall
1923#[derive(Debug, Serialize)]
1924pub struct RecallResponse {
1925    pub memories: Vec<RecallResult>,
1926    pub query_embedding_time_ms: u64,
1927    pub search_time_ms: u64,
1928}
1929
1930/// Request to forget (delete) memories
1931#[derive(Debug, Deserialize)]
1932pub struct ForgetRequest {
1933    pub agent_id: String,
1934    #[serde(default)]
1935    pub memory_ids: Option<Vec<String>>,
1936    #[serde(default)]
1937    pub memory_type: Option<MemoryType>,
1938    #[serde(default)]
1939    pub session_id: Option<String>,
1940    #[serde(default)]
1941    pub tags: Option<Vec<String>>,
1942    /// Delete memories below this importance threshold
1943    #[serde(default)]
1944    pub below_importance: Option<f32>,
1945}
1946
1947/// Response from forget
1948#[derive(Debug, Serialize)]
1949pub struct ForgetResponse {
1950    pub deleted_count: usize,
1951}
1952
1953/// Request to update a memory
1954#[derive(Debug, Deserialize)]
1955pub struct UpdateMemoryRequest {
1956    #[serde(default)]
1957    pub content: Option<String>,
1958    #[serde(default)]
1959    pub importance: Option<f32>,
1960    #[serde(default)]
1961    pub tags: Option<Vec<String>>,
1962    #[serde(default)]
1963    pub metadata: Option<serde_json::Value>,
1964    #[serde(default)]
1965    pub memory_type: Option<MemoryType>,
1966}
1967
1968/// Request to update importance of a memory
1969#[derive(Debug, Deserialize)]
1970pub struct UpdateImportanceRequest {
1971    pub memory_id: String,
1972    pub importance: f32,
1973    pub agent_id: String,
1974}
1975
1976/// Request to consolidate related memories
1977#[derive(Debug, Deserialize)]
1978pub struct ConsolidateRequest {
1979    pub agent_id: String,
1980    /// Memory IDs to consolidate (if empty, auto-detect similar memories)
1981    #[serde(default)]
1982    pub memory_ids: Option<Vec<String>>,
1983    /// Similarity threshold for auto-detection (default: 0.85)
1984    #[serde(default = "default_consolidation_threshold")]
1985    pub threshold: f32,
1986    /// Type for the consolidated memory
1987    #[serde(default)]
1988    pub target_type: Option<MemoryType>,
1989}
1990
1991fn default_consolidation_threshold() -> f32 {
1992    0.85
1993}
1994
1995/// Response from consolidation
1996#[derive(Debug, Serialize)]
1997pub struct ConsolidateResponse {
1998    pub consolidated_memory: Memory,
1999    pub source_memory_ids: Vec<String>,
2000    pub memories_removed: usize,
2001}
2002
2003/// Feedback signal for active learning
2004#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2005#[serde(rename_all = "lowercase")]
2006pub enum FeedbackSignal {
2007    Positive,
2008    Negative,
2009}
2010
2011/// Request to provide feedback on a recalled memory (active learning)
2012#[derive(Debug, Deserialize)]
2013pub struct FeedbackRequest {
2014    pub agent_id: String,
2015    pub memory_id: String,
2016    pub signal: FeedbackSignal,
2017}
2018
2019/// Response from feedback
2020#[derive(Debug, Serialize)]
2021pub struct FeedbackResponse {
2022    pub memory_id: String,
2023    pub new_importance: f32,
2024    pub signal: FeedbackSignal,
2025}
2026
2027/// Request for advanced memory search
2028#[derive(Debug, Deserialize)]
2029pub struct SearchMemoriesRequest {
2030    pub agent_id: String,
2031    #[serde(default)]
2032    pub query: Option<String>,
2033    #[serde(default)]
2034    pub memory_type: Option<MemoryType>,
2035    #[serde(default)]
2036    pub session_id: Option<String>,
2037    #[serde(default)]
2038    pub tags: Option<Vec<String>>,
2039    #[serde(default)]
2040    pub min_importance: Option<f32>,
2041    #[serde(default)]
2042    pub max_importance: Option<f32>,
2043    #[serde(default)]
2044    pub created_after: Option<u64>,
2045    #[serde(default)]
2046    pub created_before: Option<u64>,
2047    #[serde(default = "default_top_k")]
2048    pub top_k: usize,
2049    #[serde(default)]
2050    pub sort_by: Option<MemorySortField>,
2051}
2052
2053/// Fields to sort memories by
2054#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
2055#[serde(rename_all = "snake_case")]
2056pub enum MemorySortField {
2057    CreatedAt,
2058    LastAccessedAt,
2059    Importance,
2060    AccessCount,
2061}
2062
2063/// Response from memory search
2064#[derive(Debug, Serialize)]
2065pub struct SearchMemoriesResponse {
2066    pub memories: Vec<RecallResult>,
2067    pub total_count: usize,
2068}
2069
2070// ============================================================================
2071// Dakera Session Request/Response Types
2072// ============================================================================
2073
2074/// Request to start a session
2075#[derive(Debug, Deserialize)]
2076pub struct SessionStartRequest {
2077    pub agent_id: String,
2078    #[serde(skip_serializing_if = "Option::is_none")]
2079    pub metadata: Option<serde_json::Value>,
2080    /// Optional custom session ID
2081    #[serde(skip_serializing_if = "Option::is_none")]
2082    pub id: Option<String>,
2083}
2084
2085/// Response from starting a session
2086#[derive(Debug, Serialize)]
2087pub struct SessionStartResponse {
2088    pub session: Session,
2089}
2090
2091/// Request to end a session
2092#[derive(Debug, Deserialize)]
2093pub struct SessionEndRequest {
2094    #[serde(default)]
2095    pub summary: Option<String>,
2096    /// Auto-generate summary from session memories
2097    #[serde(default)]
2098    pub auto_summarize: bool,
2099}
2100
2101/// Response from ending a session
2102#[derive(Debug, Serialize)]
2103pub struct SessionEndResponse {
2104    pub session: Session,
2105    pub memory_count: usize,
2106}
2107
2108/// Response listing sessions
2109#[derive(Debug, Serialize)]
2110pub struct ListSessionsResponse {
2111    pub sessions: Vec<Session>,
2112    pub total: usize,
2113}
2114
2115/// Response for session memories
2116#[derive(Debug, Serialize)]
2117pub struct SessionMemoriesResponse {
2118    pub session: Session,
2119    pub memories: Vec<Memory>,
2120    /// Total number of memories in this session (before pagination)
2121    #[serde(skip_serializing_if = "Option::is_none")]
2122    pub total: Option<usize>,
2123}
2124
2125// ============================================================================
2126// Dakera Agent & Knowledge Types
2127// ============================================================================
2128
2129/// Lightweight agent summary for batch listing (uses count() not get_all)
2130#[derive(Debug, Serialize, Deserialize, Clone)]
2131pub struct AgentSummary {
2132    pub agent_id: String,
2133    pub memory_count: usize,
2134    pub session_count: usize,
2135    pub active_sessions: usize,
2136}
2137
2138/// Agent memory statistics
2139#[derive(Debug, Serialize)]
2140pub struct AgentStats {
2141    pub agent_id: String,
2142    pub total_memories: usize,
2143    pub memories_by_type: std::collections::HashMap<String, usize>,
2144    pub total_sessions: usize,
2145    pub active_sessions: usize,
2146    pub avg_importance: f32,
2147    pub oldest_memory_at: Option<u64>,
2148    pub newest_memory_at: Option<u64>,
2149}
2150
2151/// Request for knowledge graph traversal
2152#[derive(Debug, Deserialize)]
2153pub struct KnowledgeGraphRequest {
2154    pub agent_id: String,
2155    pub memory_id: String,
2156    #[serde(default = "default_graph_depth")]
2157    pub depth: usize,
2158    #[serde(default = "default_graph_min_similarity")]
2159    pub min_similarity: f32,
2160}
2161
2162fn default_graph_depth() -> usize {
2163    2
2164}
2165
2166fn default_graph_min_similarity() -> f32 {
2167    0.7
2168}
2169
2170/// Knowledge graph node
2171#[derive(Debug, Serialize)]
2172pub struct KnowledgeGraphNode {
2173    pub memory: Memory,
2174    pub similarity: f32,
2175    pub related: Vec<KnowledgeGraphEdge>,
2176}
2177
2178/// Knowledge graph edge
2179#[derive(Debug, Serialize)]
2180pub struct KnowledgeGraphEdge {
2181    pub memory_id: String,
2182    pub similarity: f32,
2183    pub shared_tags: Vec<String>,
2184}
2185
2186/// Response from knowledge graph query
2187#[derive(Debug, Serialize)]
2188pub struct KnowledgeGraphResponse {
2189    pub root: KnowledgeGraphNode,
2190    pub total_nodes: usize,
2191}
2192
2193// ============================================================================
2194// Full Knowledge Graph Types (Global Network Topology)
2195// ============================================================================
2196
2197fn default_full_graph_max_nodes() -> usize {
2198    200
2199}
2200
2201fn default_full_graph_min_similarity() -> f32 {
2202    0.50
2203}
2204
2205fn default_full_graph_cluster_threshold() -> f32 {
2206    0.60
2207}
2208
2209fn default_full_graph_max_edges_per_node() -> usize {
2210    8
2211}
2212
2213/// Request for full knowledge graph (all memories, pairwise similarity)
2214#[derive(Debug, Deserialize)]
2215pub struct FullKnowledgeGraphRequest {
2216    pub agent_id: String,
2217    #[serde(default = "default_full_graph_max_nodes")]
2218    pub max_nodes: usize,
2219    #[serde(default = "default_full_graph_min_similarity")]
2220    pub min_similarity: f32,
2221    #[serde(default = "default_full_graph_cluster_threshold")]
2222    pub cluster_threshold: f32,
2223    #[serde(default = "default_full_graph_max_edges_per_node")]
2224    pub max_edges_per_node: usize,
2225}
2226
2227/// A node in the full knowledge graph
2228#[derive(Debug, Serialize)]
2229pub struct FullGraphNode {
2230    pub id: String,
2231    pub content: String,
2232    pub memory_type: String,
2233    pub importance: f32,
2234    pub tags: Vec<String>,
2235    pub created_at: Option<String>,
2236    pub cluster_id: usize,
2237    pub centrality: f32,
2238}
2239
2240/// An edge in the full knowledge graph
2241#[derive(Debug, Serialize)]
2242pub struct FullGraphEdge {
2243    pub source: String,
2244    pub target: String,
2245    pub similarity: f32,
2246    pub shared_tags: Vec<String>,
2247}
2248
2249/// A cluster of related memories
2250#[derive(Debug, Serialize)]
2251pub struct GraphCluster {
2252    pub id: usize,
2253    pub node_count: usize,
2254    pub top_tags: Vec<String>,
2255    pub avg_importance: f32,
2256}
2257
2258/// Statistics about the full knowledge graph
2259#[derive(Debug, Serialize)]
2260pub struct GraphStats {
2261    pub total_memories: usize,
2262    pub included_memories: usize,
2263    pub total_edges: usize,
2264    pub cluster_count: usize,
2265    pub density: f32,
2266    pub hub_memory_id: Option<String>,
2267}
2268
2269/// Response from full knowledge graph query
2270#[derive(Debug, Serialize)]
2271pub struct FullKnowledgeGraphResponse {
2272    pub nodes: Vec<FullGraphNode>,
2273    pub edges: Vec<FullGraphEdge>,
2274    pub clusters: Vec<GraphCluster>,
2275    pub stats: GraphStats,
2276}
2277
2278/// Request to summarize memories
2279#[derive(Debug, Deserialize)]
2280pub struct SummarizeRequest {
2281    pub agent_id: String,
2282    pub memory_ids: Vec<String>,
2283    #[serde(default)]
2284    pub target_type: Option<MemoryType>,
2285}
2286
2287/// Response from summarization
2288#[derive(Debug, Serialize)]
2289pub struct SummarizeResponse {
2290    pub summary_memory: Memory,
2291    pub source_count: usize,
2292}
2293
2294/// Request to deduplicate memories
2295#[derive(Debug, Deserialize)]
2296pub struct DeduplicateRequest {
2297    pub agent_id: String,
2298    #[serde(default = "default_dedup_threshold")]
2299    pub threshold: f32,
2300    #[serde(default)]
2301    pub memory_type: Option<MemoryType>,
2302    /// Dry run — report duplicates without merging
2303    #[serde(default)]
2304    pub dry_run: bool,
2305}
2306
2307fn default_dedup_threshold() -> f32 {
2308    0.92
2309}
2310
2311/// A group of duplicate memories
2312#[derive(Debug, Serialize)]
2313pub struct DuplicateGroup {
2314    pub canonical_id: String,
2315    pub duplicate_ids: Vec<String>,
2316    pub avg_similarity: f32,
2317}
2318
2319/// Response from deduplication
2320#[derive(Debug, Serialize)]
2321pub struct DeduplicateResponse {
2322    pub groups: Vec<DuplicateGroup>,
2323    pub duplicates_found: usize,
2324    pub duplicates_merged: usize,
2325}