contextlite-client 2.0.7

Ultra-fast Rust client for ContextLite - the high-performance context engine for retrieval and AI applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
//! Core types for the ContextLite Rust client.
//!
//! This module defines all data structures used for communication with the ContextLite server,
//! following the same patterns as the Go, Python, and Node.js clients for consistency.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// A document in the ContextLite system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Document {
    /// Unique document identifier (auto-generated if not provided)
    pub id: Option<String>,
    
    /// File path or logical path of the document
    pub path: String,
    
    /// Content of the document
    pub content: String,
    
    /// Optional metadata as key-value pairs
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, String>>,
    
    /// Creation timestamp
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<DateTime<Utc>>,
    
    /// Last update timestamp
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<DateTime<Utc>>,
}

impl Document {
    /// Create a new document with required fields
    pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
        Self {
            id: None,
            path: path.into(),
            content: content.into(),
            metadata: None,
            created_at: None,
            updated_at: None,
        }
    }
    
    /// Set the document ID
    pub fn with_id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(id.into());
        self
    }
    
    /// Set metadata for the document
    pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
        self.metadata = Some(metadata);
        self
    }
}

/// Search query parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchQuery {
    /// The search query string
    pub q: String,
    
    /// Maximum number of results to return
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<usize>,
    
    /// Number of results to skip (for pagination)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub offset: Option<usize>,
    
    /// Additional filters as arbitrary JSON
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filters: Option<serde_json::Value>,
}

impl SearchQuery {
    /// Create a new search query
    pub fn new(query: impl Into<String>) -> Self {
        Self {
            q: query.into(),
            limit: None,
            offset: None,
            filters: None,
        }
    }
    
    /// Set the result limit
    pub fn with_limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }
    
    /// Set the result offset for pagination
    pub fn with_offset(mut self, offset: usize) -> Self {
        self.offset = Some(offset);
        self
    }
    
    /// Add filters to the query
    pub fn with_filters(mut self, filters: serde_json::Value) -> Self {
        self.filters = Some(filters);
        self
    }
}

/// Reference to a document in search results or context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentReference {
    /// Document ID
    pub id: String,
    
    /// Document path
    pub path: String,
    
    /// Document content (may be truncated)
    pub content: String,
    
    /// Relevance score for this document
    #[serde(skip_serializing_if = "Option::is_none")]
    pub score: Option<f64>,
    
    /// Additional metadata
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, String>>,
}

/// Response from document search
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResponse {
    /// Search query that was executed
    pub query: String,
    
    /// Documents matching the search
    pub documents: Vec<DocumentReference>,
    
    /// Total number of results found
    pub total: usize,
    
    /// Search execution time in milliseconds
    #[serde(skip_serializing_if = "Option::is_none")]
    pub took_ms: Option<u64>,
}

/// Request for context assembly
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextRequest {
    /// Query for context assembly
    pub q: String,
    
    /// Token budget for the assembled context
    #[serde(skip_serializing_if = "Option::is_none")]
    pub budget: Option<usize>,
    
    /// Maximum number of documents to include
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_results: Option<usize>,
    
    /// Whether to include document metadata
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include_metadata: Option<bool>,
    
    /// SMT optimization parameters
    #[serde(skip_serializing_if = "Option::is_none")]
    pub smt_options: Option<serde_json::Value>,
}

impl ContextRequest {
    /// Create a new context request
    pub fn new(query: impl Into<String>) -> Self {
        Self {
            q: query.into(),
            budget: None,
            max_results: None,
            include_metadata: None,
            smt_options: None,
        }
    }
    
    /// Set the token budget
    pub fn with_budget(mut self, budget: usize) -> Self {
        self.budget = Some(budget);
        self
    }
    
    /// Set the maximum number of results
    pub fn with_max_results(mut self, max_results: usize) -> Self {
        self.max_results = Some(max_results);
        self
    }
    
    /// Include document metadata in the response
    pub fn with_metadata(mut self, include: bool) -> Self {
        self.include_metadata = Some(include);
        self
    }
}

/// Assembled context response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextResponse {
    /// The search query used
    pub query: String,
    
    /// Documents that were included in the context
    pub documents: Option<Vec<DocumentReference>>,
    
    /// Total number of documents included
    pub total_documents: usize,
    
    /// Total token count
    pub total_tokens: usize,
    
    /// Coherence score of the assembled context
    pub coherence_score: f64,
    
    /// SMT solver metrics
    #[serde(skip_serializing_if = "Option::is_none")]
    pub smt_metrics: Option<serde_json::Value>,
    
    /// Timing information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timings: Option<serde_json::Value>,
    
    /// Whether this was a cache hit
    pub cache_hit: bool,
    
    /// Cache key fingerprint
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cache_key_fingerprint: Option<String>,
}

/// System health status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteHealthStatus {
    /// Overall system status
    pub status: String,
    
    /// System version
    pub version: String,
    
    /// Timestamp of the status check
    pub timestamp: i64,
    
    /// Database information
    pub database: DatabaseStats,
    
    /// SMT solver information
    pub smt: SmtInfo,
    
    /// Available features
    pub features: Features,
}

/// Database statistics and status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseStats {
    /// Number of documents currently indexed
    pub documents_indexed: String,
    
    /// Number of active cache entries
    pub cache_entries: String,
    
    /// Whether FTS (Full Text Search) is enabled
    pub fts_enabled: bool,
    
    /// Last optimization timestamp
    pub last_optimized: i64,
}

/// SMT solver information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SmtInfo {
    /// Whether SMT optimization is enabled
    pub enabled: bool,
    
    /// SMT solver name
    pub solver: String,
    
    /// SMT solver version
    pub version: String,
    
    /// SMT policy description
    pub policy: String,
}

/// Available system features
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Features {
    /// Whether caching is enabled
    pub cache_enabled: bool,
    
    /// Whether FTS search is available
    pub fts_search: bool,
    
    /// Whether quantum scoring is enabled
    pub quantum_scoring: bool,
    
    /// Whether SMT optimization is available
    pub smt_optimization: bool,
}

/// Storage information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageInfo {
    /// Database file path
    pub database_path: String,
    
    /// Database size in bytes
    pub size_bytes: u64,
    
    /// Number of documents
    pub document_count: usize,
    
    /// Last vacuum timestamp
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_vacuum: Option<DateTime<Utc>>,
}

/// Client configuration
#[derive(Debug, Clone)]
pub struct ClientConfig {
    /// Base URL of the ContextLite server
    pub base_url: String,
    
    /// Authentication token
    pub auth_token: Option<String>,
    
    /// Request timeout in seconds
    pub timeout_seconds: u64,
    
    /// Maximum number of retries for failed requests
    pub max_retries: usize,
    
    /// User agent string
    pub user_agent: String,
}

impl ClientConfig {
    /// Create a new client configuration
    pub fn new(base_url: impl Into<String>) -> Self {
        Self {
            base_url: base_url.into(),
            auth_token: None,
            timeout_seconds: 30,
            max_retries: 3,
            user_agent: format!("contextlite-rust-client/{}", env!("CARGO_PKG_VERSION")),
        }
    }
    
    /// Set the authentication token
    pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
        self.auth_token = Some(token.into());
        self
    }
    
    /// Set the request timeout
    pub fn with_timeout(mut self, seconds: u64) -> Self {
        self.timeout_seconds = seconds;
        self
    }
    
    /// Set the maximum number of retries
    pub fn with_max_retries(mut self, retries: usize) -> Self {
        self.max_retries = retries;
        self
    }
    
    /// Set a custom user agent
    pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
        self.user_agent = user_agent.into();
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_document_creation() {
        let mut metadata = HashMap::new();
        metadata.insert("lang".to_string(), "rust".to_string());
        
        let doc = Document::new("test.rs", "fn main() {}")
            .with_id("test-id")
            .with_metadata(metadata);
        
        assert_eq!(doc.path, "test.rs");
        assert_eq!(doc.content, "fn main() {}");
        assert_eq!(doc.id, Some("test-id".to_string()));
        assert!(doc.metadata.is_some());
    }
    
    #[test]
    fn test_search_query_builder() {
        let query = SearchQuery::new("rust async")
            .with_limit(10)
            .with_offset(5);
        
        assert_eq!(query.q, "rust async");
        assert_eq!(query.limit, Some(10));
        assert_eq!(query.offset, Some(5));
    }
    
    #[test]
    fn test_context_request_builder() {
        let request = ContextRequest::new("example query")
            .with_budget(1000)
            .with_max_results(5)
            .with_metadata(true);
        
        assert_eq!(request.q, "example query");
        assert_eq!(request.budget, Some(1000));
        assert_eq!(request.max_results, Some(5));
        assert_eq!(request.include_metadata, Some(true));
    }
    
    #[test]
    fn test_client_config_builder() {
        let config = ClientConfig::new("http://localhost:8080")
            .with_auth_token("test-token")
            .with_timeout(60)
            .with_max_retries(5);
        
        assert_eq!(config.base_url, "http://localhost:8080");
        assert_eq!(config.auth_token, Some("test-token".to_string()));
        assert_eq!(config.timeout_seconds, 60);
        assert_eq!(config.max_retries, 5);
    }
}