use crate::error::ApiError; use crate::providers::anthropic::{self, AnthropicClient, AuthSource}; use crate::providers::openai_compat::{self, OpenAiCompatClient, OpenAiCompatConfig}; use crate::providers::{self, Provider, ProviderKind}; use crate::types::{MessageRequest, MessageResponse, StreamEvent}; async fn send_via_provider( provider: &P, request: &MessageRequest, ) -> Result { provider.send_message(request).await } async fn stream_via_provider( provider: &P, request: &MessageRequest, ) -> Result { provider.stream_message(request).await } #[derive(Debug, Clone)] pub enum ProviderClient { Anthropic(AnthropicClient), Xai(OpenAiCompatClient), OpenAi(OpenAiCompatClient), } impl ProviderClient { pub fn from_model(model: &str) -> Result { Self::from_model_with_anthropic_auth(model, None) } pub fn from_model_with_anthropic_auth( model: &str, anthropic_auth: Option, ) -> Result { let resolved_model = providers::resolve_model_alias(model); match providers::detect_provider_kind(&resolved_model) { ProviderKind::Anthropic => Ok(Self::Anthropic( anthropic_auth .map(AnthropicClient::from_auth) .unwrap_or(AnthropicClient::from_env()?), )), ProviderKind::Xai => Ok(Self::Xai(OpenAiCompatClient::from_env( OpenAiCompatConfig::xai(), )?)), ProviderKind::OpenAi => Ok(Self::OpenAi(OpenAiCompatClient::from_env( OpenAiCompatConfig::openai(), )?)), } } #[must_use] pub const fn provider_kind(&self) -> ProviderKind { match self { Self::Anthropic(_) => ProviderKind::Anthropic, Self::Xai(_) => ProviderKind::Xai, Self::OpenAi(_) => ProviderKind::OpenAi, } } pub async fn send_message( &self, request: &MessageRequest, ) -> Result { match self { Self::Anthropic(client) => send_via_provider(client, request).await, Self::Xai(client) | Self::OpenAi(client) => send_via_provider(client, request).await, } } pub async fn stream_message( &self, request: &MessageRequest, ) -> Result { match self { Self::Anthropic(client) => stream_via_provider(client, request) .await .map(MessageStream::Anthropic), Self::Xai(client) | Self::OpenAi(client) => stream_via_provider(client, request) .await .map(MessageStream::OpenAiCompat), } } } #[derive(Debug)] pub enum MessageStream { Anthropic(anthropic::MessageStream), OpenAiCompat(openai_compat::MessageStream), } impl MessageStream { #[must_use] pub fn request_id(&self) -> Option<&str> { match self { Self::Anthropic(stream) => stream.request_id(), Self::OpenAiCompat(stream) => stream.request_id(), } } pub async fn next_event(&mut self) -> Result, ApiError> { match self { Self::Anthropic(stream) => stream.next_event().await, Self::OpenAiCompat(stream) => stream.next_event().await, } } } pub use anthropic::{ oauth_token_is_expired, resolve_saved_oauth_token, resolve_startup_auth_source, OAuthTokenSet, }; #[must_use] pub fn read_base_url() -> String { anthropic::read_base_url() } #[must_use] pub fn read_xai_base_url() -> String { openai_compat::read_base_url(OpenAiCompatConfig::xai()) } #[cfg(test)] mod tests { use crate::providers::{detect_provider_kind, resolve_model_alias, ProviderKind}; #[test] fn resolves_existing_and_grok_aliases() { assert_eq!(resolve_model_alias("opus"), "claude-opus-4-6"); assert_eq!(resolve_model_alias("grok"), "grok-3"); assert_eq!(resolve_model_alias("grok-mini"), "grok-3-mini"); } #[test] fn provider_detection_prefers_model_family() { assert_eq!(detect_provider_kind("grok-3"), ProviderKind::Xai); assert_eq!( detect_provider_kind("claude-sonnet-4-6"), ProviderKind::Anthropic ); } }