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 Classify, Event, LLMRequest, LanguageModelProvider, RetryPolicy, 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 Classify for CloudError {
199 fn failure(&self) -> aither_core::llm::Failure {
200 match self {
201 Self::OpenAI(error) => error.failure(),
202 Self::Claude(error) => error.failure(),
203 Self::Gemini(error) => error.failure(),
204 Self::Copilot(error) => error.failure(),
205 Self::Codex(error) => error.failure(),
206 }
207 }
208}
209
210impl LanguageModel for CloudProvider {
211 type Error = CloudError;
212
213 fn respond(
214 &self,
215 request: LLMRequest,
216 ) -> impl Stream<Item = Result<Event, Self::Error>> + Send {
217 let provider = match self {
218 Self::OpenAI(inner) => ProviderInner::OpenAI(inner.clone()),
219 Self::Claude(inner) => ProviderInner::Claude(inner.clone()),
220 Self::Gemini(inner) => ProviderInner::Gemini(inner.clone()),
221 Self::Copilot(inner) => ProviderInner::Copilot(inner.clone()),
222 Self::Codex(inner) => ProviderInner::Codex(inner.clone()),
223 };
224
225 async_stream::stream! {
226 match provider {
227 ProviderInner::OpenAI(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::Claude(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::Gemini(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 ProviderInner::Copilot(inner) => {
246 let mut stream = std::pin::pin!(inner.respond(request));
247 while let Some(result) = stream.next().await {
248 yield result.map_err(CloudError::from);
249 }
250 }
251 ProviderInner::Codex(inner) => {
252 let mut stream = std::pin::pin!(inner.respond(request));
253 while let Some(result) = stream.next().await {
254 yield result.map_err(CloudError::from);
255 }
256 }
257 }
258 }
259 }
260
261 fn retry_policy(&self) -> RetryPolicy {
262 match self {
263 Self::OpenAI(inner) => inner.retry_policy(),
264 Self::Claude(inner) => inner.retry_policy(),
265 Self::Gemini(inner) => inner.retry_policy(),
266 Self::Copilot(inner) => inner.retry_policy(),
267 Self::Codex(inner) => inner.retry_policy(),
268 }
269 }
270
271 async fn profile(&self) -> Result<Profile, Self::Error> {
272 match self {
273 Self::OpenAI(inner) => inner.profile().await.map_err(CloudError::from),
274 Self::Claude(inner) => inner.profile().await.map_err(CloudError::from),
275 Self::Gemini(inner) => inner.profile().await.map_err(CloudError::from),
276 Self::Copilot(inner) => inner.profile().await.map_err(CloudError::from),
277 Self::Codex(inner) => inner.profile().await.map_err(CloudError::from),
278 }
279 }
280}
281
282enum ProviderInner {
284 OpenAI(OpenAI),
285 Claude(Claude),
286 Gemini(Gemini),
287 Copilot(Copilot),
288 Codex(Codex),
289}
290
291#[derive(Clone, Debug)]
295pub enum CloudModelProvider {
296 OpenAI(OpenAIProvider),
298 Claude(ClaudeProvider),
300 Gemini(GeminiProvider),
302 Copilot(CopilotProvider),
304 Codex(CodexProvider),
306}
307
308impl From<OpenAIProvider> for CloudModelProvider {
309 fn from(provider: OpenAIProvider) -> Self {
310 Self::OpenAI(provider)
311 }
312}
313
314impl From<ClaudeProvider> for CloudModelProvider {
315 fn from(provider: ClaudeProvider) -> Self {
316 Self::Claude(provider)
317 }
318}
319
320impl From<GeminiProvider> for CloudModelProvider {
321 fn from(provider: GeminiProvider) -> Self {
322 Self::Gemini(provider)
323 }
324}
325
326impl From<CopilotProvider> for CloudModelProvider {
327 fn from(provider: CopilotProvider) -> Self {
328 Self::Copilot(provider)
329 }
330}
331
332impl From<CodexProvider> for CloudModelProvider {
333 fn from(provider: CodexProvider) -> Self {
334 Self::Codex(provider)
335 }
336}
337
338impl LanguageModelProvider for CloudModelProvider {
339 type Model = CloudProvider;
340 type Error = CloudError;
341
342 fn list_models(
343 &self,
344 ) -> impl std::future::Future<Output = Result<Vec<Profile>, Self::Error>> + Send {
345 let provider = self.clone();
346 async move {
347 match provider {
348 Self::OpenAI(p) => p.list_models().await.map_err(CloudError::from),
349 Self::Claude(p) => p.list_models().await.map_err(CloudError::from),
350 Self::Gemini(p) => p.list_models().await.map_err(CloudError::from),
351 Self::Copilot(p) => p.list_models().await.map_err(CloudError::from),
352 Self::Codex(p) => p.list_models().await.map_err(CloudError::from),
353 }
354 }
355 }
356
357 fn get_model(
358 &self,
359 name: &str,
360 ) -> impl std::future::Future<Output = Result<Self::Model, Self::Error>> + Send {
361 let provider = self.clone();
362 let name = name.to_string();
363 async move {
364 match provider {
365 Self::OpenAI(p) => p
366 .get_model(&name)
367 .await
368 .map(CloudProvider::from)
369 .map_err(CloudError::from),
370 Self::Claude(p) => p
371 .get_model(&name)
372 .await
373 .map(CloudProvider::from)
374 .map_err(CloudError::from),
375 Self::Gemini(p) => p
376 .get_model(&name)
377 .await
378 .map(CloudProvider::from)
379 .map_err(CloudError::from),
380 Self::Copilot(p) => p
381 .get_model(&name)
382 .await
383 .map(CloudProvider::from)
384 .map_err(CloudError::from),
385 Self::Codex(p) => p
386 .get_model(&name)
387 .await
388 .map(CloudProvider::from)
389 .map_err(CloudError::from),
390 }
391 }
392 }
393
394 fn profile() -> ProviderProfile {
395 ProviderProfile::new("cloud", "Unified cloud provider")
396 }
397}