vectorizer_sdk/
client.rs

1//! Vectorizer client with transport abstraction
2
3use crate::error::{VectorizerError, Result};
4use crate::models::*;
5use crate::models::hybrid_search::{HybridSearchRequest, HybridSearchResponse, HybridScoringAlgorithm};
6use crate::transport::{Transport, Protocol};
7use crate::http_transport::HttpTransport;
8
9#[cfg(feature = "umicp")]
10use crate::umicp_transport::UmicpTransport;
11
12use serde_json;
13use std::sync::Arc;
14
15/// Configuration for VectorizerClient
16pub struct ClientConfig {
17    /// Base URL for HTTP transport
18    pub base_url: Option<String>,
19    /// Connection string (supports http://, https://, umicp://)
20    pub connection_string: Option<String>,
21    /// Protocol to use
22    pub protocol: Option<Protocol>,
23    /// API key for authentication
24    pub api_key: Option<String>,
25    /// Request timeout in seconds
26    pub timeout_secs: Option<u64>,
27    /// UMICP configuration
28    #[cfg(feature = "umicp")]
29    pub umicp: Option<UmicpConfig>,
30}
31
32#[cfg(feature = "umicp")]
33/// UMICP-specific configuration
34pub struct UmicpConfig {
35    pub host: String,
36    pub port: u16,
37}
38
39impl Default for ClientConfig {
40    fn default() -> Self {
41        Self {
42            base_url: Some("http://localhost:15002".to_string()),
43            connection_string: None,
44            protocol: None,
45            api_key: None,
46            timeout_secs: Some(30),
47            #[cfg(feature = "umicp")]
48            umicp: None,
49        }
50    }
51}
52
53/// Vectorizer client
54pub struct VectorizerClient {
55    transport: Arc<dyn Transport>,
56    protocol: Protocol,
57    base_url: String,
58}
59
60impl VectorizerClient {
61    /// Get the base URL (for HTTP transport)
62    pub fn base_url(&self) -> &str {
63        &self.base_url
64    }
65
66    /// Create a new client with configuration
67    pub fn new(config: ClientConfig) -> Result<Self> {
68        let timeout_secs = config.timeout_secs.unwrap_or(30);
69
70        // Determine protocol and create transport
71        let (transport, protocol, base_url): (Arc<dyn Transport>, Protocol, String) = if let Some(conn_str) = config.connection_string {
72            // Use connection string
73            let (proto, host, port) = crate::transport::parse_connection_string(&conn_str)?;
74            
75            match proto {
76                Protocol::Http => {
77                    let transport = HttpTransport::new(&host, config.api_key.as_deref(), timeout_secs)?;
78                    (Arc::new(transport), Protocol::Http, host.clone())
79                },
80                #[cfg(feature = "umicp")]
81                Protocol::Umicp => {
82                    let port = port.unwrap_or(15003);
83                    let transport = UmicpTransport::new(&host, port, config.api_key.as_deref(), timeout_secs)?;
84                    let base_url = format!("umicp://{}:{}", host, port);
85                    (Arc::new(transport), Protocol::Umicp, base_url)
86                },
87            }
88        } else {
89            // Use explicit configuration
90            let proto = config.protocol.unwrap_or(Protocol::Http);
91            
92            match proto {
93                Protocol::Http => {
94                    let base_url = config.base_url.unwrap_or_else(|| "http://localhost:15002".to_string());
95                    let transport = HttpTransport::new(&base_url, config.api_key.as_deref(), timeout_secs)?;
96                    (Arc::new(transport), Protocol::Http, base_url.clone())
97                },
98                #[cfg(feature = "umicp")]
99                Protocol::Umicp => {
100                    #[cfg(feature = "umicp")]
101                    {
102                        let umicp_config = config.umicp.ok_or_else(|| {
103                            VectorizerError::configuration("UMICP configuration is required when using UMICP protocol")
104                        })?;
105                        
106                        let transport = UmicpTransport::new(
107                            &umicp_config.host,
108                            umicp_config.port,
109                            config.api_key.as_deref(),
110                            timeout_secs,
111                        )?;
112                        let base_url = format!("umicp://{}:{}", umicp_config.host, umicp_config.port);
113                        (Arc::new(transport), Protocol::Umicp, base_url)
114                    }
115                    #[cfg(not(feature = "umicp"))]
116                    {
117                        return Err(VectorizerError::configuration(
118                            "UMICP feature is not enabled. Enable it with --features umicp"
119                        ));
120                    }
121                },
122            }
123        };
124
125        Ok(Self { transport, protocol, base_url })
126    }
127
128    /// Create a new client with default configuration
129    pub fn new_default() -> Result<Self> {
130        Self::new(ClientConfig::default())
131    }
132
133    /// Create client with custom URL
134    pub fn new_with_url(base_url: &str) -> Result<Self> {
135        Self::new(ClientConfig {
136            base_url: Some(base_url.to_string()),
137            ..Default::default()
138        })
139    }
140
141    /// Create client with API key
142    pub fn new_with_api_key(base_url: &str, api_key: &str) -> Result<Self> {
143        Self::new(ClientConfig {
144            base_url: Some(base_url.to_string()),
145            api_key: Some(api_key.to_string()),
146            ..Default::default()
147        })
148    }
149
150    /// Create client from connection string
151    pub fn from_connection_string(connection_string: &str, api_key: Option<&str>) -> Result<Self> {
152        Self::new(ClientConfig {
153            connection_string: Some(connection_string.to_string()),
154            api_key: api_key.map(|s| s.to_string()),
155            ..Default::default()
156        })
157    }
158
159    /// Get the current protocol being used
160    pub fn protocol(&self) -> Protocol {
161        self.protocol
162    }
163
164    /// Health check
165    pub async fn health_check(&self) -> Result<HealthStatus> {
166        let response = self.make_request("GET", "/health", None).await?;
167        let health: HealthStatus = serde_json::from_str(&response)
168            .map_err(|e| VectorizerError::server(format!("Failed to parse health check response: {}", e)))?;
169        Ok(health)
170    }
171
172    /// List collections
173    pub async fn list_collections(&self) -> Result<Vec<CollectionInfo>> {
174        let response = self.make_request("GET", "/collections", None).await?;
175        let collections_response: CollectionsResponse = serde_json::from_str(&response)
176            .map_err(|e| VectorizerError::server(format!("Failed to parse collections response: {}", e)))?;
177        Ok(collections_response.collections)
178    }
179
180    /// Search vectors
181    pub async fn search_vectors(
182        &self,
183        collection: &str,
184        query: &str,
185        limit: Option<usize>,
186        score_threshold: Option<f32>,
187    ) -> Result<SearchResponse> {
188        let mut payload = serde_json::Map::new();
189        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
190        payload.insert("limit".to_string(), serde_json::Value::Number(limit.unwrap_or(10).into()));
191
192        if let Some(threshold) = score_threshold {
193            payload.insert("score_threshold".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(threshold as f64).unwrap()));
194        }
195
196        let response = self.make_request("POST", &format!("/collections/{}/search/text", collection), Some(serde_json::Value::Object(payload))).await?;
197        let search_response: SearchResponse = serde_json::from_str(&response)
198            .map_err(|e| VectorizerError::server(format!("Failed to parse search response: {}", e)))?;
199        Ok(search_response)
200    }
201
202    // ===== INTELLIGENT SEARCH OPERATIONS =====
203
204    /// Intelligent search with multi-query expansion and semantic reranking
205    pub async fn intelligent_search(&self, request: IntelligentSearchRequest) -> Result<IntelligentSearchResponse> {
206        let response = self.make_request("POST", "/intelligent_search", Some(serde_json::to_value(request).unwrap())).await?;
207        let search_response: IntelligentSearchResponse = serde_json::from_str(&response)
208            .map_err(|e| VectorizerError::server(format!("Failed to parse intelligent search response: {}", e)))?;
209        Ok(search_response)
210    }
211
212    /// Semantic search with advanced reranking and similarity thresholds
213    pub async fn semantic_search(&self, request: SemanticSearchRequest) -> Result<SemanticSearchResponse> {
214        let response = self.make_request("POST", "/semantic_search", Some(serde_json::to_value(request).unwrap())).await?;
215        let search_response: SemanticSearchResponse = serde_json::from_str(&response)
216            .map_err(|e| VectorizerError::server(format!("Failed to parse semantic search response: {}", e)))?;
217        Ok(search_response)
218    }
219
220    /// Context-aware search with metadata filtering and contextual reranking
221    pub async fn contextual_search(&self, request: ContextualSearchRequest) -> Result<ContextualSearchResponse> {
222        let response = self.make_request("POST", "/contextual_search", Some(serde_json::to_value(request).unwrap())).await?;
223        let search_response: ContextualSearchResponse = serde_json::from_str(&response)
224            .map_err(|e| VectorizerError::server(format!("Failed to parse contextual search response: {}", e)))?;
225        Ok(search_response)
226    }
227
228    /// Multi-collection search with cross-collection reranking and aggregation
229    pub async fn multi_collection_search(&self, request: MultiCollectionSearchRequest) -> Result<MultiCollectionSearchResponse> {
230        let response = self.make_request("POST", "/multi_collection_search", Some(serde_json::to_value(request).unwrap())).await?;
231        let search_response: MultiCollectionSearchResponse = serde_json::from_str(&response)
232            .map_err(|e| VectorizerError::server(format!("Failed to parse multi-collection search response: {}", e)))?;
233        Ok(search_response)
234    }
235
236    /// Perform hybrid search combining dense and sparse vectors
237    pub async fn hybrid_search(&self, request: HybridSearchRequest) -> Result<HybridSearchResponse> {
238        let url = format!("/collections/{}/hybrid_search", request.collection);
239        let payload = serde_json::json!({
240            "query": request.query,
241            "alpha": request.alpha,
242            "algorithm": match request.algorithm {
243                HybridScoringAlgorithm::ReciprocalRankFusion => "rrf",
244                HybridScoringAlgorithm::WeightedCombination => "weighted",
245                HybridScoringAlgorithm::AlphaBlending => "alpha",
246            },
247            "dense_k": request.dense_k,
248            "sparse_k": request.sparse_k,
249            "final_k": request.final_k,
250            "query_sparse": request.query_sparse.as_ref().map(|sv| serde_json::json!({
251                "indices": sv.indices,
252                "values": sv.values,
253            })),
254        });
255        let response = self.make_request("POST", &url, Some(payload)).await?;
256        let search_response: HybridSearchResponse = serde_json::from_str(&response)
257            .map_err(|e| VectorizerError::server(format!("Failed to parse hybrid search response: {}", e)))?;
258        Ok(search_response)
259    }
260
261    // ===== QDRANT COMPATIBILITY METHODS =====
262
263    /// List all collections (Qdrant-compatible API)
264    pub async fn qdrant_list_collections(&self) -> Result<serde_json::Value> {
265        let response = self.make_request("GET", "/qdrant/collections", None).await?;
266        let result: serde_json::Value = serde_json::from_str(&response)
267            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant collections response: {}", e)))?;
268        Ok(result)
269    }
270
271    /// Get collection information (Qdrant-compatible API)
272    pub async fn qdrant_get_collection(&self, name: &str) -> Result<serde_json::Value> {
273        let url = format!("/qdrant/collections/{}", name);
274        let response = self.make_request("GET", &url, None).await?;
275        let result: serde_json::Value = serde_json::from_str(&response)
276            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant collection response: {}", e)))?;
277        Ok(result)
278    }
279
280    /// Create collection (Qdrant-compatible API)
281    pub async fn qdrant_create_collection(&self, name: &str, config: &serde_json::Value) -> Result<serde_json::Value> {
282        let url = format!("/qdrant/collections/{}", name);
283        let payload = serde_json::json!({ "config": config });
284        let response = self.make_request("PUT", &url, Some(payload)).await?;
285        let result: serde_json::Value = serde_json::from_str(&response)
286            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant create collection response: {}", e)))?;
287        Ok(result)
288    }
289
290    /// Upsert points to collection (Qdrant-compatible API)
291    pub async fn qdrant_upsert_points(&self, collection: &str, points: &serde_json::Value, wait: bool) -> Result<serde_json::Value> {
292        let url = format!("/qdrant/collections/{}/points", collection);
293        let payload = serde_json::json!({
294            "points": points,
295            "wait": wait,
296        });
297        let response = self.make_request("PUT", &url, Some(payload)).await?;
298        let result: serde_json::Value = serde_json::from_str(&response)
299            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant upsert points response: {}", e)))?;
300        Ok(result)
301    }
302
303    /// Search points in collection (Qdrant-compatible API)
304    pub async fn qdrant_search_points(
305        &self,
306        collection: &str,
307        vector: &[f32],
308        limit: Option<usize>,
309        filter: Option<&serde_json::Value>,
310        with_payload: bool,
311        with_vector: bool,
312    ) -> Result<serde_json::Value> {
313        let url = format!("/qdrant/collections/{}/points/search", collection);
314        let mut payload = serde_json::json!({
315            "vector": vector,
316            "limit": limit.unwrap_or(10),
317            "with_payload": with_payload,
318            "with_vector": with_vector,
319        });
320        if let Some(filter) = filter {
321            payload["filter"] = filter.clone();
322        }
323        let response = self.make_request("POST", &url, Some(payload)).await?;
324        let result: serde_json::Value = serde_json::from_str(&response)
325            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant search response: {}", e)))?;
326        Ok(result)
327    }
328
329    /// Delete points from collection (Qdrant-compatible API)
330    pub async fn qdrant_delete_points(&self, collection: &str, point_ids: &[serde_json::Value], wait: bool) -> Result<serde_json::Value> {
331        let url = format!("/qdrant/collections/{}/points/delete", collection);
332        let payload = serde_json::json!({
333            "points": point_ids,
334            "wait": wait,
335        });
336        let response = self.make_request("POST", &url, Some(payload)).await?;
337        let result: serde_json::Value = serde_json::from_str(&response)
338            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant delete points response: {}", e)))?;
339        Ok(result)
340    }
341
342    /// Retrieve points by IDs (Qdrant-compatible API)
343    pub async fn qdrant_retrieve_points(
344        &self,
345        collection: &str,
346        point_ids: &[serde_json::Value],
347        with_payload: bool,
348        with_vector: bool,
349    ) -> Result<serde_json::Value> {
350        let ids_str = point_ids.iter()
351            .map(|id| match id {
352                serde_json::Value::String(s) => s.clone(),
353                serde_json::Value::Number(n) => n.to_string(),
354                _ => serde_json::to_string(id).unwrap_or_default(),
355            })
356            .collect::<Vec<_>>()
357            .join(",");
358        let url = format!(
359            "/qdrant/collections/{}/points?ids={}&with_payload={}&with_vector={}",
360            collection, ids_str, with_payload, with_vector
361        );
362        let response = self.make_request("GET", &url, None).await?;
363        let result: serde_json::Value = serde_json::from_str(&response)
364            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant retrieve points response: {}", e)))?;
365        Ok(result)
366    }
367
368    /// Count points in collection (Qdrant-compatible API)
369    pub async fn qdrant_count_points(&self, collection: &str, filter: Option<&serde_json::Value>) -> Result<serde_json::Value> {
370        let url = format!("/qdrant/collections/{}/points/count", collection);
371        let payload = if let Some(filter) = filter {
372            serde_json::json!({ "filter": filter })
373        } else {
374            serde_json::json!({})
375        };
376        let response = self.make_request("POST", &url, Some(payload)).await?;
377        let result: serde_json::Value = serde_json::from_str(&response)
378            .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant count points response: {}", e)))?;
379        Ok(result)
380    }
381
382    /// Create collection
383    pub async fn create_collection(
384        &self,
385        name: &str,
386        dimension: usize,
387        metric: Option<SimilarityMetric>,
388    ) -> Result<CollectionInfo> {
389        let mut payload = serde_json::Map::new();
390        payload.insert("name".to_string(), serde_json::Value::String(name.to_string()));
391        payload.insert("dimension".to_string(), serde_json::Value::Number(dimension.into()));
392        payload.insert("metric".to_string(), serde_json::Value::String(format!("{:?}", metric.unwrap_or_default()).to_lowercase()));
393
394        let response = self.make_request("POST", "/collections", Some(serde_json::Value::Object(payload))).await?;
395        let create_response: CreateCollectionResponse = serde_json::from_str(&response)
396            .map_err(|e| VectorizerError::server(format!("Failed to parse create collection response: {}", e)))?;
397
398        // Create a basic CollectionInfo from the response
399        let info = CollectionInfo {
400            name: create_response.collection,
401            dimension: dimension,
402            metric: format!("{:?}", metric.unwrap_or_default()).to_lowercase(),
403            vector_count: 0,
404            document_count: 0,
405            created_at: "".to_string(),
406            updated_at: "".to_string(),
407            indexing_status: crate::models::IndexingStatus {
408                status: "created".to_string(),
409                progress: 0.0,
410                total_documents: 0,
411                processed_documents: 0,
412                vector_count: 0,
413                estimated_time_remaining: None,
414                last_updated: "".to_string(),
415            },
416        };
417        Ok(info)
418    }
419
420    /// Insert texts
421    pub async fn insert_texts(
422        &self,
423        collection: &str,
424        texts: Vec<BatchTextRequest>,
425    ) -> Result<BatchResponse> {
426        let payload = serde_json::json!({
427            "texts": texts
428        });
429
430        let response = self.make_request("POST", &format!("/collections/{}/documents", collection), Some(serde_json::to_value(payload)?)).await?;
431        let batch_response: BatchResponse = serde_json::from_str(&response)
432            .map_err(|e| VectorizerError::server(format!("Failed to parse insert texts response: {}", e)))?;
433        Ok(batch_response)
434    }
435
436    /// Delete collection
437    pub async fn delete_collection(&self, name: &str) -> Result<()> {
438        self.make_request("DELETE", &format!("/collections/{}", name), None).await?;
439        Ok(())
440    }
441
442    /// Get vector
443    pub async fn get_vector(&self, collection: &str, vector_id: &str) -> Result<Vector> {
444        let response = self.make_request("GET", &format!("/collections/{}/vectors/{}", collection, vector_id), None).await?;
445        let vector: Vector = serde_json::from_str(&response)
446            .map_err(|e| VectorizerError::server(format!("Failed to parse get vector response: {}", e)))?;
447        Ok(vector)
448    }
449
450    /// Get collection info
451    pub async fn get_collection_info(&self, collection: &str) -> Result<CollectionInfo> {
452        let response = self.make_request("GET", &format!("/collections/{}", collection), None).await?;
453        let info: CollectionInfo = serde_json::from_str(&response)
454            .map_err(|e| VectorizerError::server(format!("Failed to parse collection info: {}", e)))?;
455        Ok(info)
456    }
457
458    /// Generate embeddings
459    pub async fn embed_text(&self, text: &str, model: Option<&str>) -> Result<EmbeddingResponse> {
460        let mut payload = serde_json::Map::new();
461        payload.insert("text".to_string(), serde_json::Value::String(text.to_string()));
462
463        if let Some(model) = model {
464            payload.insert("model".to_string(), serde_json::Value::String(model.to_string()));
465        }
466
467        let response = self.make_request("POST", "/embed", Some(serde_json::Value::Object(payload))).await?;
468        let embedding_response: EmbeddingResponse = serde_json::from_str(&response)
469            .map_err(|e| VectorizerError::server(format!("Failed to parse embedding response: {}", e)))?;
470        Ok(embedding_response)
471    }
472
473    // =============================================================================
474    // DISCOVERY OPERATIONS
475    // =============================================================================
476
477    /// Complete discovery pipeline with intelligent search and prompt generation
478    pub async fn discover(
479        &self,
480        query: &str,
481        include_collections: Option<Vec<String>>,
482        exclude_collections: Option<Vec<String>>,
483        max_bullets: Option<usize>,
484        broad_k: Option<usize>,
485        focus_k: Option<usize>,
486    ) -> Result<serde_json::Value> {
487        // Validate query
488        if query.trim().is_empty() {
489            return Err(VectorizerError::validation("Query cannot be empty"));
490        }
491        
492        // Validate max_bullets
493        if let Some(max) = max_bullets {
494            if max == 0 {
495                return Err(VectorizerError::validation("max_bullets must be greater than 0"));
496            }
497        }
498        
499        let mut payload = serde_json::Map::new();
500        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
501        
502        if let Some(inc) = include_collections {
503            payload.insert("include_collections".to_string(), serde_json::to_value(inc).unwrap());
504        }
505        if let Some(exc) = exclude_collections {
506            payload.insert("exclude_collections".to_string(), serde_json::to_value(exc).unwrap());
507        }
508        if let Some(max) = max_bullets {
509            payload.insert("max_bullets".to_string(), serde_json::Value::Number(max.into()));
510        }
511        if let Some(k) = broad_k {
512            payload.insert("broad_k".to_string(), serde_json::Value::Number(k.into()));
513        }
514        if let Some(k) = focus_k {
515            payload.insert("focus_k".to_string(), serde_json::Value::Number(k.into()));
516        }
517
518        let response = self.make_request("POST", "/discover", Some(serde_json::Value::Object(payload))).await?;
519        let result: serde_json::Value = serde_json::from_str(&response)
520            .map_err(|e| VectorizerError::server(format!("Failed to parse discover response: {}", e)))?;
521        Ok(result)
522    }
523
524    /// Pre-filter collections by name patterns
525    pub async fn filter_collections(
526        &self,
527        query: &str,
528        include: Option<Vec<String>>,
529        exclude: Option<Vec<String>>,
530    ) -> Result<serde_json::Value> {
531        // Validate query
532        if query.trim().is_empty() {
533            return Err(VectorizerError::validation("Query cannot be empty"));
534        }
535        
536        let mut payload = serde_json::Map::new();
537        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
538        
539        if let Some(inc) = include {
540            payload.insert("include".to_string(), serde_json::to_value(inc).unwrap());
541        }
542        if let Some(exc) = exclude {
543            payload.insert("exclude".to_string(), serde_json::to_value(exc).unwrap());
544        }
545
546        let response = self.make_request("POST", "/discovery/filter_collections", Some(serde_json::Value::Object(payload))).await?;
547        let result: serde_json::Value = serde_json::from_str(&response)
548            .map_err(|e| VectorizerError::server(format!("Failed to parse filter response: {}", e)))?;
549        Ok(result)
550    }
551
552    /// Rank collections by relevance
553    pub async fn score_collections(
554        &self,
555        query: &str,
556        name_match_weight: Option<f32>,
557        term_boost_weight: Option<f32>,
558        signal_boost_weight: Option<f32>,
559    ) -> Result<serde_json::Value> {
560        // Validate weights (must be between 0.0 and 1.0)
561        if let Some(w) = name_match_weight {
562            if w < 0.0 || w > 1.0 {
563                return Err(VectorizerError::validation("name_match_weight must be between 0.0 and 1.0"));
564            }
565        }
566        if let Some(w) = term_boost_weight {
567            if w < 0.0 || w > 1.0 {
568                return Err(VectorizerError::validation("term_boost_weight must be between 0.0 and 1.0"));
569            }
570        }
571        if let Some(w) = signal_boost_weight {
572            if w < 0.0 || w > 1.0 {
573                return Err(VectorizerError::validation("signal_boost_weight must be between 0.0 and 1.0"));
574            }
575        }
576        
577        let mut payload = serde_json::Map::new();
578        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
579        
580        if let Some(w) = name_match_weight {
581            payload.insert("name_match_weight".to_string(), serde_json::json!(w));
582        }
583        if let Some(w) = term_boost_weight {
584            payload.insert("term_boost_weight".to_string(), serde_json::json!(w));
585        }
586        if let Some(w) = signal_boost_weight {
587            payload.insert("signal_boost_weight".to_string(), serde_json::json!(w));
588        }
589
590        let response = self.make_request("POST", "/discovery/score_collections", Some(serde_json::Value::Object(payload))).await?;
591        let result: serde_json::Value = serde_json::from_str(&response)
592            .map_err(|e| VectorizerError::server(format!("Failed to parse score response: {}", e)))?;
593        Ok(result)
594    }
595
596    /// Generate query variations
597    pub async fn expand_queries(
598        &self,
599        query: &str,
600        max_expansions: Option<usize>,
601        include_definition: Option<bool>,
602        include_features: Option<bool>,
603        include_architecture: Option<bool>,
604    ) -> Result<serde_json::Value> {
605        let mut payload = serde_json::Map::new();
606        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
607        
608        if let Some(max) = max_expansions {
609            payload.insert("max_expansions".to_string(), serde_json::Value::Number(max.into()));
610        }
611        if let Some(def) = include_definition {
612            payload.insert("include_definition".to_string(), serde_json::Value::Bool(def));
613        }
614        if let Some(feat) = include_features {
615            payload.insert("include_features".to_string(), serde_json::Value::Bool(feat));
616        }
617        if let Some(arch) = include_architecture {
618            payload.insert("include_architecture".to_string(), serde_json::Value::Bool(arch));
619        }
620
621        let response = self.make_request("POST", "/discovery/expand_queries", Some(serde_json::Value::Object(payload))).await?;
622        let result: serde_json::Value = serde_json::from_str(&response)
623            .map_err(|e| VectorizerError::server(format!("Failed to parse expand response: {}", e)))?;
624        Ok(result)
625    }
626
627    // =============================================================================
628    // FILE OPERATIONS
629    // =============================================================================
630
631    /// Retrieve complete file content from a collection
632    pub async fn get_file_content(
633        &self,
634        collection: &str,
635        file_path: &str,
636        max_size_kb: Option<usize>,
637    ) -> Result<serde_json::Value> {
638        let mut payload = serde_json::Map::new();
639        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
640        payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
641        
642        if let Some(max) = max_size_kb {
643            payload.insert("max_size_kb".to_string(), serde_json::Value::Number(max.into()));
644        }
645
646        let response = self.make_request("POST", "/file/content", Some(serde_json::Value::Object(payload))).await?;
647        let result: serde_json::Value = serde_json::from_str(&response)
648            .map_err(|e| VectorizerError::server(format!("Failed to parse file content response: {}", e)))?;
649        Ok(result)
650    }
651
652    /// List all indexed files in a collection
653    pub async fn list_files_in_collection(
654        &self,
655        collection: &str,
656        filter_by_type: Option<Vec<String>>,
657        min_chunks: Option<usize>,
658        max_results: Option<usize>,
659        sort_by: Option<&str>,
660    ) -> Result<serde_json::Value> {
661        let mut payload = serde_json::Map::new();
662        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
663        
664        if let Some(types) = filter_by_type {
665            payload.insert("filter_by_type".to_string(), serde_json::to_value(types).unwrap());
666        }
667        if let Some(min) = min_chunks {
668            payload.insert("min_chunks".to_string(), serde_json::Value::Number(min.into()));
669        }
670        if let Some(max) = max_results {
671            payload.insert("max_results".to_string(), serde_json::Value::Number(max.into()));
672        }
673        if let Some(sort) = sort_by {
674            payload.insert("sort_by".to_string(), serde_json::Value::String(sort.to_string()));
675        }
676
677        let response = self.make_request("POST", "/file/list", Some(serde_json::Value::Object(payload))).await?;
678        let result: serde_json::Value = serde_json::from_str(&response)
679            .map_err(|e| VectorizerError::server(format!("Failed to parse list files response: {}", e)))?;
680        Ok(result)
681    }
682
683    /// Get extractive or structural summary of an indexed file
684    pub async fn get_file_summary(
685        &self,
686        collection: &str,
687        file_path: &str,
688        summary_type: Option<&str>,
689        max_sentences: Option<usize>,
690    ) -> Result<serde_json::Value> {
691        let mut payload = serde_json::Map::new();
692        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
693        payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
694        
695        if let Some(stype) = summary_type {
696            payload.insert("summary_type".to_string(), serde_json::Value::String(stype.to_string()));
697        }
698        if let Some(max) = max_sentences {
699            payload.insert("max_sentences".to_string(), serde_json::Value::Number(max.into()));
700        }
701
702        let response = self.make_request("POST", "/file/summary", Some(serde_json::Value::Object(payload))).await?;
703        let result: serde_json::Value = serde_json::from_str(&response)
704            .map_err(|e| VectorizerError::server(format!("Failed to parse file summary response: {}", e)))?;
705        Ok(result)
706    }
707
708    /// Retrieve chunks in original file order for progressive reading
709    pub async fn get_file_chunks_ordered(
710        &self,
711        collection: &str,
712        file_path: &str,
713        start_chunk: Option<usize>,
714        limit: Option<usize>,
715        include_context: Option<bool>,
716    ) -> Result<serde_json::Value> {
717        let mut payload = serde_json::Map::new();
718        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
719        payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
720        
721        if let Some(start) = start_chunk {
722            payload.insert("start_chunk".to_string(), serde_json::Value::Number(start.into()));
723        }
724        if let Some(lim) = limit {
725            payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
726        }
727        if let Some(ctx) = include_context {
728            payload.insert("include_context".to_string(), serde_json::Value::Bool(ctx));
729        }
730
731        let response = self.make_request("POST", "/file/chunks", Some(serde_json::Value::Object(payload))).await?;
732        let result: serde_json::Value = serde_json::from_str(&response)
733            .map_err(|e| VectorizerError::server(format!("Failed to parse chunks response: {}", e)))?;
734        Ok(result)
735    }
736
737    /// Generate hierarchical project structure overview
738    pub async fn get_project_outline(
739        &self,
740        collection: &str,
741        max_depth: Option<usize>,
742        include_summaries: Option<bool>,
743        highlight_key_files: Option<bool>,
744    ) -> Result<serde_json::Value> {
745        let mut payload = serde_json::Map::new();
746        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
747        
748        if let Some(depth) = max_depth {
749            payload.insert("max_depth".to_string(), serde_json::Value::Number(depth.into()));
750        }
751        if let Some(summ) = include_summaries {
752            payload.insert("include_summaries".to_string(), serde_json::Value::Bool(summ));
753        }
754        if let Some(highlight) = highlight_key_files {
755            payload.insert("highlight_key_files".to_string(), serde_json::Value::Bool(highlight));
756        }
757
758        let response = self.make_request("POST", "/file/outline", Some(serde_json::Value::Object(payload))).await?;
759        let result: serde_json::Value = serde_json::from_str(&response)
760            .map_err(|e| VectorizerError::server(format!("Failed to parse outline response: {}", e)))?;
761        Ok(result)
762    }
763
764    /// Find semantically related files using vector similarity
765    pub async fn get_related_files(
766        &self,
767        collection: &str,
768        file_path: &str,
769        limit: Option<usize>,
770        similarity_threshold: Option<f32>,
771        include_reason: Option<bool>,
772    ) -> Result<serde_json::Value> {
773        let mut payload = serde_json::Map::new();
774        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
775        payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
776        
777        if let Some(lim) = limit {
778            payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
779        }
780        if let Some(thresh) = similarity_threshold {
781            payload.insert("similarity_threshold".to_string(), serde_json::json!(thresh));
782        }
783        if let Some(reason) = include_reason {
784            payload.insert("include_reason".to_string(), serde_json::Value::Bool(reason));
785        }
786
787        let response = self.make_request("POST", "/file/related", Some(serde_json::Value::Object(payload))).await?;
788        let result: serde_json::Value = serde_json::from_str(&response)
789            .map_err(|e| VectorizerError::server(format!("Failed to parse related files response: {}", e)))?;
790        Ok(result)
791    }
792
793    /// Semantic search filtered by file type
794    pub async fn search_by_file_type(
795        &self,
796        collection: &str,
797        query: &str,
798        file_types: Vec<String>,
799        limit: Option<usize>,
800        return_full_files: Option<bool>,
801    ) -> Result<serde_json::Value> {
802        // Validate file_types is not empty
803        if file_types.is_empty() {
804            return Err(VectorizerError::validation("file_types cannot be empty"));
805        }
806        
807        let mut payload = serde_json::Map::new();
808        payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
809        payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
810        payload.insert("file_types".to_string(), serde_json::to_value(file_types).unwrap());
811        
812        if let Some(lim) = limit {
813            payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
814        }
815        if let Some(full) = return_full_files {
816            payload.insert("return_full_files".to_string(), serde_json::Value::Bool(full));
817        }
818
819        let response = self.make_request("POST", "/file/search_by_type", Some(serde_json::Value::Object(payload))).await?;
820        let result: serde_json::Value = serde_json::from_str(&response)
821            .map_err(|e| VectorizerError::server(format!("Failed to parse search by type response: {}", e)))?;
822        Ok(result)
823    }
824
825    /// Make HTTP request
826    async fn make_request(
827        &self,
828        method: &str,
829        endpoint: &str,
830        payload: Option<serde_json::Value>,
831    ) -> Result<String> {
832        match method {
833            "GET" => self.transport.get(endpoint).await,
834            "POST" => self.transport.post(endpoint, payload.as_ref()).await,
835            "PUT" => self.transport.put(endpoint, payload.as_ref()).await,
836            "DELETE" => self.transport.delete(endpoint).await,
837            _ => Err(VectorizerError::configuration(format!("Unsupported method: {}", method))),
838        }
839    }
840}