oxicode_ai/providers/
mod.rs1use std::sync::OnceLock;
4
5mod anthropic;
6mod azure;
7mod bedrock;
8#[cfg(feature = "protobuf")]
9mod cursor;
10#[cfg(feature = "protobuf")]
11mod devin;
12mod event;
13mod gemini_cli;
14mod gitlab_duo;
15mod gitlab_duo_agent;
16mod google;
17mod google_shared;
18pub mod model_fetch;
19mod ollama;
20mod openai;
21mod openai_responses;
22pub mod openai_responses_shared;
23mod options;
24pub mod register_builtins;
25mod sse;
26
27#[allow(unused_imports)]
28pub use register_builtins::AuthMethod;
29#[allow(unused_imports)]
30pub use register_builtins::create_builtin_provider_with_options;
31mod trait_def;
32mod vertex;
33
34use futures::Stream;
35use std::pin::Pin;
36
37#[allow(unused_imports)]
38pub use crate::Api;
39pub use crate::CacheRetention;
40pub use crate::Context;
41pub use crate::Model;
42#[allow(unused_imports)]
43pub use crate::ThinkingLevel;
44use crate::error::ProviderError;
45#[allow(unused_imports)]
46pub use anthropic::AnthropicProvider;
47#[allow(unused_imports)]
48pub use azure::AzureProvider;
49#[allow(unused_imports)]
50pub use bedrock::BedrockProvider;
51pub use event::ProviderEvent;
52#[allow(unused_imports)]
53pub use gemini_cli::GeminiCliProvider;
54pub use google::GoogleProvider;
55#[allow(unused_imports)]
56pub use ollama::OllamaProvider;
57#[allow(unused_imports)]
58pub use openai::OpenAiProvider;
59pub use openai::normalize_messages;
60#[allow(unused_imports)]
61pub use openai_responses::OpenAiResponsesProvider;
62#[allow(unused_imports)]
63pub use options::{ProviderOptions, StreamOptions, ThinkingBudgets};
64pub use trait_def::{Provider, StreamResult};
65#[allow(unused_imports)]
66pub use vertex::VertexProvider;
67
68use parking_lot::RwLock;
69use std::collections::HashMap;
70use std::sync::Arc;
71use std::sync::LazyLock;
72
73const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600;
80const DEFAULT_PROVIDER_CONNECT_TIMEOUT_SECS: u64 = 10;
81
82pub fn shared_client() -> &'static reqwest::Client {
94 static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
95 CLIENT.get_or_init(|| {
96 #[allow(clippy::expect_used)]
100 reqwest::Client::builder()
101 .connect_timeout(std::time::Duration::from_secs(
102 DEFAULT_PROVIDER_CONNECT_TIMEOUT_SECS,
103 ))
104 .timeout(std::time::Duration::from_secs(
105 DEFAULT_PROVIDER_TIMEOUT_SECS,
106 ))
107 .build()
108 .expect("provider shared_client: reqwest builder should not fail")
109 })
110}
111
112pub type ProviderFactory = Box<dyn Fn() -> anyhow::Result<Arc<dyn Provider>> + Send + Sync>;
116
117pub struct ProviderRegistry {
127 custom: RwLock<HashMap<String, Arc<dyn Provider>>>,
128 factories: RwLock<HashMap<String, ProviderFactory>>,
129}
130
131impl Default for ProviderRegistry {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137impl ProviderRegistry {
138 pub fn new() -> Self {
140 Self {
141 custom: RwLock::new(HashMap::new()),
142 factories: RwLock::new(HashMap::new()),
143 }
144 }
145
146 pub fn register(&self, name: &str, provider: impl Provider + 'static) {
148 self.custom
149 .write()
150 .insert(name.to_string(), Arc::new(provider));
151 }
152
153 pub fn register_arc(&self, name: &str, provider: Arc<dyn Provider>) {
155 self.custom.write().insert(name.to_string(), provider);
156 }
157
158 pub fn remove(&self, name: &str) {
160 self.custom.write().remove(name);
161 }
162
163 pub fn names(&self) -> Vec<String> {
165 self.custom.read().keys().cloned().collect()
166 }
167
168 pub fn get(&self, name: &str) -> Option<Arc<dyn Provider>> {
172 {
174 let guard = self.custom.read();
175 if let Some(provider) = guard.get(name) {
176 return Some(Arc::clone(provider));
177 }
178 }
179
180 get_provider(name).map(Arc::from)
182 }
183
184 pub fn get_custom(&self, name: &str) -> Option<Arc<dyn Provider>> {
189 {
190 let guard = self.custom.read();
191 if let Some(provider) = guard.get(name) {
192 return Some(Arc::clone(provider));
193 }
194 }
195 self.materialize_factory(name)
197 }
198
199 pub fn register_factory(
214 &self,
215 name: &str,
216 factory: impl Fn() -> anyhow::Result<Arc<dyn Provider>> + Send + Sync + 'static,
217 ) {
218 self.factories
219 .write()
220 .insert(name.to_string(), Box::new(factory));
221 }
222
223 fn materialize_factory(&self, name: &str) -> Option<Arc<dyn Provider>> {
228 let factory = {
229 let mut factories = self.factories.write();
230 factories.remove(name)?
231 };
232 match factory() {
233 Ok(provider) => {
234 self.custom
235 .write()
236 .insert(name.to_string(), Arc::clone(&provider));
237 Some(provider)
238 }
239 Err(e) => {
240 tracing::warn!(provider = name, error = %e, "Provider factory failed");
241 None
242 }
243 }
244 }
245}
246
247static CUSTOM_PROVIDERS: LazyLock<RwLock<HashMap<String, Arc<dyn Provider>>>> =
254 LazyLock::new(|| RwLock::new(HashMap::new()));
255
256pub fn register_provider(name: &str, provider: impl Provider + 'static) {
261 CUSTOM_PROVIDERS
262 .write()
263 .insert(name.to_string(), Arc::new(provider));
264}
265
266pub fn unregister_provider(name: &str) {
268 CUSTOM_PROVIDERS.write().remove(name);
269}
270
271pub fn custom_provider_names() -> Vec<String> {
273 CUSTOM_PROVIDERS.read().keys().cloned().collect()
274}
275
276pub fn get_provider(name: &str) -> Option<Box<dyn Provider>> {
281 {
283 let custom = CUSTOM_PROVIDERS.read();
284 if let Some(provider) = custom.get(name) {
285 return Some(Box::new(ArcedProvider(provider.clone())));
286 }
287 }
288
289 register_builtins::create_builtin_provider(name)
291}
292
293pub fn get_provider_arc(name: &str) -> Option<Arc<dyn Provider>> {
295 {
296 let custom = CUSTOM_PROVIDERS.read();
297 if let Some(provider) = custom.get(name) {
298 return Some(Arc::clone(provider));
299 }
300 }
301 register_builtins::create_builtin_provider(name).map(Arc::from)
302}
303
304struct ArcedProvider(Arc<dyn Provider>);
306
307impl Provider for ArcedProvider {
308 fn stream<'a>(
309 &'a self,
310 model: &'a Model,
311 context: &'a Context,
312 options: Option<StreamOptions>,
313 ) -> Pin<Box<dyn Future<Output = StreamResult> + Send + 'a>> {
314 Box::pin(async move { self.0.stream(model, context, options).await })
315 }
316}
317
318pub async fn stream(
320 model: &Model,
321 context: &Context,
322 options: Option<StreamOptions>,
323) -> Result<Pin<Box<dyn Stream<Item = ProviderEvent> + Send>>, ProviderError> {
324 let provider = get_provider(&model.provider)
325 .ok_or_else(|| ProviderError::UnknownProvider(model.provider.clone()))?;
326
327 provider.stream(model, context, options).await
328}