1use crate::apis::api_client::{ApiClient, CompletionOptions, Message, ToolCall, ToolResult};
2use crate::app::logger::{format_log_with_color, LogLevel};
3use crate::errors::AppError;
4use anyhow::{Context, Result};
5use async_trait::async_trait;
6use rand;
7use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
8use reqwest::Client as ReqwestClient;
9use reqwest::Response;
10use serde::{Deserialize, Serialize};
11use serde_json::{self, json, Value};
12use std::env;
13use std::time::Duration;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17struct AnthropicMessage {
18 role: String,
19 content: Vec<AnthropicContent>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(tag = "type")]
24enum AnthropicContent {
25 #[serde(rename = "text")]
26 Text { text: String },
27
28 #[serde(rename = "tool_use")]
29 ToolUse {
30 id: String,
31 name: String,
32 input: Value,
33 },
34
35 #[serde(rename = "tool_result")]
36 ToolResult {
37 #[serde(rename = "tool_use_id")]
38 tool_call_id: String,
39 content: String,
40 },
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
46struct AnthropicTool {
47 name: String,
48 description: Option<String>,
49 #[serde(rename = "input_schema")]
50 schema: Value,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54struct AnthropicResponseFormat {
55 #[serde(rename = "type")]
56 format_type: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 schema: Option<Value>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62struct AnthropicToolChoice {
63 #[serde(rename = "type")]
64 choice_type: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68struct AnthropicRequest {
69 model: String,
70 messages: Vec<AnthropicMessage>,
71 max_tokens: usize,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 system: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 temperature: Option<f32>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 top_p: Option<f32>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 tools: Option<Vec<AnthropicTool>>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 tool_choice: Option<AnthropicToolChoice>,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 response_format: Option<AnthropicResponseFormat>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87struct AnthropicResponse {
88 id: String,
89 model: String,
90 role: String,
91 content: Vec<AnthropicContent>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 usage: Option<Value>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 type_field: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 stop_reason: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 stop_sequence: Option<String>,
100}
101
102pub struct AnthropicClient {
103 client: ReqwestClient,
104 model: String,
105 api_base: String,
106}
107
108impl AnthropicClient {
109 async fn send_request_with_retry<T: serde::Serialize + Clone>(
111 &self,
112 request: &T,
113 ) -> Result<Response> {
114 let mut retries = 0;
116 let max_retries = 3; let mut delay_ms = 1000; loop {
120 let result = self.client.post(&self.api_base).json(request).send().await;
121
122 match result {
123 Ok(resp) => {
124 if resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS
126 || resp.status().as_u16() == 529
127 {
128 if retries >= max_retries {
129 return Ok(resp);
131 }
132
133 let retry_after = resp
135 .headers()
136 .get("retry-after")
137 .and_then(|val| val.to_str().ok())
138 .and_then(|val| val.parse::<u64>().ok())
139 .unwrap_or(delay_ms);
140
141 let error_body = resp.text().await.unwrap_or_default();
143 eprintln!(
144 "{}",
145 format_log_with_color(
146 LogLevel::Warning,
147 &format!(
148 "Anthropic API rate limited or overloaded: {}",
149 error_body
150 )
151 )
152 );
153
154 let jitter = rand::random::<u64>() % 500;
156 let sleep_duration = Duration::from_millis(retry_after + jitter);
157
158 tokio::time::sleep(sleep_duration).await;
160
161 delay_ms = (delay_ms * 2).min(10000); retries += 1;
164 continue;
165 }
166
167 return Ok(resp);
169 }
170 Err(e) => {
171 if retries >= max_retries {
173 return Err(AppError::NetworkError(format!(
174 "Failed to send request to Anthropic after {} retries: {}",
175 retries, e
176 ))
177 .into());
178 }
179
180 let jitter = rand::random::<u64>() % 500;
182 let sleep_duration = Duration::from_millis(delay_ms + jitter);
183 tokio::time::sleep(sleep_duration).await;
184
185 delay_ms = (delay_ms * 2).min(10000); retries += 1;
188 }
189 }
190 }
191 }
192
193 pub fn new(model: Option<String>) -> Result<Self> {
194 let api_key = env::var("ANTHROPIC_API_KEY")
196 .context("ANTHROPIC_API_KEY environment variable not set")?;
197
198 Self::with_api_key(api_key, model)
199 }
200
201 pub fn with_api_key(api_key: String, model: Option<String>) -> Result<Self> {
202 let mut headers = HeaderMap::new();
204 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
205 headers.insert(
206 AUTHORIZATION,
207 HeaderValue::from_str(&format!("Bearer {}", api_key))?,
208 );
209 headers.insert("anthropic-version", HeaderValue::from_static("2023-06-01"));
210 headers.insert("x-api-key", HeaderValue::from_str(&api_key)?);
211
212 let client = ReqwestClient::builder().default_headers(headers).build()?;
213
214 let model = model.unwrap_or_else(|| "claude-3-7-sonnet-20250219".to_string());
216
217 Ok(Self {
218 client,
219 model,
220 api_base: "https://api.anthropic.com/v1/messages".to_string(),
221 })
222 }
223
224 fn extract_system_message(&self, messages: &[Message]) -> Option<String> {
225 messages
226 .iter()
227 .find(|msg| msg.role == "system")
228 .map(|system_msg| system_msg.content.clone())
229 }
230
231 fn convert_messages(&self, messages: Vec<Message>) -> Vec<AnthropicMessage> {
232 messages
233 .into_iter()
234 .filter(|msg| msg.role != "system") .map(|msg| AnthropicMessage {
236 role: msg.role,
237 content: vec![AnthropicContent::Text { text: msg.content }],
238 })
239 .collect()
240 }
241
242 fn convert_tool_definitions(
243 &self,
244 tools: Vec<crate::apis::api_client::ToolDefinition>,
245 ) -> Vec<AnthropicTool> {
246 tools
247 .into_iter()
248 .map(|tool| {
249 let mut schema = serde_json::Map::new();
251 schema.insert(
252 "$schema".to_string(),
253 json!("https://json-schema.org/draft/2020-12/schema"),
254 );
255 schema.insert("type".to_string(), json!("object"));
256
257 if let Value::Object(params) = &tool.parameters {
259 if let Some(props) = params.get("properties") {
260 schema.insert("properties".to_string(), props.clone());
261 }
262
263 if let Some(required) = params.get("required") {
264 schema.insert("required".to_string(), required.clone());
265 }
266 }
267
268 AnthropicTool {
269 name: tool.name,
270 description: Some(tool.description),
271 schema: Value::Object(schema),
272 }
273 })
274 .collect()
275 }
276}
277
278#[async_trait]
279impl ApiClient for AnthropicClient {
280 async fn complete(&self, messages: Vec<Message>, options: CompletionOptions) -> Result<String> {
281 let system_message = self.extract_system_message(&messages);
283 let converted_messages = self.convert_messages(messages);
284
285 let max_tokens = options.max_tokens.unwrap_or(2048) as usize;
286
287 let mut request = AnthropicRequest {
288 model: self.model.clone(),
289 messages: converted_messages,
290 max_tokens,
291 system: system_message,
292 temperature: options.temperature,
293 top_p: options.top_p,
294 tools: None,
295 tool_choice: None,
296 response_format: None,
297 };
298
299 if let Some(json_schema) = &options.json_schema {
301 request.response_format = Some(AnthropicResponseFormat {
302 format_type: "json".to_string(),
303 schema: serde_json::from_str(json_schema).ok(),
304 });
305 }
306
307 let response = self.send_request_with_retry(&request).await?;
309
310 if !response.status().is_success() {
311 let status = response.status();
312 let error_text = response
313 .text()
314 .await
315 .unwrap_or_else(|_| "Unknown error".to_string());
316 return Err(AppError::NetworkError(format!(
317 "Anthropic API error: {} - {}",
318 status, error_text
319 ))
320 .into());
321 }
322
323 let response_text = response.text().await.map_err(|e| {
325 let error_msg = format!("Failed to get response text: {}", e);
326 eprintln!("{}", format_log_with_color(LogLevel::Error, &error_msg));
327 AppError::NetworkError(error_msg)
328 })?;
329
330 eprintln!(
332 "{}",
333 format_log_with_color(
334 LogLevel::Debug,
335 &format!(
336 "Anthropic API response received: {} bytes",
337 response_text.len()
338 )
339 )
340 );
341
342 let anthropic_response: AnthropicResponse =
344 serde_json::from_str(&response_text).map_err(|e| {
345 let error_msg = format!("Failed to parse Anthropic response: {}", e);
346 eprintln!("{}", format_log_with_color(LogLevel::Error, &error_msg));
347 AppError::Other(error_msg)
348 })?;
349
350 let mut text_content = String::new();
352
353 for content_item in &anthropic_response.content {
355 if let AnthropicContent::Text { text } = content_item {
356 text_content = text.clone();
357 break;
358 }
359 }
360
361 if text_content.is_empty() {
363 let error_msg = "No text content in Anthropic response".to_string();
364 eprintln!("{}", format_log_with_color(LogLevel::Error, &error_msg));
365 return Err(AppError::LLMError(error_msg).into());
366 }
367
368 let content = text_content;
369
370 Ok(content)
371 }
372
373 async fn complete_with_tools(
374 &self,
375 messages: Vec<Message>,
376 options: CompletionOptions,
377 tool_results: Option<Vec<ToolResult>>,
378 ) -> Result<(String, Option<Vec<ToolCall>>)> {
379 let system_message = self.extract_system_message(&messages);
381 let mut converted_messages = self.convert_messages(messages);
382
383 if let Some(results) = tool_results {
385 for result in results {
387 let tool_call_id = if result.tool_call_id.is_empty() {
389 format!("tool-{}", rand::random::<u64>())
391 } else {
392 result.tool_call_id.clone()
393 };
394
395 let tool_use_msg = AnthropicMessage {
397 role: "assistant".to_string(),
398 content: vec![AnthropicContent::ToolUse {
399 id: tool_call_id.clone(),
400 name: "tool".to_string(), input: json!({}), }],
403 };
404
405 let tool_result_msg = AnthropicMessage {
407 role: "user".to_string(),
408 content: vec![AnthropicContent::ToolResult {
409 tool_call_id: tool_call_id.clone(),
410 content: result.output.clone(),
411 }],
412 };
413
414 converted_messages.push(tool_use_msg);
416 converted_messages.push(tool_result_msg);
417 }
418 }
419
420 let max_tokens = options.max_tokens.unwrap_or(2048) as usize;
421
422 let mut request = AnthropicRequest {
423 model: self.model.clone(),
424 messages: converted_messages,
425 max_tokens,
426 system: system_message,
427 temperature: options.temperature,
428 top_p: options.top_p,
429 tools: None,
430 tool_choice: None,
431 response_format: None,
432 };
433
434 if let Some(json_schema) = &options.json_schema {
437 if options.tools.is_none() {
439 request.response_format = Some(AnthropicResponseFormat {
440 format_type: "json".to_string(),
441 schema: serde_json::from_str(json_schema).ok(),
442 });
443 }
444 }
445
446 if let Some(tools) = options.tools {
448 let converted_tools = self.convert_tool_definitions(tools);
449 request.tools = Some(converted_tools);
450
451 request.tool_choice = Some(AnthropicToolChoice {
453 choice_type: if options.require_tool_use {
454 "required".to_string()
455 } else {
456 "auto".to_string()
457 },
458 });
459 }
460
461 let response = self.send_request_with_retry(&request).await?;
463
464 if !response.status().is_success() {
465 let status = response.status();
466 let error_text = response
467 .text()
468 .await
469 .unwrap_or_else(|_| "Unknown error".to_string());
470 return Err(AppError::NetworkError(format!(
471 "Anthropic API error: {} - {}",
472 status, error_text
473 ))
474 .into());
475 }
476
477 let response_text = response.text().await.map_err(|e| {
479 let error_msg = format!("Failed to get response text: {}", e);
480 eprintln!("{}", format_log_with_color(LogLevel::Error, &error_msg));
481 AppError::NetworkError(error_msg)
482 })?;
483
484 eprintln!(
486 "{}",
487 format_log_with_color(
488 LogLevel::Debug,
489 &format!(
490 "Anthropic API response received: {} bytes",
491 response_text.len()
492 )
493 )
494 );
495
496 let anthropic_response: AnthropicResponse =
498 serde_json::from_str(&response_text).map_err(|e| {
499 let error_msg = format!("Failed to parse Anthropic response: {}", e);
500 eprintln!("{}", format_log_with_color(LogLevel::Error, &error_msg));
501 AppError::Other(error_msg)
502 })?;
503
504 let mut tool_calls_vec = Vec::new();
506 let mut text_content = String::new();
507
508 for content_item in &anthropic_response.content {
510 match content_item {
511 AnthropicContent::Text { text } => {
512 if text_content.is_empty() {
514 text_content = text.clone();
515 }
516 }
517 AnthropicContent::ToolUse { name, input, .. } => {
518 tool_calls_vec.push(crate::apis::api_client::ToolCall {
520 id: None, name: name.clone(),
522 arguments: input.clone(),
523 });
524 }
525 AnthropicContent::ToolResult { .. } => {
526 }
528 }
529 }
530
531 let content = if text_content.is_empty() {
533 String::new()
534 } else {
535 text_content
536 };
537
538 let tool_calls = if tool_calls_vec.is_empty() {
543 None
544 } else {
545 Some(tool_calls_vec)
546 };
547
548 Ok((content, tool_calls))
549 }
550}