1mod supported_countries;
2
3use std::str::FromStr;
4
5use anyhow::{Context as _, Result, anyhow};
6use chrono::{DateTime, Utc};
7use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
8use http_client::http::{HeaderMap, HeaderValue};
9use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
10use serde::{Deserialize, Serialize};
11use strum::{EnumIter, EnumString};
12use thiserror::Error;
13use util::ResultExt as _;
14
15pub use supported_countries::*;
16
17pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
18
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
20#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
21pub struct AnthropicModelCacheConfiguration {
22 pub min_total_token: usize,
23 pub should_speculate: bool,
24 pub max_cache_anchors: usize,
25}
26
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
29pub enum AnthropicModelMode {
30 #[default]
31 Default,
32 Thinking {
33 budget_tokens: Option<u32>,
34 },
35}
36
37#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
38#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
39pub enum Model {
40 #[default]
41 #[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
42 Claude3_5Sonnet,
43 #[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
44 Claude3_7Sonnet,
45 #[serde(
46 rename = "claude-3-7-sonnet-thinking",
47 alias = "claude-3-7-sonnet-thinking-latest"
48 )]
49 Claude3_7SonnetThinking,
50 #[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
51 Claude3_5Haiku,
52 #[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
53 Claude3Opus,
54 #[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
55 Claude3Sonnet,
56 #[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-latest")]
57 Claude3Haiku,
58 #[serde(rename = "custom")]
59 Custom {
60 name: String,
61 max_tokens: usize,
62 /// The name displayed in the UI, such as in the assistant panel model dropdown menu.
63 display_name: Option<String>,
64 /// Override this model with a different Anthropic model for tool calls.
65 tool_override: Option<String>,
66 /// Indicates whether this custom model supports caching.
67 cache_configuration: Option<AnthropicModelCacheConfiguration>,
68 max_output_tokens: Option<u32>,
69 default_temperature: Option<f32>,
70 #[serde(default)]
71 extra_beta_headers: Vec<String>,
72 #[serde(default)]
73 mode: AnthropicModelMode,
74 },
75}
76
77impl Model {
78 pub fn from_id(id: &str) -> Result<Self> {
79 if id.starts_with("claude-3-5-sonnet") {
80 Ok(Self::Claude3_5Sonnet)
81 } else if id.starts_with("claude-3-7-sonnet-thinking") {
82 Ok(Self::Claude3_7SonnetThinking)
83 } else if id.starts_with("claude-3-7-sonnet") {
84 Ok(Self::Claude3_7Sonnet)
85 } else if id.starts_with("claude-3-5-haiku") {
86 Ok(Self::Claude3_5Haiku)
87 } else if id.starts_with("claude-3-opus") {
88 Ok(Self::Claude3Opus)
89 } else if id.starts_with("claude-3-sonnet") {
90 Ok(Self::Claude3Sonnet)
91 } else if id.starts_with("claude-3-haiku") {
92 Ok(Self::Claude3Haiku)
93 } else {
94 Err(anyhow!("invalid model id"))
95 }
96 }
97
98 pub fn id(&self) -> &str {
99 match self {
100 Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
101 Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
102 Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
103 Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
104 Model::Claude3Opus => "claude-3-opus-latest",
105 Model::Claude3Sonnet => "claude-3-sonnet-20240229",
106 Model::Claude3Haiku => "claude-3-haiku-20240307",
107 Self::Custom { name, .. } => name,
108 }
109 }
110
111 /// The id of the model that should be used for making API requests
112 pub fn request_id(&self) -> &str {
113 match self {
114 Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
115 Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
116 Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
117 Model::Claude3Opus => "claude-3-opus-latest",
118 Model::Claude3Sonnet => "claude-3-sonnet-20240229",
119 Model::Claude3Haiku => "claude-3-haiku-20240307",
120 Self::Custom { name, .. } => name,
121 }
122 }
123
124 pub fn display_name(&self) -> &str {
125 match self {
126 Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
127 Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
128 Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
129 Self::Claude3_5Haiku => "Claude 3.5 Haiku",
130 Self::Claude3Opus => "Claude 3 Opus",
131 Self::Claude3Sonnet => "Claude 3 Sonnet",
132 Self::Claude3Haiku => "Claude 3 Haiku",
133 Self::Custom {
134 name, display_name, ..
135 } => display_name.as_ref().unwrap_or(name),
136 }
137 }
138
139 pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
140 match self {
141 Self::Claude3_5Sonnet
142 | Self::Claude3_5Haiku
143 | Self::Claude3_7Sonnet
144 | Self::Claude3_7SonnetThinking
145 | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
146 min_total_token: 2_048,
147 should_speculate: true,
148 max_cache_anchors: 4,
149 }),
150 Self::Custom {
151 cache_configuration,
152 ..
153 } => cache_configuration.clone(),
154 _ => None,
155 }
156 }
157
158 pub fn max_token_count(&self) -> usize {
159 match self {
160 Self::Claude3_5Sonnet
161 | Self::Claude3_5Haiku
162 | Self::Claude3_7Sonnet
163 | Self::Claude3_7SonnetThinking
164 | Self::Claude3Opus
165 | Self::Claude3Sonnet
166 | Self::Claude3Haiku => 200_000,
167 Self::Custom { max_tokens, .. } => *max_tokens,
168 }
169 }
170
171 pub fn max_output_tokens(&self) -> u32 {
172 match self {
173 Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
174 Self::Claude3_5Sonnet
175 | Self::Claude3_7Sonnet
176 | Self::Claude3_7SonnetThinking
177 | Self::Claude3_5Haiku => 8_192,
178 Self::Custom {
179 max_output_tokens, ..
180 } => max_output_tokens.unwrap_or(4_096),
181 }
182 }
183
184 pub fn default_temperature(&self) -> f32 {
185 match self {
186 Self::Claude3_5Sonnet
187 | Self::Claude3_7Sonnet
188 | Self::Claude3_7SonnetThinking
189 | Self::Claude3_5Haiku
190 | Self::Claude3Opus
191 | Self::Claude3Sonnet
192 | Self::Claude3Haiku => 1.0,
193 Self::Custom {
194 default_temperature,
195 ..
196 } => default_temperature.unwrap_or(1.0),
197 }
198 }
199
200 pub fn mode(&self) -> AnthropicModelMode {
201 match self {
202 Self::Claude3_5Sonnet
203 | Self::Claude3_7Sonnet
204 | Self::Claude3_5Haiku
205 | Self::Claude3Opus
206 | Self::Claude3Sonnet
207 | Self::Claude3Haiku => AnthropicModelMode::Default,
208 Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
209 budget_tokens: Some(4_096),
210 },
211 Self::Custom { mode, .. } => mode.clone(),
212 }
213 }
214
215 pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
216
217 pub fn beta_headers(&self) -> String {
218 let mut headers = Self::DEFAULT_BETA_HEADERS
219 .into_iter()
220 .map(|header| header.to_string())
221 .collect::<Vec<_>>();
222
223 if let Self::Custom {
224 extra_beta_headers, ..
225 } = self
226 {
227 headers.extend(
228 extra_beta_headers
229 .iter()
230 .filter(|header| !header.trim().is_empty())
231 .cloned(),
232 );
233 }
234
235 headers.join(",")
236 }
237
238 pub fn tool_model_id(&self) -> &str {
239 if let Self::Custom {
240 tool_override: Some(tool_override),
241 ..
242 } = self
243 {
244 tool_override
245 } else {
246 self.request_id()
247 }
248 }
249}
250
251pub async fn complete(
252 client: &dyn HttpClient,
253 api_url: &str,
254 api_key: &str,
255 request: Request,
256) -> Result<Response, AnthropicError> {
257 let uri = format!("{api_url}/v1/messages");
258 let beta_headers = Model::from_id(&request.model)
259 .map(|model| model.beta_headers())
260 .unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
261 let request_builder = HttpRequest::builder()
262 .method(Method::POST)
263 .uri(uri)
264 .header("Anthropic-Version", "2023-06-01")
265 .header("Anthropic-Beta", beta_headers)
266 .header("X-Api-Key", api_key)
267 .header("Content-Type", "application/json");
268
269 let serialized_request =
270 serde_json::to_string(&request).context("failed to serialize request")?;
271 let request = request_builder
272 .body(AsyncBody::from(serialized_request))
273 .context("failed to construct request body")?;
274
275 let mut response = client
276 .send(request)
277 .await
278 .context("failed to send request to Anthropic")?;
279 if response.status().is_success() {
280 let mut body = Vec::new();
281 response
282 .body_mut()
283 .read_to_end(&mut body)
284 .await
285 .context("failed to read response body")?;
286 let response_message: Response =
287 serde_json::from_slice(&body).context("failed to deserialize response body")?;
288 Ok(response_message)
289 } else {
290 let mut body = Vec::new();
291 response
292 .body_mut()
293 .read_to_end(&mut body)
294 .await
295 .context("failed to read response body")?;
296 let body_str =
297 std::str::from_utf8(&body).context("failed to parse response body as UTF-8")?;
298 Err(AnthropicError::Other(anyhow!(
299 "Failed to connect to API: {} {}",
300 response.status(),
301 body_str
302 )))
303 }
304}
305
306pub async fn stream_completion(
307 client: &dyn HttpClient,
308 api_url: &str,
309 api_key: &str,
310 request: Request,
311) -> Result<BoxStream<'static, Result<Event, AnthropicError>>, AnthropicError> {
312 stream_completion_with_rate_limit_info(client, api_url, api_key, request)
313 .await
314 .map(|output| output.0)
315}
316
317/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
318#[derive(Debug)]
319pub struct RateLimitInfo {
320 pub requests_limit: usize,
321 pub requests_remaining: usize,
322 pub requests_reset: DateTime<Utc>,
323 pub tokens_limit: usize,
324 pub tokens_remaining: usize,
325 pub tokens_reset: DateTime<Utc>,
326}
327
328impl RateLimitInfo {
329 fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
330 let tokens_limit = get_header("anthropic-ratelimit-tokens-limit", headers)?.parse()?;
331 let requests_limit = get_header("anthropic-ratelimit-requests-limit", headers)?.parse()?;
332 let tokens_remaining =
333 get_header("anthropic-ratelimit-tokens-remaining", headers)?.parse()?;
334 let requests_remaining =
335 get_header("anthropic-ratelimit-requests-remaining", headers)?.parse()?;
336 let requests_reset = get_header("anthropic-ratelimit-requests-reset", headers)?;
337 let tokens_reset = get_header("anthropic-ratelimit-tokens-reset", headers)?;
338 let requests_reset = DateTime::parse_from_rfc3339(requests_reset)?.to_utc();
339 let tokens_reset = DateTime::parse_from_rfc3339(tokens_reset)?.to_utc();
340
341 Ok(Self {
342 requests_limit,
343 tokens_limit,
344 requests_remaining,
345 tokens_remaining,
346 requests_reset,
347 tokens_reset,
348 })
349 }
350}
351
352fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> Result<&'a str, anyhow::Error> {
353 Ok(headers
354 .get(key)
355 .ok_or_else(|| anyhow!("missing header `{key}`"))?
356 .to_str()?)
357}
358
359pub async fn stream_completion_with_rate_limit_info(
360 client: &dyn HttpClient,
361 api_url: &str,
362 api_key: &str,
363 request: Request,
364) -> Result<
365 (
366 BoxStream<'static, Result<Event, AnthropicError>>,
367 Option<RateLimitInfo>,
368 ),
369 AnthropicError,
370> {
371 let request = StreamingRequest {
372 base: request,
373 stream: true,
374 };
375 let uri = format!("{api_url}/v1/messages");
376 let beta_headers = Model::from_id(&request.base.model)
377 .map(|model| model.beta_headers())
378 .unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
379 let request_builder = HttpRequest::builder()
380 .method(Method::POST)
381 .uri(uri)
382 .header("Anthropic-Version", "2023-06-01")
383 .header("Anthropic-Beta", beta_headers)
384 .header("X-Api-Key", api_key)
385 .header("Content-Type", "application/json");
386 let serialized_request =
387 serde_json::to_string(&request).context("failed to serialize request")?;
388 let request = request_builder
389 .body(AsyncBody::from(serialized_request))
390 .context("failed to construct request body")?;
391
392 let mut response = client
393 .send(request)
394 .await
395 .context("failed to send request to Anthropic")?;
396 if response.status().is_success() {
397 let rate_limits = RateLimitInfo::from_headers(response.headers());
398 let reader = BufReader::new(response.into_body());
399 let stream = reader
400 .lines()
401 .filter_map(|line| async move {
402 match line {
403 Ok(line) => {
404 let line = line.strip_prefix("data: ")?;
405 match serde_json::from_str(line) {
406 Ok(response) => Some(Ok(response)),
407 Err(error) => Some(Err(AnthropicError::Other(anyhow!(error)))),
408 }
409 }
410 Err(error) => Some(Err(AnthropicError::Other(anyhow!(error)))),
411 }
412 })
413 .boxed();
414 Ok((stream, rate_limits.log_err()))
415 } else {
416 let mut body = Vec::new();
417 response
418 .body_mut()
419 .read_to_end(&mut body)
420 .await
421 .context("failed to read response body")?;
422
423 let body_str =
424 std::str::from_utf8(&body).context("failed to parse response body as UTF-8")?;
425
426 match serde_json::from_str::<Event>(body_str) {
427 Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
428 Ok(_) => Err(AnthropicError::Other(anyhow!(
429 "Unexpected success response while expecting an error: '{body_str}'",
430 ))),
431 Err(_) => Err(AnthropicError::Other(anyhow!(
432 "Failed to connect to API: {} {}",
433 response.status(),
434 body_str,
435 ))),
436 }
437 }
438}
439
440#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
441#[serde(rename_all = "lowercase")]
442pub enum CacheControlType {
443 Ephemeral,
444}
445
446#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
447pub struct CacheControl {
448 #[serde(rename = "type")]
449 pub cache_type: CacheControlType,
450}
451
452#[derive(Debug, Serialize, Deserialize)]
453pub struct Message {
454 pub role: Role,
455 pub content: Vec<RequestContent>,
456}
457
458#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
459#[serde(rename_all = "lowercase")]
460pub enum Role {
461 User,
462 Assistant,
463}
464
465#[derive(Debug, Serialize, Deserialize)]
466#[serde(tag = "type")]
467pub enum RequestContent {
468 #[serde(rename = "text")]
469 Text {
470 text: String,
471 #[serde(skip_serializing_if = "Option::is_none")]
472 cache_control: Option<CacheControl>,
473 },
474 #[serde(rename = "image")]
475 Image {
476 source: ImageSource,
477 #[serde(skip_serializing_if = "Option::is_none")]
478 cache_control: Option<CacheControl>,
479 },
480 #[serde(rename = "tool_use")]
481 ToolUse {
482 id: String,
483 name: String,
484 input: serde_json::Value,
485 #[serde(skip_serializing_if = "Option::is_none")]
486 cache_control: Option<CacheControl>,
487 },
488 #[serde(rename = "tool_result")]
489 ToolResult {
490 tool_use_id: String,
491 is_error: bool,
492 content: String,
493 #[serde(skip_serializing_if = "Option::is_none")]
494 cache_control: Option<CacheControl>,
495 },
496}
497
498#[derive(Debug, Serialize, Deserialize)]
499#[serde(tag = "type")]
500pub enum ResponseContent {
501 #[serde(rename = "text")]
502 Text { text: String },
503 #[serde(rename = "thinking")]
504 Thinking { thinking: String },
505 #[serde(rename = "redacted_thinking")]
506 RedactedThinking { data: String },
507 #[serde(rename = "tool_use")]
508 ToolUse {
509 id: String,
510 name: String,
511 input: serde_json::Value,
512 },
513}
514
515#[derive(Debug, Serialize, Deserialize)]
516pub struct ImageSource {
517 #[serde(rename = "type")]
518 pub source_type: String,
519 pub media_type: String,
520 pub data: String,
521}
522
523#[derive(Debug, Serialize, Deserialize)]
524pub struct Tool {
525 pub name: String,
526 pub description: String,
527 pub input_schema: serde_json::Value,
528}
529
530#[derive(Debug, Serialize, Deserialize)]
531#[serde(tag = "type", rename_all = "lowercase")]
532pub enum ToolChoice {
533 Auto,
534 Any,
535 Tool { name: String },
536}
537
538#[derive(Debug, Serialize, Deserialize)]
539#[serde(tag = "type", rename_all = "lowercase")]
540pub enum Thinking {
541 Enabled { budget_tokens: Option<u32> },
542}
543
544#[derive(Debug, Serialize, Deserialize)]
545#[serde(untagged)]
546pub enum StringOrContents {
547 String(String),
548 Content(Vec<RequestContent>),
549}
550
551#[derive(Debug, Serialize, Deserialize)]
552pub struct Request {
553 pub model: String,
554 pub max_tokens: u32,
555 pub messages: Vec<Message>,
556 #[serde(default, skip_serializing_if = "Vec::is_empty")]
557 pub tools: Vec<Tool>,
558 #[serde(default, skip_serializing_if = "Option::is_none")]
559 pub thinking: Option<Thinking>,
560 #[serde(default, skip_serializing_if = "Option::is_none")]
561 pub tool_choice: Option<ToolChoice>,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
563 pub system: Option<StringOrContents>,
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub metadata: Option<Metadata>,
566 #[serde(default, skip_serializing_if = "Vec::is_empty")]
567 pub stop_sequences: Vec<String>,
568 #[serde(default, skip_serializing_if = "Option::is_none")]
569 pub temperature: Option<f32>,
570 #[serde(default, skip_serializing_if = "Option::is_none")]
571 pub top_k: Option<u32>,
572 #[serde(default, skip_serializing_if = "Option::is_none")]
573 pub top_p: Option<f32>,
574}
575
576#[derive(Debug, Serialize, Deserialize)]
577struct StreamingRequest {
578 #[serde(flatten)]
579 pub base: Request,
580 pub stream: bool,
581}
582
583#[derive(Debug, Serialize, Deserialize)]
584pub struct Metadata {
585 pub user_id: Option<String>,
586}
587
588#[derive(Debug, Serialize, Deserialize, Default)]
589pub struct Usage {
590 #[serde(default, skip_serializing_if = "Option::is_none")]
591 pub input_tokens: Option<u32>,
592 #[serde(default, skip_serializing_if = "Option::is_none")]
593 pub output_tokens: Option<u32>,
594 #[serde(default, skip_serializing_if = "Option::is_none")]
595 pub cache_creation_input_tokens: Option<u32>,
596 #[serde(default, skip_serializing_if = "Option::is_none")]
597 pub cache_read_input_tokens: Option<u32>,
598}
599
600#[derive(Debug, Serialize, Deserialize)]
601pub struct Response {
602 pub id: String,
603 #[serde(rename = "type")]
604 pub response_type: String,
605 pub role: Role,
606 pub content: Vec<ResponseContent>,
607 pub model: String,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub stop_reason: Option<String>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub stop_sequence: Option<String>,
612 pub usage: Usage,
613}
614
615#[derive(Debug, Serialize, Deserialize)]
616#[serde(tag = "type")]
617pub enum Event {
618 #[serde(rename = "message_start")]
619 MessageStart { message: Response },
620 #[serde(rename = "content_block_start")]
621 ContentBlockStart {
622 index: usize,
623 content_block: ResponseContent,
624 },
625 #[serde(rename = "content_block_delta")]
626 ContentBlockDelta { index: usize, delta: ContentDelta },
627 #[serde(rename = "content_block_stop")]
628 ContentBlockStop { index: usize },
629 #[serde(rename = "message_delta")]
630 MessageDelta { delta: MessageDelta, usage: Usage },
631 #[serde(rename = "message_stop")]
632 MessageStop,
633 #[serde(rename = "ping")]
634 Ping,
635 #[serde(rename = "error")]
636 Error { error: ApiError },
637}
638
639#[derive(Debug, Serialize, Deserialize)]
640#[serde(tag = "type")]
641pub enum ContentDelta {
642 #[serde(rename = "text_delta")]
643 TextDelta { text: String },
644 #[serde(rename = "thinking_delta")]
645 ThinkingDelta { thinking: String },
646 #[serde(rename = "signature_delta")]
647 SignatureDelta { signature: String },
648 #[serde(rename = "input_json_delta")]
649 InputJsonDelta { partial_json: String },
650}
651
652#[derive(Debug, Serialize, Deserialize)]
653pub struct MessageDelta {
654 pub stop_reason: Option<String>,
655 pub stop_sequence: Option<String>,
656}
657
658#[derive(Error, Debug)]
659pub enum AnthropicError {
660 #[error("an error occurred while interacting with the Anthropic API: {error_type}: {message}", error_type = .0.error_type, message = .0.message)]
661 ApiError(ApiError),
662 #[error("{0}")]
663 Other(#[from] anyhow::Error),
664}
665
666#[derive(Debug, Serialize, Deserialize)]
667pub struct ApiError {
668 #[serde(rename = "type")]
669 pub error_type: String,
670 pub message: String,
671}
672
673/// An Anthropic API error code.
674/// <https://docs.anthropic.com/en/api/errors#http-errors>
675#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
676#[strum(serialize_all = "snake_case")]
677pub enum ApiErrorCode {
678 /// 400 - `invalid_request_error`: There was an issue with the format or content of your request.
679 InvalidRequestError,
680 /// 401 - `authentication_error`: There's an issue with your API key.
681 AuthenticationError,
682 /// 403 - `permission_error`: Your API key does not have permission to use the specified resource.
683 PermissionError,
684 /// 404 - `not_found_error`: The requested resource was not found.
685 NotFoundError,
686 /// 413 - `request_too_large`: Request exceeds the maximum allowed number of bytes.
687 RequestTooLarge,
688 /// 429 - `rate_limit_error`: Your account has hit a rate limit.
689 RateLimitError,
690 /// 500 - `api_error`: An unexpected error has occurred internal to Anthropic's systems.
691 ApiError,
692 /// 529 - `overloaded_error`: Anthropic's API is temporarily overloaded.
693 OverloadedError,
694}
695
696impl ApiError {
697 pub fn code(&self) -> Option<ApiErrorCode> {
698 ApiErrorCode::from_str(&self.error_type).ok()
699 }
700
701 pub fn is_rate_limit_error(&self) -> bool {
702 matches!(self.error_type.as_str(), "rate_limit_error")
703 }
704}