semantic_query/clients/
flexible.rs

1use crate::clients::{ClaudeConfig, DeepSeekConfig};
2use crate::core::{LowLevelClient};
3use crate::error::{AIError};
4use crate::interceptors::{FileInterceptor, Interceptor};
5use async_trait::async_trait;
6use std::env;
7use std::path::PathBuf;
8use std::sync::{Arc, Mutex};
9
10
11/// Client type for lazy initialization
12#[derive(Debug, Clone)]
13pub enum ClientType {
14    Claude,
15    DeepSeek,
16    Mock,
17}
18
19impl Into<Box<dyn LowLevelClient>> for ClientType {
20    fn into(self) -> Box<dyn LowLevelClient> {
21        match self {
22            ClientType::Claude => {
23                use super::claude::ClaudeClient;
24                Box::new(ClaudeClient::default())
25            }
26            ClientType::DeepSeek => {
27                use super::deepseek::DeepSeekClient;
28                Box::new(DeepSeekClient::default())
29            }
30            ClientType::Mock => {
31                // Note: This creates a mock without a controllable handle
32                // Use FlexibleClient::new_mock() if you need to control the mock
33                use super::mock::MockClient;
34                let (mock_client, _handle) = MockClient::new();
35                // The handle is dropped here, making this mock uncontrollable
36                Box::new(mock_client)
37            }
38        }
39    }
40}
41
42impl Default for ClientType {
43      /// Get the default client type based on available API keys
44      fn default() -> Self {
45        // Check for API keys in order of preference
46        if env::var("ANTHROPIC_API_KEY").is_ok() || 
47           std::fs::read_to_string(".env").map_or(false, |content| content.contains("ANTHROPIC_API_KEY")) {
48            Self::Claude
49        } else if env::var("DEEPSEEK_API_KEY").is_ok() || 
50                 std::fs::read_to_string(".env").map_or(false, |content| content.contains("DEEPSEEK_API_KEY")) {
51            Self::DeepSeek
52        } else {
53            Self::Mock
54        }
55    }
56}
57impl ClientType {
58    /// Parse client type from string (case insensitive)
59    pub fn from_str(s: &str) -> Result<Self, String> {
60        match s.to_lowercase().as_str() {
61            "claude" => Ok(Self::Claude),
62            "deepseek" => Ok(Self::DeepSeek),
63            "mock" => Ok(Self::Mock),
64            _ => Err(format!("Unknown client type: '{}'. Supported: claude, deepseek, mock", s))
65        }
66    }
67    
68    /// Create a mock variant that returns both the client type and a handle
69    pub fn mock_with_handle() -> (Self, Arc<super::mock::MockHandle>) {
70        use super::mock::MockClient;
71        let (_, handle) = MockClient::new();
72        (Self::Mock, handle)
73    }
74}
75
76
77impl std::fmt::Display for ClientType {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            ClientType::Claude => write!(f, "Claude"),
81            ClientType::DeepSeek => write!(f, "DeepSeek"),
82            ClientType::Mock => write!(f, "Mock"),
83        }
84    }
85}
86
87
88#[derive(Debug)]
89/// Flexible client that wraps any LowLevelClient and provides factory functions
90pub struct FlexibleClient {
91    inner: Arc<Mutex<Box<dyn LowLevelClient>>>,
92    interceptor: Option<Arc<dyn Interceptor>>,
93}
94
95
96impl FlexibleClient {
97    /// Create a new FlexibleClient with lazy initialization
98    pub fn new_lazy(client_type: ClientType) -> Self {
99       
100        Self { 
101            inner: Arc::new(Mutex::new(client_type.into())),
102            interceptor: None,
103        }
104    }
105    
106    /// Create a new FlexibleClient wrapping the given client
107    pub fn new(client: Box<dyn LowLevelClient>) -> Self {
108        Self { 
109            inner: Arc::new(Mutex::new(client)),
110            interceptor: None,
111        }
112    }
113    
114    /// Create a new FlexibleClient with an interceptor
115    pub fn with_interceptor(&self, interceptor: Arc<dyn Interceptor>) -> Self {
116        Self {
117            inner: self.inner.clone(),
118            interceptor: Some(interceptor),
119        }
120    }
121    
122       /// Create a new FlexibleClient with an interceptor
123       pub fn with_file_interceptor(&self, path: PathBuf) -> Self {
124        Self {
125            inner: self.inner.clone(),
126            interceptor: Some(Arc::new(FileInterceptor::new(path))),
127        }
128    }
129    /// Create a FlexibleClient with a Claude client
130    pub fn claude(config: ClaudeConfig) -> Self {
131        use super::claude::ClaudeClient;
132        Self::new(Box::new(ClaudeClient::new(config)))
133    }
134    
135    /// Create a FlexibleClient with a DeepSeek client  
136    pub fn deepseek(config: DeepSeekConfig) -> Self {
137        use super::deepseek::DeepSeekClient;
138        Self::new(Box::new(DeepSeekClient::new(config)))
139    }
140    
141    
142    /// Create a FlexibleClient with a mock and return the handle for configuration
143    pub fn mock() -> (Self, Arc<super::mock::MockHandle>) {
144        use super::mock::MockClient;
145        let (mock_client, handle) = MockClient::new();
146        let flexible = Self::new(Box::new(mock_client));
147        (flexible, handle)
148    }
149
150    /// Create a FlexibleClient mock with predefined responses
151    pub fn new_mock_with_responses(responses: Vec<super::mock::MockResponse>) -> (Self, Arc<super::mock::MockHandle>) {
152        use super::mock::MockClient;
153        let (mock_client, handle) = MockClient::with_responses(responses);
154        let flexible = Self::new(Box::new(mock_client));
155        (flexible, handle)
156    }
157    
158    /// Convert into the inner boxed client (initializes if needed)
159    pub fn into_inner(self) -> Result<Box<dyn LowLevelClient>, AIError> {
160        let inner = self.inner.lock().unwrap().clone_box();
161        Ok(inner)
162    }
163}
164
165impl Clone for FlexibleClient {
166    fn clone(&self) -> Self {
167        Self {
168            inner: self.inner.clone(),
169            interceptor: self.interceptor.clone(),
170        }
171    }
172}
173
174#[async_trait]
175impl LowLevelClient for FlexibleClient {
176    async fn ask_raw(&self, prompt: String) -> Result<String, AIError> {
177        
178        // Clone the client to avoid holding the mutex across await
179        let client = {
180            let inner = self.inner.lock().unwrap();
181            inner.as_ref().clone_box()
182        };
183        
184        let response = client.ask_raw(prompt.clone()).await?;
185        
186        // Save to interceptor if present
187        if let Some(interceptor) = &self.interceptor {
188            if let Err(e) = interceptor.save(&prompt, &response).await {
189                // Log error but don't fail the request
190                eprintln!("Interceptor save failed: {}", e);
191            }
192        }
193        
194        Ok(response)
195    }
196    
197    fn clone_box(&self) -> Box<dyn LowLevelClient> {
198        Box::new(self.clone())
199    }
200}