claude_client/
lib.rs

1pub mod claude {
2    use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
3    use serde::{Deserialize, Serialize};
4    use std::env;
5
6    const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1";
7    const DEFAULT_MODEL: &str = "claude-3-7-sonnet-20250219";
8
9    #[derive(Debug, Serialize)]
10    pub struct Message {
11        pub role: String,
12        pub content: String,
13    }
14
15    #[derive(Debug, Serialize)]
16    pub struct ClaudeRequest {
17        pub model: String,
18        pub messages: Vec<Message>,
19        pub max_tokens: u32,
20        pub system: Option<String>,
21    }
22
23    #[derive(Debug, Deserialize)]
24    pub struct ClaudeResponse {
25        pub content: Vec<Content>,
26        pub role: String,
27        pub model: String,
28        pub id: String,
29    }
30
31    #[derive(Debug, Deserialize)]
32    pub struct Content {
33        pub text: String,
34        #[serde(rename = "type")]
35        pub content_type: String,
36    }
37
38    #[derive(Debug, Deserialize)]
39    pub struct Model {
40        #[serde(rename = "type")]
41        pub model_type: String,
42        pub id: String,
43        pub display_name: String,
44        pub created_at: String,
45    }
46
47    #[derive(Debug, Deserialize)]
48    pub struct ListModelsResponse {
49        pub data: Vec<Model>,
50        pub has_more: bool,
51        pub first_id: String,
52        pub last_id: String,
53    }
54
55    pub struct ClaudeClient {
56        client: reqwest::Client,
57        api_key: String,
58    }
59
60    impl ClaudeClient {
61        pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
62            let api_key = env::var("ANTHROPIC_API_KEY")
63                .map_err(|_| "ANTHROPIC_API_KEY environment variable not set")?;
64
65            Ok(Self {
66                client: reqwest::Client::new(),
67                api_key,
68            })
69        }
70
71        pub async fn send_message(
72            &self,
73            model: Option<&str>,
74            system_prompt: &str,
75            user_message: &str,
76        ) -> Result<String, Box<dyn std::error::Error>> {
77            let model = model.unwrap_or(DEFAULT_MODEL);
78            let mut headers = HeaderMap::new();
79            headers.insert(
80                "x-api-key",
81                HeaderValue::from_str(&self.api_key)
82                    .map_err(|e| format!("Invalid API key format: {}", e))?,
83            );
84            headers.insert(
85                "anthropic-api-key",
86                HeaderValue::from_str(&self.api_key)
87                    .map_err(|e| format!("Invalid API key format: {}", e))?,
88            );
89            headers.insert(
90                "anthropic-version",
91                HeaderValue::from_static("2023-06-01"),
92            );
93            headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
94            headers.insert(
95                CONTENT_TYPE,
96                HeaderValue::from_static("application/json"),
97            );
98
99            let request = ClaudeRequest {
100                model: model.to_string(),
101                messages: vec![
102                    Message {
103                        role: "user".to_string(),
104                        content: user_message.to_string(),
105                    },
106                ],
107                max_tokens: 4000,
108                system: Some(system_prompt.to_string()),
109            };
110
111            let raw_response = self
112                .client
113                .post(format!("{}/messages", ANTHROPIC_API_URL))
114                .headers(headers)
115                .json(&request)
116                .send()
117                .await?
118                .text()
119                .await?;
120            println!("Raw response: {}", raw_response);
121            let response: ClaudeResponse = serde_json::from_str(&raw_response)?;
122
123            Ok(response.content.into_iter()
124                .filter(|c| c.content_type == "text")
125                .map(|c| c.text)
126                .collect::<Vec<_>>()
127                .join(""))
128        }
129
130        pub async fn list_models(&self) -> Result<Vec<Model>, Box<dyn std::error::Error>> {
131            let mut headers = HeaderMap::new();
132            headers.insert(
133                "x-api-key",
134                HeaderValue::from_str(&self.api_key)
135                    .map_err(|e| format!("Invalid API key format: {}", e))?,
136            );
137            headers.insert(
138                "anthropic-api-key",
139                HeaderValue::from_str(&self.api_key)
140                    .map_err(|e| format!("Invalid API key format: {}", e))?,
141            );
142            headers.insert(
143                "anthropic-version",
144                HeaderValue::from_static("2023-06-01"),
145            );
146            headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
147
148            let raw_response = self
149                .client
150                .get(format!("{}/models", ANTHROPIC_API_URL))
151                .headers(headers)
152                .send()
153                .await?
154                .text()
155                .await?;
156            println!("Raw response: {}", raw_response);
157            let response: ListModelsResponse = serde_json::from_str(&raw_response)?;
158
159            Ok(response.data)
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests;