1use crate::provider::Credentials;
2use std::time::Duration;
3
4#[derive(Debug)]
6pub struct LlmClient {
7 inner: reqwest::Client,
8 base_url: String,
9 credentials: Option<Box<dyn Credentials>>,
10}
11
12impl LlmClient {
13 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 pub fn inner(&self) -> &reqwest::Client {
24 &self.inner
25 }
26
27 pub fn base_url(&self) -> &str {
29 &self.base_url
30 }
31
32 pub fn with_credentials(mut self, credentials: Box<dyn Credentials>) -> Self {
34 self.credentials = Some(credentials);
35 self
36 }
37
38 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#[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 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 pub fn timeout(mut self, timeout: Duration) -> Self {
84 self.timeout = timeout;
85 self
86 }
87
88 pub fn pool_idle_timeout(mut self, timeout: Duration) -> Self {
90 self.pool_idle_timeout = timeout;
91 self
92 }
93
94 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 pub fn credentials(mut self, credentials: Box<dyn Credentials>) -> Self {
102 self.credentials = Some(credentials);
103 self
104 }
105
106 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}