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