anthropic.rs

  1mod supported_countries;
  2
  3use std::{pin::Pin, str::FromStr};
  4
  5use anyhow::{anyhow, Context as _, Result};
  6use chrono::{DateTime, Utc};
  7use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
  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
440pub async fn extract_tool_args_from_events(
441    tool_name: String,
442    mut events: Pin<Box<dyn Send + Stream<Item = Result<Event>>>>,
443) -> Result<impl Send + Stream<Item = Result<String>>> {
444    let mut tool_use_index = None;
445    while let Some(event) = events.next().await {
446        if let Event::ContentBlockStart {
447            index,
448            content_block: ResponseContent::ToolUse { name, .. },
449        } = event?
450        {
451            if name == tool_name {
452                tool_use_index = Some(index);
453                break;
454            }
455        }
456    }
457
458    let Some(tool_use_index) = tool_use_index else {
459        return Err(anyhow!("tool not used"));
460    };
461
462    Ok(events.filter_map(move |event| {
463        let result = match event {
464            Err(error) => Some(Err(error)),
465            Ok(Event::ContentBlockDelta { index, delta }) => match delta {
466                ContentDelta::TextDelta { .. } => None,
467                ContentDelta::ThinkingDelta { .. } => None,
468                ContentDelta::SignatureDelta { .. } => None,
469                ContentDelta::InputJsonDelta { partial_json } => {
470                    if index == tool_use_index {
471                        Some(Ok(partial_json))
472                    } else {
473                        None
474                    }
475                }
476            },
477            _ => None,
478        };
479
480        async move { result }
481    }))
482}
483
484#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
485#[serde(rename_all = "lowercase")]
486pub enum CacheControlType {
487    Ephemeral,
488}
489
490#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
491pub struct CacheControl {
492    #[serde(rename = "type")]
493    pub cache_type: CacheControlType,
494}
495
496#[derive(Debug, Serialize, Deserialize)]
497pub struct Message {
498    pub role: Role,
499    pub content: Vec<RequestContent>,
500}
501
502#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
503#[serde(rename_all = "lowercase")]
504pub enum Role {
505    User,
506    Assistant,
507}
508
509#[derive(Debug, Serialize, Deserialize)]
510#[serde(tag = "type")]
511pub enum RequestContent {
512    #[serde(rename = "text")]
513    Text {
514        text: String,
515        #[serde(skip_serializing_if = "Option::is_none")]
516        cache_control: Option<CacheControl>,
517    },
518    #[serde(rename = "image")]
519    Image {
520        source: ImageSource,
521        #[serde(skip_serializing_if = "Option::is_none")]
522        cache_control: Option<CacheControl>,
523    },
524    #[serde(rename = "tool_use")]
525    ToolUse {
526        id: String,
527        name: String,
528        input: serde_json::Value,
529        #[serde(skip_serializing_if = "Option::is_none")]
530        cache_control: Option<CacheControl>,
531    },
532    #[serde(rename = "tool_result")]
533    ToolResult {
534        tool_use_id: String,
535        is_error: bool,
536        content: String,
537        #[serde(skip_serializing_if = "Option::is_none")]
538        cache_control: Option<CacheControl>,
539    },
540}
541
542#[derive(Debug, Serialize, Deserialize)]
543#[serde(tag = "type")]
544pub enum ResponseContent {
545    #[serde(rename = "text")]
546    Text { text: String },
547    #[serde(rename = "thinking")]
548    Thinking { thinking: String },
549    #[serde(rename = "redacted_thinking")]
550    RedactedThinking { data: String },
551    #[serde(rename = "tool_use")]
552    ToolUse {
553        id: String,
554        name: String,
555        input: serde_json::Value,
556    },
557}
558
559#[derive(Debug, Serialize, Deserialize)]
560pub struct ImageSource {
561    #[serde(rename = "type")]
562    pub source_type: String,
563    pub media_type: String,
564    pub data: String,
565}
566
567#[derive(Debug, Serialize, Deserialize)]
568pub struct Tool {
569    pub name: String,
570    pub description: String,
571    pub input_schema: serde_json::Value,
572}
573
574#[derive(Debug, Serialize, Deserialize)]
575#[serde(tag = "type", rename_all = "lowercase")]
576pub enum ToolChoice {
577    Auto,
578    Any,
579    Tool { name: String },
580}
581
582#[derive(Debug, Serialize, Deserialize)]
583#[serde(tag = "type", rename_all = "lowercase")]
584pub enum Thinking {
585    Enabled { budget_tokens: Option<u32> },
586}
587
588#[derive(Debug, Serialize, Deserialize)]
589#[serde(untagged)]
590pub enum StringOrContents {
591    String(String),
592    Content(Vec<RequestContent>),
593}
594
595#[derive(Debug, Serialize, Deserialize)]
596pub struct Request {
597    pub model: String,
598    pub max_tokens: u32,
599    pub messages: Vec<Message>,
600    #[serde(default, skip_serializing_if = "Vec::is_empty")]
601    pub tools: Vec<Tool>,
602    #[serde(default, skip_serializing_if = "Option::is_none")]
603    pub thinking: Option<Thinking>,
604    #[serde(default, skip_serializing_if = "Option::is_none")]
605    pub tool_choice: Option<ToolChoice>,
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub system: Option<StringOrContents>,
608    #[serde(default, skip_serializing_if = "Option::is_none")]
609    pub metadata: Option<Metadata>,
610    #[serde(default, skip_serializing_if = "Vec::is_empty")]
611    pub stop_sequences: Vec<String>,
612    #[serde(default, skip_serializing_if = "Option::is_none")]
613    pub temperature: Option<f32>,
614    #[serde(default, skip_serializing_if = "Option::is_none")]
615    pub top_k: Option<u32>,
616    #[serde(default, skip_serializing_if = "Option::is_none")]
617    pub top_p: Option<f32>,
618}
619
620#[derive(Debug, Serialize, Deserialize)]
621struct StreamingRequest {
622    #[serde(flatten)]
623    pub base: Request,
624    pub stream: bool,
625}
626
627#[derive(Debug, Serialize, Deserialize)]
628pub struct Metadata {
629    pub user_id: Option<String>,
630}
631
632#[derive(Debug, Serialize, Deserialize, Default)]
633pub struct Usage {
634    #[serde(default, skip_serializing_if = "Option::is_none")]
635    pub input_tokens: Option<u32>,
636    #[serde(default, skip_serializing_if = "Option::is_none")]
637    pub output_tokens: Option<u32>,
638    #[serde(default, skip_serializing_if = "Option::is_none")]
639    pub cache_creation_input_tokens: Option<u32>,
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub cache_read_input_tokens: Option<u32>,
642}
643
644#[derive(Debug, Serialize, Deserialize)]
645pub struct Response {
646    pub id: String,
647    #[serde(rename = "type")]
648    pub response_type: String,
649    pub role: Role,
650    pub content: Vec<ResponseContent>,
651    pub model: String,
652    #[serde(default, skip_serializing_if = "Option::is_none")]
653    pub stop_reason: Option<String>,
654    #[serde(default, skip_serializing_if = "Option::is_none")]
655    pub stop_sequence: Option<String>,
656    pub usage: Usage,
657}
658
659#[derive(Debug, Serialize, Deserialize)]
660#[serde(tag = "type")]
661pub enum Event {
662    #[serde(rename = "message_start")]
663    MessageStart { message: Response },
664    #[serde(rename = "content_block_start")]
665    ContentBlockStart {
666        index: usize,
667        content_block: ResponseContent,
668    },
669    #[serde(rename = "content_block_delta")]
670    ContentBlockDelta { index: usize, delta: ContentDelta },
671    #[serde(rename = "content_block_stop")]
672    ContentBlockStop { index: usize },
673    #[serde(rename = "message_delta")]
674    MessageDelta { delta: MessageDelta, usage: Usage },
675    #[serde(rename = "message_stop")]
676    MessageStop,
677    #[serde(rename = "ping")]
678    Ping,
679    #[serde(rename = "error")]
680    Error { error: ApiError },
681}
682
683#[derive(Debug, Serialize, Deserialize)]
684#[serde(tag = "type")]
685pub enum ContentDelta {
686    #[serde(rename = "text_delta")]
687    TextDelta { text: String },
688    #[serde(rename = "thinking_delta")]
689    ThinkingDelta { thinking: String },
690    #[serde(rename = "signature_delta")]
691    SignatureDelta { signature: String },
692    #[serde(rename = "input_json_delta")]
693    InputJsonDelta { partial_json: String },
694}
695
696#[derive(Debug, Serialize, Deserialize)]
697pub struct MessageDelta {
698    pub stop_reason: Option<String>,
699    pub stop_sequence: Option<String>,
700}
701
702#[derive(Error, Debug)]
703pub enum AnthropicError {
704    #[error("an error occurred while interacting with the Anthropic API: {error_type}: {message}", error_type = .0.error_type, message = .0.message)]
705    ApiError(ApiError),
706    #[error("{0}")]
707    Other(#[from] anyhow::Error),
708}
709
710#[derive(Debug, Serialize, Deserialize)]
711pub struct ApiError {
712    #[serde(rename = "type")]
713    pub error_type: String,
714    pub message: String,
715}
716
717/// An Anthropic API error code.
718/// <https://docs.anthropic.com/en/api/errors#http-errors>
719#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
720#[strum(serialize_all = "snake_case")]
721pub enum ApiErrorCode {
722    /// 400 - `invalid_request_error`: There was an issue with the format or content of your request.
723    InvalidRequestError,
724    /// 401 - `authentication_error`: There's an issue with your API key.
725    AuthenticationError,
726    /// 403 - `permission_error`: Your API key does not have permission to use the specified resource.
727    PermissionError,
728    /// 404 - `not_found_error`: The requested resource was not found.
729    NotFoundError,
730    /// 413 - `request_too_large`: Request exceeds the maximum allowed number of bytes.
731    RequestTooLarge,
732    /// 429 - `rate_limit_error`: Your account has hit a rate limit.
733    RateLimitError,
734    /// 500 - `api_error`: An unexpected error has occurred internal to Anthropic's systems.
735    ApiError,
736    /// 529 - `overloaded_error`: Anthropic's API is temporarily overloaded.
737    OverloadedError,
738}
739
740impl ApiError {
741    pub fn code(&self) -> Option<ApiErrorCode> {
742        ApiErrorCode::from_str(&self.error_type).ok()
743    }
744
745    pub fn is_rate_limit_error(&self) -> bool {
746        matches!(self.error_type.as_str(), "rate_limit_error")
747    }
748}