Skip to main content

agentik_sdk/http/
client.rs

1use crate::config::ClientConfig;
2use crate::http::auth::AuthHandler;
3use crate::types::errors::{AnthropicError, Result};
4use crate::types::shared::RequestId;
5use reqwest::{Client, Request, RequestBuilder, Response};
6use serde_json::Value;
7
8#[derive(Debug, Clone)]
9pub struct HttpClient {
10    client: Client,
11    config: ClientConfig,
12    auth: AuthHandler,
13}
14
15impl HttpClient {
16    pub fn new(config: ClientConfig) -> Result<Self> {
17        // Validate configuration before creating client
18        config.validate()?;
19
20        let client = Client::builder()
21            .timeout(config.timeout)
22            .build()
23            .map_err(|e| AnthropicError::Connection {
24                message: e.to_string(),
25            })?;
26
27        let auth = AuthHandler::with_method(config.api_key.clone(), config.auth_method.clone());
28
29        Ok(Self {
30            client,
31            config,
32            auth,
33        })
34    }
35
36    /// Send a prepared request with authentication and error handling
37    pub async fn send(&self, mut request: Request) -> Result<Response> {
38        // Add authentication headers
39        let headers = request.headers_mut();
40        self.auth.add_auth_headers(headers)?;
41
42        let response =
43            self.client
44                .execute(request)
45                .await
46                .map_err(|e| AnthropicError::Connection {
47                    message: e.to_string(),
48                })?;
49
50        self.handle_response_status(response).await
51    }
52
53    /// Create a GET request builder
54    pub fn get(&self, url: &str) -> RequestBuilder {
55        self.client.get(url)
56    }
57
58    /// Create a POST request builder
59    pub fn post(&self, url: &str) -> RequestBuilder {
60        self.client.post(url)
61    }
62
63    /// Create a PUT request builder
64    pub fn put(&self, url: &str) -> RequestBuilder {
65        self.client.put(url)
66    }
67
68    /// Create a DELETE request builder
69    pub fn delete(&self, url: &str) -> RequestBuilder {
70        self.client.delete(url)
71    }
72
73    /// Build a full URL from a path
74    pub fn build_url(&self, path: &str) -> String {
75        format!("{}{}", self.config.base_url.trim_end_matches('/'), path)
76    }
77
78    /// Handle response status codes and convert to appropriate errors
79    async fn handle_response_status(&self, response: Response) -> Result<Response> {
80        let status = response.status();
81        if status.is_success() {
82            return Ok(response);
83        }
84
85        let status_code = status.as_u16();
86
87        // Try to extract error message from response body
88        let error_message = match response.text().await {
89            Ok(body) => {
90                // Try to parse as JSON and extract error message
91                match serde_json::from_str::<Value>(&body) {
92                    Ok(json) => json
93                        .get("error")
94                        .and_then(|e| e.get("message"))
95                        .and_then(|m| m.as_str())
96                        .unwrap_or(&body)
97                        .to_string(),
98                    Err(_) => body,
99                }
100            }
101            Err(_) => format!(
102                "HTTP {}: {}",
103                status_code,
104                status.canonical_reason().unwrap_or("Unknown")
105            ),
106        };
107
108        Err(AnthropicError::from_status(status_code, error_message))
109    }
110
111    /// Extract request ID from response headers
112    pub fn extract_request_id(&self, response: &Response) -> Option<RequestId> {
113        response
114            .headers()
115            .get("request-id")
116            .and_then(|value| value.to_str().ok())
117            .map(|id| RequestId::new(id.to_string()))
118    }
119
120    /// Get the base URL
121    pub fn base_url(&self) -> &str {
122        &self.config.base_url
123    }
124
125    /// Get the current configuration
126    pub fn config(&self) -> &ClientConfig {
127        &self.config
128    }
129
130    /// Get the underlying reqwest client
131    ///
132    /// This is useful for creating custom requests or integrating with other libraries.
133    pub fn client(&self) -> &Client {
134        &self.client
135    }
136}
137