1#![deny(missing_docs)]
2pub use brainwires_core::provider::{ChatOptions, Provider};
10
11#[cfg(feature = "native")]
13pub mod http_client;
14#[cfg(feature = "native")]
15pub mod rate_limiter;
16
17#[cfg(feature = "native")]
18pub use http_client::RateLimitedClient;
19#[cfg(feature = "native")]
20pub use rate_limiter::RateLimiter;
21
22#[cfg(feature = "native")]
26pub mod openai_chat;
27
28#[cfg(feature = "native")]
30pub mod openai_responses;
31
32#[cfg(feature = "native")]
34pub mod anthropic;
35
36#[cfg(feature = "native")]
38pub mod gemini;
39
40#[cfg(feature = "native")]
42pub mod ollama;
43
44#[cfg(feature = "native")]
46pub mod brainwires_http;
47#[cfg(feature = "native")]
48pub use brainwires_http::{DEFAULT_BACKEND_URL, DEV_BACKEND_URL, get_backend_from_api_key};
49
50pub mod registry;
56
57#[cfg(feature = "native")]
61pub mod model_listing;
62
63#[cfg(feature = "native")]
65pub mod chat_factory;
66
67pub mod local_llm;
71
72#[cfg(feature = "native")]
78pub use anthropic::AnthropicClient;
79#[cfg(feature = "native")]
80pub use brainwires_http::BrainwiresHttpProvider;
81#[cfg(feature = "native")]
82pub use gemini::GoogleClient;
83#[cfg(feature = "native")]
84pub use ollama::OllamaProvider;
85#[cfg(feature = "native")]
86pub use openai_chat::OpenAiClient;
87
88#[cfg(feature = "native")]
90pub use anthropic::chat::AnthropicChatProvider;
91#[cfg(feature = "native")]
92pub use gemini::chat::GoogleChatProvider;
93#[cfg(feature = "native")]
94pub use ollama::chat::OllamaChatProvider;
95#[cfg(feature = "native")]
96pub use openai_chat::chat::OpenAiChatProvider;
97#[cfg(feature = "native")]
98pub use openai_responses::OpenAiResponsesProvider;
99
100#[cfg(feature = "native")]
104pub use model_listing::{AvailableModel, ModelCapability, ModelLister, create_model_lister};
105
106#[cfg(feature = "native")]
108pub use chat_factory::ChatProviderFactory;
109
110pub use local_llm::*;
112
113use serde::{Deserialize, Serialize};
114use std::fmt;
115use std::str::FromStr;
116
117#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
119#[serde(rename_all = "lowercase")]
120pub enum ProviderType {
121 Anthropic,
123 OpenAI,
125 Google,
127 Groq,
129 Ollama,
131 Brainwires,
133 Together,
135 Fireworks,
137 Anyscale,
139 Bedrock,
141 VertexAI,
143 ElevenLabs,
145 Deepgram,
147 Azure,
149 Fish,
151 Cartesia,
153 Murf,
155 OpenAiResponses,
157 MiniMax,
159 Custom,
161}
162
163impl ProviderType {
164 pub fn default_model(&self) -> &'static str {
166 match self {
167 Self::Anthropic => "claude-sonnet-4-6",
168 Self::OpenAI => "gpt-5-mini",
169 Self::Google => "gemini-2.5-flash",
170 Self::Groq => "llama-3.3-70b-versatile",
171 Self::Ollama => "llama3.3",
172 Self::Brainwires => "gpt-5-mini",
173 Self::Together => "meta-llama/Llama-3.1-8B-Instruct",
174 Self::Fireworks => "accounts/fireworks/models/llama-v3p1-8b-instruct",
175 Self::Anyscale => "meta-llama/Meta-Llama-3.1-8B-Instruct",
176 Self::Bedrock => "anthropic.claude-sonnet-4-6-v1:0",
177 Self::VertexAI => "claude-sonnet-4-6",
178 Self::ElevenLabs => "eleven_multilingual_v2",
179 Self::Deepgram => "nova-2",
180 Self::Azure => "en-US-JennyNeural",
181 Self::Fish => "default",
182 Self::Cartesia => "sonic-english",
183 Self::Murf => "en-US-natalie",
184 Self::OpenAiResponses => "gpt-5-mini",
185 Self::MiniMax => "MiniMax-M2.7",
186 Self::Custom => "claude-sonnet-4-6",
187 }
188 }
189
190 pub fn from_str_opt(s: &str) -> Option<Self> {
192 match s.to_lowercase().as_str() {
193 "anthropic" => Some(Self::Anthropic),
194 "openai" => Some(Self::OpenAI),
195 "google" | "gemini" => Some(Self::Google),
196 "groq" => Some(Self::Groq),
197 "ollama" => Some(Self::Ollama),
198 "brainwires" => Some(Self::Brainwires),
199 "together" => Some(Self::Together),
200 "fireworks" => Some(Self::Fireworks),
201 "anyscale" => Some(Self::Anyscale),
202 "bedrock" => Some(Self::Bedrock),
203 "vertex-ai" | "vertexai" | "vertex_ai" => Some(Self::VertexAI),
204 "elevenlabs" => Some(Self::ElevenLabs),
205 "deepgram" => Some(Self::Deepgram),
206 "azure" => Some(Self::Azure),
207 "fish" => Some(Self::Fish),
208 "cartesia" => Some(Self::Cartesia),
209 "murf" => Some(Self::Murf),
210 "openai-responses" | "openai_responses" => Some(Self::OpenAiResponses),
211 "minimax" => Some(Self::MiniMax),
212 "custom" => Some(Self::Custom),
213 _ => None,
214 }
215 }
216
217 pub fn as_str(&self) -> &'static str {
219 match self {
220 Self::Anthropic => "anthropic",
221 Self::OpenAI => "openai",
222 Self::Google => "google",
223 Self::Groq => "groq",
224 Self::Ollama => "ollama",
225 Self::Brainwires => "brainwires",
226 Self::Together => "together",
227 Self::Fireworks => "fireworks",
228 Self::Anyscale => "anyscale",
229 Self::Bedrock => "bedrock",
230 Self::VertexAI => "vertex-ai",
231 Self::ElevenLabs => "elevenlabs",
232 Self::Deepgram => "deepgram",
233 Self::Azure => "azure",
234 Self::Fish => "fish",
235 Self::Cartesia => "cartesia",
236 Self::Murf => "murf",
237 Self::OpenAiResponses => "openai-responses",
238 Self::MiniMax => "minimax",
239 Self::Custom => "custom",
240 }
241 }
242
243 pub fn requires_api_key(&self) -> bool {
245 !matches!(self, Self::Ollama | Self::Bedrock | Self::VertexAI)
246 }
247}
248
249impl fmt::Display for ProviderType {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 write!(f, "{}", self.as_str())
252 }
253}
254
255impl FromStr for ProviderType {
256 type Err = anyhow::Error;
257
258 fn from_str(s: &str) -> Result<Self, Self::Err> {
259 Self::from_str_opt(s).ok_or_else(|| anyhow::anyhow!("Unknown provider: {}", s))
260 }
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ProviderConfig {
266 pub provider: ProviderType,
268 pub model: String,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub api_key: Option<String>,
273 #[serde(skip_serializing_if = "Option::is_none")]
275 pub base_url: Option<String>,
276 #[serde(flatten)]
278 pub options: std::collections::HashMap<String, serde_json::Value>,
279 #[cfg(feature = "telemetry")]
281 #[serde(skip)]
282 pub analytics_collector: Option<std::sync::Arc<brainwires_telemetry::AnalyticsCollector>>,
283}
284
285impl ProviderConfig {
286 pub fn new(provider: ProviderType, model: String) -> Self {
288 Self {
289 provider,
290 model,
291 api_key: None,
292 base_url: None,
293 options: std::collections::HashMap::new(),
294 #[cfg(feature = "telemetry")]
295 analytics_collector: None,
296 }
297 }
298
299 pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
301 self.api_key = Some(api_key.into());
302 self
303 }
304
305 pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
307 self.base_url = Some(base_url.into());
308 self
309 }
310
311 pub fn with_option(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
313 self.options.insert(key.into(), value);
314 self
315 }
316
317 pub fn with_region(self, region: impl Into<String>) -> Self {
319 self.with_option("region", serde_json::Value::String(region.into()))
320 }
321
322 pub fn with_project_id(self, project_id: impl Into<String>) -> Self {
324 self.with_option("project_id", serde_json::Value::String(project_id.into()))
325 }
326
327 #[cfg(feature = "telemetry")]
329 pub fn with_analytics(
330 mut self,
331 collector: std::sync::Arc<brainwires_telemetry::AnalyticsCollector>,
332 ) -> Self {
333 self.analytics_collector = Some(collector);
334 self
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_provider_type_default_model() {
344 assert_eq!(ProviderType::Anthropic.default_model(), "claude-sonnet-4-6");
345 assert_eq!(ProviderType::OpenAI.default_model(), "gpt-5-mini");
346 assert_eq!(ProviderType::Google.default_model(), "gemini-2.5-flash");
347 assert_eq!(
348 ProviderType::Groq.default_model(),
349 "llama-3.3-70b-versatile"
350 );
351 assert_eq!(ProviderType::Ollama.default_model(), "llama3.3");
352 assert_eq!(ProviderType::Brainwires.default_model(), "gpt-5-mini");
353 assert_eq!(ProviderType::MiniMax.default_model(), "MiniMax-M2.7");
354 }
355
356 #[test]
357 fn test_provider_type_from_str() {
358 assert_eq!(
359 ProviderType::from_str_opt("anthropic"),
360 Some(ProviderType::Anthropic)
361 );
362 assert_eq!(
363 ProviderType::from_str_opt("openai"),
364 Some(ProviderType::OpenAI)
365 );
366 assert_eq!(
367 ProviderType::from_str_opt("google"),
368 Some(ProviderType::Google)
369 );
370 assert_eq!(
371 ProviderType::from_str_opt("gemini"),
372 Some(ProviderType::Google)
373 );
374 assert_eq!(ProviderType::from_str_opt("groq"), Some(ProviderType::Groq));
375 assert_eq!(
376 ProviderType::from_str_opt("ollama"),
377 Some(ProviderType::Ollama)
378 );
379 assert_eq!(
380 ProviderType::from_str_opt("brainwires"),
381 Some(ProviderType::Brainwires)
382 );
383 assert_eq!(
384 ProviderType::from_str_opt("together"),
385 Some(ProviderType::Together)
386 );
387 assert_eq!(
388 ProviderType::from_str_opt("fireworks"),
389 Some(ProviderType::Fireworks)
390 );
391 assert_eq!(
392 ProviderType::from_str_opt("anyscale"),
393 Some(ProviderType::Anyscale)
394 );
395 assert_eq!(
396 ProviderType::from_str_opt("elevenlabs"),
397 Some(ProviderType::ElevenLabs)
398 );
399 assert_eq!(
400 ProviderType::from_str_opt("deepgram"),
401 Some(ProviderType::Deepgram)
402 );
403 assert_eq!(
404 ProviderType::from_str_opt("custom"),
405 Some(ProviderType::Custom)
406 );
407 assert_eq!(
408 ProviderType::from_str_opt("minimax"),
409 Some(ProviderType::MiniMax)
410 );
411 assert_eq!(ProviderType::from_str_opt("unknown"), None);
412 }
413
414 #[test]
415 fn test_provider_type_requires_api_key() {
416 assert!(ProviderType::Anthropic.requires_api_key());
417 assert!(ProviderType::OpenAI.requires_api_key());
418 assert!(!ProviderType::Ollama.requires_api_key());
419 assert!(ProviderType::ElevenLabs.requires_api_key());
420 assert!(ProviderType::MiniMax.requires_api_key());
421 }
422
423 #[test]
424 fn test_provider_config() {
425 let config = ProviderConfig::new(ProviderType::Anthropic, "claude-3".to_string())
426 .with_api_key("sk-test")
427 .with_base_url("https://api.example.com");
428 assert_eq!(config.provider, ProviderType::Anthropic);
429 assert_eq!(config.api_key, Some("sk-test".to_string()));
430 assert_eq!(config.base_url, Some("https://api.example.com".to_string()));
431 }
432}