1pub use aither_claude::{self as claude, Claude, ClaudeProvider};
10pub use aither_codex::{self as codex, Codex, CodexProvider};
11pub use aither_copilot::{self as copilot, Copilot, CopilotProvider};
12pub use aither_gemini::{self as gemini, Gemini, GeminiProvider};
13pub use aither_openai::{self as openai, OpenAI, OpenAIProvider};
14
15use aither_core::{
16 EmbeddingModel, LanguageModel,
17 llm::{
18 Event, LLMRequest, LanguageModelProvider, model::Profile,
19 provider::Profile as ProviderProfile,
20 },
21};
22use futures_core::Stream;
23use futures_lite::StreamExt;
24
25#[derive(Clone)]
27pub enum CloudProvider {
28 OpenAI(OpenAI),
30 Claude(Claude),
32 Gemini(Gemini),
34 Copilot(Copilot),
36 Codex(Codex),
38}
39
40#[derive(Clone, Debug)]
42pub enum CloudEmbedder {
43 OpenAI(OpenAI),
45 Gemini(Gemini),
47}
48
49impl From<OpenAI> for CloudEmbedder {
50 fn from(client: OpenAI) -> Self {
51 Self::OpenAI(client)
52 }
53}
54
55impl From<Gemini> for CloudEmbedder {
56 fn from(client: Gemini) -> Self {
57 Self::Gemini(client)
58 }
59}
60
61#[derive(Debug, thiserror::Error)]
63pub enum CloudEmbedderError {
64 #[error("provider does not support embeddings: {0}")]
66 UnsupportedProvider(&'static str),
67}
68
69impl TryFrom<CloudProvider> for CloudEmbedder {
70 type Error = CloudEmbedderError;
71
72 fn try_from(value: CloudProvider) -> Result<Self, Self::Error> {
73 match value {
74 CloudProvider::OpenAI(client) => Ok(Self::OpenAI(client)),
75 CloudProvider::Gemini(client) => Ok(Self::Gemini(client)),
76 CloudProvider::Claude(_) => Err(CloudEmbedderError::UnsupportedProvider("claude")),
77 CloudProvider::Copilot(_) => Err(CloudEmbedderError::UnsupportedProvider("copilot")),
78 CloudProvider::Codex(_) => Err(CloudEmbedderError::UnsupportedProvider("codex")),
79 }
80 }
81}
82
83impl EmbeddingModel for CloudEmbedder {
84 fn dim(&self) -> usize {
85 match self {
86 Self::OpenAI(client) => client.dim(),
87 Self::Gemini(client) => client.dim(),
88 }
89 }
90
91 fn embed(
92 &self,
93 text: &str,
94 ) -> impl std::future::Future<Output = aither_core::Result<Vec<f32>>> + Send {
95 let embedder = self.clone();
96 let text = text.to_string();
97 async move {
98 match embedder {
99 Self::OpenAI(client) => client.embed(text.as_str()).await,
100 Self::Gemini(client) => client.embed(text.as_str()).await,
101 }
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::{CloudEmbedder, CloudEmbedderError, CloudProvider};
109 use crate::{Claude, Copilot, Gemini, OpenAI};
110
111 #[test]
112 fn openai_and_gemini_convert_to_cloud_embedder() {
113 assert!(matches!(
114 CloudEmbedder::try_from(CloudProvider::OpenAI(OpenAI::new("test"))),
115 Ok(CloudEmbedder::OpenAI(_))
116 ));
117 assert!(matches!(
118 CloudEmbedder::try_from(CloudProvider::Gemini(Gemini::new("test"))),
119 Ok(CloudEmbedder::Gemini(_))
120 ));
121 }
122
123 #[test]
124 fn unsupported_cloud_providers_fail_fast() {
125 assert!(matches!(
126 CloudEmbedder::try_from(CloudProvider::Claude(Claude::new("test"))),
127 Err(CloudEmbedderError::UnsupportedProvider("claude"))
128 ));
129 assert!(matches!(
130 CloudEmbedder::try_from(CloudProvider::Copilot(Copilot::new("test"))),
131 Err(CloudEmbedderError::UnsupportedProvider("copilot"))
132 ));
133 }
134}
135
136impl From<OpenAI> for CloudProvider {
137 fn from(client: OpenAI) -> Self {
138 Self::OpenAI(client)
139 }
140}
141
142impl From<Claude> for CloudProvider {
143 fn from(client: Claude) -> Self {
144 Self::Claude(client)
145 }
146}
147
148impl From<Gemini> for CloudProvider {
149 fn from(client: Gemini) -> Self {
150 Self::Gemini(client)
151 }
152}
153
154impl From<Copilot> for CloudProvider {
155 fn from(client: Copilot) -> Self {
156 Self::Copilot(client)
157 }
158}
159
160impl From<Codex> for CloudProvider {
161 fn from(client: Codex) -> Self {
162 Self::Codex(client)
163 }
164}
165
166impl std::fmt::Debug for CloudProvider {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 match self {
169 Self::OpenAI(_) => f.debug_tuple("CloudProvider::OpenAI").finish(),
170 Self::Claude(_) => f.debug_tuple("CloudProvider::Claude").finish(),
171 Self::Gemini(_) => f.debug_tuple("CloudProvider::Gemini").finish(),
172 Self::Copilot(_) => f.debug_tuple("CloudProvider::Copilot").finish(),
173 Self::Codex(_) => f.debug_tuple("CloudProvider::Codex").finish(),
174 }
175 }
176}
177
178#[derive(Debug, thiserror::Error)]
180pub enum CloudError {
181 #[error("OpenAI error: {0}")]
183 OpenAI(#[from] aither_openai::OpenAIError),
184 #[error("Claude error: {0}")]
186 Claude(#[from] aither_claude::ClaudeError),
187 #[error("Gemini error: {0}")]
189 Gemini(#[from] aither_gemini::GeminiError),
190 #[error("Copilot error: {0}")]
192 Copilot(#[from] aither_copilot::CopilotError),
193 #[error("Codex error: {0}")]
195 Codex(#[from] aither_codex::CodexError),
196}
197
198impl LanguageModel for CloudProvider {
199 type Error = CloudError;
200
201 fn respond(
202 &self,
203 request: LLMRequest,
204 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
205 let provider = match self {
206 Self::OpenAI(inner) => ProviderInner::OpenAI(inner.clone()),
207 Self::Claude(inner) => ProviderInner::Claude(inner.clone()),
208 Self::Gemini(inner) => ProviderInner::Gemini(inner.clone()),
209 Self::Copilot(inner) => ProviderInner::Copilot(inner.clone()),
210 Self::Codex(inner) => ProviderInner::Codex(inner.clone()),
211 };
212
213 async_stream::stream! {
214 match provider {
215 ProviderInner::OpenAI(inner) => {
216 let mut stream = std::pin::pin!(inner.respond(request));
217 while let Some(result) = stream.next().await {
218 yield result.map_err(CloudError::from);
219 }
220 }
221 ProviderInner::Claude(inner) => {
222 let mut stream = std::pin::pin!(inner.respond(request));
223 while let Some(result) = stream.next().await {
224 yield result.map_err(CloudError::from);
225 }
226 }
227 ProviderInner::Gemini(inner) => {
228 let mut stream = std::pin::pin!(inner.respond(request));
229 while let Some(result) = stream.next().await {
230 yield result.map_err(CloudError::from);
231 }
232 }
233 ProviderInner::Copilot(inner) => {
234 let mut stream = std::pin::pin!(inner.respond(request));
235 while let Some(result) = stream.next().await {
236 yield result.map_err(CloudError::from);
237 }
238 }
239 ProviderInner::Codex(inner) => {
240 let mut stream = std::pin::pin!(inner.respond(request));
241 while let Some(result) = stream.next().await {
242 yield result.map_err(CloudError::from);
243 }
244 }
245 }
246 }
247 }
248
249 async fn profile(&self) -> Profile {
250 match self {
251 Self::OpenAI(inner) => inner.profile().await,
252 Self::Claude(inner) => inner.profile().await,
253 Self::Gemini(inner) => inner.profile().await,
254 Self::Copilot(inner) => inner.profile().await,
255 Self::Codex(inner) => inner.profile().await,
256 }
257 }
258}
259
260enum ProviderInner {
262 OpenAI(OpenAI),
263 Claude(Claude),
264 Gemini(Gemini),
265 Copilot(Copilot),
266 Codex(Codex),
267}
268
269#[derive(Clone, Debug)]
273pub enum CloudModelProvider {
274 OpenAI(OpenAIProvider),
276 Claude(ClaudeProvider),
278 Gemini(GeminiProvider),
280 Copilot(CopilotProvider),
282 Codex(CodexProvider),
284}
285
286impl From<OpenAIProvider> for CloudModelProvider {
287 fn from(provider: OpenAIProvider) -> Self {
288 Self::OpenAI(provider)
289 }
290}
291
292impl From<ClaudeProvider> for CloudModelProvider {
293 fn from(provider: ClaudeProvider) -> Self {
294 Self::Claude(provider)
295 }
296}
297
298impl From<GeminiProvider> for CloudModelProvider {
299 fn from(provider: GeminiProvider) -> Self {
300 Self::Gemini(provider)
301 }
302}
303
304impl From<CopilotProvider> for CloudModelProvider {
305 fn from(provider: CopilotProvider) -> Self {
306 Self::Copilot(provider)
307 }
308}
309
310impl From<CodexProvider> for CloudModelProvider {
311 fn from(provider: CodexProvider) -> Self {
312 Self::Codex(provider)
313 }
314}
315
316impl LanguageModelProvider for CloudModelProvider {
317 type Model = CloudProvider;
318 type Error = CloudError;
319
320 fn list_models(
321 &self,
322 ) -> impl std::future::Future<Output = Result<Vec<Profile>, Self::Error>> + Send {
323 let provider = self.clone();
324 async move {
325 match provider {
326 Self::OpenAI(p) => p.list_models().await.map_err(CloudError::from),
327 Self::Claude(p) => p.list_models().await.map_err(CloudError::from),
328 Self::Gemini(p) => p.list_models().await.map_err(CloudError::from),
329 Self::Copilot(p) => p.list_models().await.map_err(CloudError::from),
330 Self::Codex(p) => p.list_models().await.map_err(CloudError::from),
331 }
332 }
333 }
334
335 fn get_model(
336 &self,
337 name: &str,
338 ) -> impl std::future::Future<Output = Result<Self::Model, Self::Error>> + Send {
339 let provider = self.clone();
340 let name = name.to_string();
341 async move {
342 match provider {
343 Self::OpenAI(p) => p
344 .get_model(&name)
345 .await
346 .map(CloudProvider::from)
347 .map_err(CloudError::from),
348 Self::Claude(p) => p
349 .get_model(&name)
350 .await
351 .map(CloudProvider::from)
352 .map_err(CloudError::from),
353 Self::Gemini(p) => p
354 .get_model(&name)
355 .await
356 .map(CloudProvider::from)
357 .map_err(CloudError::from),
358 Self::Copilot(p) => p
359 .get_model(&name)
360 .await
361 .map(CloudProvider::from)
362 .map_err(CloudError::from),
363 Self::Codex(p) => p
364 .get_model(&name)
365 .await
366 .map(CloudProvider::from)
367 .map_err(CloudError::from),
368 }
369 }
370 }
371
372 fn profile() -> ProviderProfile {
373 ProviderProfile::new("cloud", "Unified cloud provider")
374 }
375}