semantic_query/clients/
flexible.rs1use 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#[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 use super::mock::MockClient;
34 let (mock_client, _handle) = MockClient::new();
35 Box::new(mock_client)
37 }
38 }
39 }
40}
41
42impl Default for ClientType {
43 fn default() -> Self {
45 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 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 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)]
89pub struct FlexibleClient {
91 inner: Arc<Mutex<Box<dyn LowLevelClient>>>,
92 interceptor: Option<Arc<dyn Interceptor>>,
93}
94
95
96impl FlexibleClient {
97 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 pub fn new(client: Box<dyn LowLevelClient>) -> Self {
108 Self {
109 inner: Arc::new(Mutex::new(client)),
110 interceptor: None,
111 }
112 }
113
114 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 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 pub fn claude(config: ClaudeConfig) -> Self {
131 use super::claude::ClaudeClient;
132 Self::new(Box::new(ClaudeClient::new(config)))
133 }
134
135 pub fn deepseek(config: DeepSeekConfig) -> Self {
137 use super::deepseek::DeepSeekClient;
138 Self::new(Box::new(DeepSeekClient::new(config)))
139 }
140
141
142 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 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 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 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 if let Some(interceptor) = &self.interceptor {
188 if let Err(e) = interceptor.save(&prompt, &response).await {
189 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}