Skip to main content

cortexai_cloudflare/
http.rs

1//! HTTP client for Cloudflare Workers using fetch API
2
3use crate::error::{CloudflareError, Result};
4use cortexai_llm_client::{HttpRequest, LlmResponse, Provider, ResponseParser};
5use worker::Fetch;
6
7/// HTTP client that uses Cloudflare Workers' fetch API
8pub struct CloudflareHttpClient;
9
10impl CloudflareHttpClient {
11    /// Execute an HTTP request using the Workers fetch API
12    pub async fn execute(request: HttpRequest) -> Result<LlmResponse> {
13        let mut headers = worker::Headers::new();
14        for (key, value) in &request.headers {
15            headers
16                .set(key, value)
17                .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
18        }
19
20        let mut init = worker::RequestInit::new();
21        init.with_method(worker::Method::Post);
22        init.with_headers(headers);
23        init.with_body(Some(wasm_bindgen::JsValue::from_str(&request.body)));
24
25        let worker_request = worker::Request::new_with_init(&request.url, &init)
26            .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
27
28        let mut response = Fetch::Request(worker_request)
29            .send()
30            .await
31            .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
32
33        let status = response.status_code();
34        let body = response
35            .text()
36            .await
37            .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
38
39        if status != 200 {
40            return Err(CloudflareError::ProviderError(format!(
41                "HTTP {}: {}",
42                status, body
43            )));
44        }
45
46        // Parse the response using the shared parser
47        let provider = extract_provider_from_url(&request.url);
48        let llm_response = ResponseParser::parse(provider, &body)?;
49
50        Ok(llm_response)
51    }
52
53    /// Execute a streaming HTTP request
54    pub async fn execute_stream(request: HttpRequest) -> Result<worker::Response> {
55        let mut headers = worker::Headers::new();
56        for (key, value) in &request.headers {
57            headers
58                .set(key, value)
59                .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
60        }
61
62        let mut init = worker::RequestInit::new();
63        init.with_method(worker::Method::Post);
64        init.with_headers(headers);
65        init.with_body(Some(wasm_bindgen::JsValue::from_str(&request.body)));
66
67        let worker_request = worker::Request::new_with_init(&request.url, &init)
68            .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
69
70        let response = Fetch::Request(worker_request)
71            .send()
72            .await
73            .map_err(|e| CloudflareError::HttpError(e.to_string()))?;
74
75        let status = response.status_code();
76        if status != 200 {
77            return Err(CloudflareError::ProviderError(format!(
78                "HTTP {} error",
79                status
80            )));
81        }
82
83        Ok(response)
84    }
85}
86
87/// Extract provider from URL for response parsing
88fn extract_provider_from_url(url: &str) -> Provider {
89    if url.contains("anthropic") {
90        Provider::Anthropic
91    } else if url.contains("openrouter") {
92        Provider::OpenRouter
93    } else {
94        Provider::OpenAI
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_extract_provider() {
104        assert!(matches!(
105            extract_provider_from_url("https://api.anthropic.com/v1/messages"),
106            Provider::Anthropic
107        ));
108        assert!(matches!(
109            extract_provider_from_url("https://openrouter.ai/api/v1/chat"),
110            Provider::OpenRouter
111        ));
112        assert!(matches!(
113            extract_provider_from_url("https://api.openai.com/v1/chat/completions"),
114            Provider::OpenAI
115        ));
116    }
117}