Skip to main content

codineer_api/
client.rs

1use crate::error::ApiError;
2use crate::providers::codineer_provider::{self, AuthSource, CodineerApiClient};
3use crate::providers::openai_compat::{self, OpenAiCompatClient, OpenAiCompatConfig};
4use crate::providers::{self, ProviderKind};
5use crate::types::{MessageRequest, MessageResponse, StreamEvent};
6
7#[derive(Debug, Clone)]
8pub enum ProviderClient {
9    CodineerApi(CodineerApiClient),
10    Xai(OpenAiCompatClient),
11    OpenAi(OpenAiCompatClient),
12}
13
14impl ProviderClient {
15    pub fn from_model(model: &str) -> Result<Self, ApiError> {
16        Self::from_model_with_default_auth(model, None)
17    }
18
19    pub fn from_model_with_default_auth(
20        model: &str,
21        default_auth: Option<AuthSource>,
22    ) -> Result<Self, ApiError> {
23        let resolved_model = providers::resolve_model_alias(model);
24        match providers::detect_provider_kind(&resolved_model) {
25            ProviderKind::CodineerApi => Ok(Self::CodineerApi(match default_auth {
26                Some(auth) => CodineerApiClient::from_auth(auth),
27                None => CodineerApiClient::from_env()?,
28            })),
29            ProviderKind::Xai => Ok(Self::Xai(OpenAiCompatClient::from_env(
30                OpenAiCompatConfig::xai(),
31            )?)),
32            ProviderKind::OpenAi => Ok(Self::OpenAi(OpenAiCompatClient::from_env(
33                OpenAiCompatConfig::openai(),
34            )?)),
35        }
36    }
37
38    #[must_use]
39    pub const fn provider_kind(&self) -> ProviderKind {
40        match self {
41            Self::CodineerApi(_) => ProviderKind::CodineerApi,
42            Self::Xai(_) => ProviderKind::Xai,
43            Self::OpenAi(_) => ProviderKind::OpenAi,
44        }
45    }
46
47    pub async fn send_message(
48        &self,
49        request: &MessageRequest,
50    ) -> Result<MessageResponse, ApiError> {
51        match self {
52            Self::CodineerApi(client) => client.send_message(request).await,
53            Self::Xai(client) | Self::OpenAi(client) => client.send_message(request).await,
54        }
55    }
56
57    pub async fn stream_message(
58        &self,
59        request: &MessageRequest,
60    ) -> Result<MessageStream, ApiError> {
61        match self {
62            Self::CodineerApi(client) => client
63                .stream_message(request)
64                .await
65                .map(MessageStream::CodineerApi),
66            Self::Xai(client) | Self::OpenAi(client) => client
67                .stream_message(request)
68                .await
69                .map(MessageStream::OpenAiCompat),
70        }
71    }
72}
73
74#[derive(Debug)]
75pub enum MessageStream {
76    CodineerApi(codineer_provider::MessageStream),
77    OpenAiCompat(openai_compat::MessageStream),
78}
79
80impl MessageStream {
81    #[must_use]
82    pub fn request_id(&self) -> Option<&str> {
83        match self {
84            Self::CodineerApi(stream) => stream.request_id(),
85            Self::OpenAiCompat(stream) => stream.request_id(),
86        }
87    }
88
89    pub async fn next_event(&mut self) -> Result<Option<StreamEvent>, ApiError> {
90        match self {
91            Self::CodineerApi(stream) => stream.next_event().await,
92            Self::OpenAiCompat(stream) => stream.next_event().await,
93        }
94    }
95}
96
97pub use codineer_provider::{
98    oauth_token_is_expired, resolve_saved_oauth_token, resolve_startup_auth_source, OAuthTokenSet,
99};
100#[must_use]
101pub fn read_base_url() -> String {
102    codineer_provider::read_base_url()
103}
104
105#[must_use]
106pub fn read_xai_base_url() -> String {
107    openai_compat::read_base_url(OpenAiCompatConfig::xai())
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::providers::{detect_provider_kind, resolve_model_alias, ProviderKind};
113
114    #[test]
115    fn resolves_existing_and_grok_aliases() {
116        assert_eq!(resolve_model_alias("opus"), "claude-opus-4-6");
117        assert_eq!(resolve_model_alias("grok"), "grok-3");
118        assert_eq!(resolve_model_alias("grok-mini"), "grok-3-mini");
119    }
120
121    #[test]
122    fn provider_detection_prefers_model_family() {
123        assert_eq!(detect_provider_kind("grok-3"), ProviderKind::Xai);
124        assert_eq!(
125            detect_provider_kind("claude-sonnet-4-6"),
126            ProviderKind::CodineerApi
127        );
128    }
129}