Skip to main content

llmg_core/
client.rs

1use crate::provider::Credentials;
2use std::time::Duration;
3
4/// HTTP client for making requests to LLM providers
5#[derive(Debug)]
6pub struct LlmClient {
7    inner: reqwest::Client,
8    base_url: String,
9    credentials: Option<Box<dyn Credentials>>,
10}
11
12impl LlmClient {
13    /// Create a new LLM client with default configuration
14    pub fn new(base_url: impl Into<String>) -> Self {
15        Self {
16            inner: reqwest::Client::new(),
17            base_url: base_url.into(),
18            credentials: None,
19        }
20    }
21
22    /// Get the underlying reqwest client
23    pub fn inner(&self) -> &reqwest::Client {
24        &self.inner
25    }
26
27    /// Get the base URL
28    pub fn base_url(&self) -> &str {
29        &self.base_url
30    }
31
32    /// Set credentials for authentication
33    pub fn with_credentials(mut self, credentials: Box<dyn Credentials>) -> Self {
34        self.credentials = Some(credentials);
35        self
36    }
37
38    /// Build a request with authentication applied
39    pub fn request(
40        &self,
41        method: reqwest::Method,
42        path: &str,
43    ) -> Result<reqwest::Request, crate::provider::LlmError> {
44        let url = format!("{}{}", self.base_url, path);
45        let mut req = reqwest::Request::new(
46            method,
47            url.parse().map_err(|e| {
48                crate::provider::LlmError::InvalidRequest(format!("Invalid URL: {}", e))
49            })?,
50        );
51
52        if let Some(creds) = &self.credentials {
53            creds.apply(&mut req)?;
54        }
55
56        Ok(req)
57    }
58}
59
60/// Builder for LlmClient
61#[derive(Debug)]
62pub struct ClientBuilder {
63    timeout: Duration,
64    pool_idle_timeout: Duration,
65    pool_max_idle_per_host: usize,
66    base_url: String,
67    credentials: Option<Box<dyn Credentials>>,
68}
69
70impl ClientBuilder {
71    /// Create a new client builder
72    pub fn new(base_url: impl Into<String>) -> Self {
73        Self {
74            timeout: Duration::from_secs(60),
75            pool_idle_timeout: Duration::from_secs(90),
76            pool_max_idle_per_host: 32,
77            base_url: base_url.into(),
78            credentials: None,
79        }
80    }
81
82    /// Set request timeout
83    pub fn timeout(mut self, timeout: Duration) -> Self {
84        self.timeout = timeout;
85        self
86    }
87
88    /// Set pool idle timeout
89    pub fn pool_idle_timeout(mut self, timeout: Duration) -> Self {
90        self.pool_idle_timeout = timeout;
91        self
92    }
93
94    /// Set max idle connections per host
95    pub fn pool_max_idle_per_host(mut self, max: usize) -> Self {
96        self.pool_max_idle_per_host = max;
97        self
98    }
99
100    /// Set credentials
101    pub fn credentials(mut self, credentials: Box<dyn Credentials>) -> Self {
102        self.credentials = Some(credentials);
103        self
104    }
105
106    /// Build the client
107    pub fn build(self) -> reqwest::Result<LlmClient> {
108        let client = reqwest::Client::builder()
109            .timeout(self.timeout)
110            .pool_idle_timeout(self.pool_idle_timeout)
111            .pool_max_idle_per_host(self.pool_max_idle_per_host)
112            .build()?;
113
114        Ok(LlmClient {
115            inner: client,
116            base_url: self.base_url,
117            credentials: self.credentials,
118        })
119    }
120}
121
122impl Default for ClientBuilder {
123    fn default() -> Self {
124        Self::new("https://api.openai.com/v1")
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_client_builder_defaults() {
134        let builder = ClientBuilder::default();
135        assert_eq!(builder.timeout, Duration::from_secs(60));
136        assert_eq!(builder.pool_max_idle_per_host, 32);
137    }
138
139    #[test]
140    fn test_client_builder_custom() {
141        let builder = ClientBuilder::new("https://api.example.com")
142            .timeout(Duration::from_secs(30))
143            .pool_max_idle_per_host(10);
144
145        assert_eq!(builder.timeout, Duration::from_secs(30));
146        assert_eq!(builder.pool_max_idle_per_host, 10);
147        assert_eq!(builder.base_url, "https://api.example.com");
148    }
149}