anthropic.rs

  1use std::io;
  2use std::str::FromStr;
  3use std::time::Duration;
  4
  5use anyhow::{Context as _, Result, anyhow};
  6use chrono::{DateTime, Utc};
  7use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
  8use http_client::http::{self, HeaderMap, HeaderValue};
  9use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode};
 10use serde::{Deserialize, Serialize};
 11pub use settings::{AnthropicAvailableModel as AvailableModel, ModelMode};
 12use strum::{EnumIter, EnumString};
 13use thiserror::Error;
 14
 15pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
 16
 17#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 18#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
 19pub struct AnthropicModelCacheConfiguration {
 20    pub min_total_token: u64,
 21    pub should_speculate: bool,
 22    pub max_cache_anchors: usize,
 23}
 24
 25#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 26#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
 27pub enum AnthropicModelMode {
 28    #[default]
 29    Default,
 30    Thinking {
 31        budget_tokens: Option<u32>,
 32    },
 33}
 34
 35impl From<ModelMode> for AnthropicModelMode {
 36    fn from(value: ModelMode) -> Self {
 37        match value {
 38            ModelMode::Default => AnthropicModelMode::Default,
 39            ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens },
 40        }
 41    }
 42}
 43
 44impl From<AnthropicModelMode> for ModelMode {
 45    fn from(value: AnthropicModelMode) -> Self {
 46        match value {
 47            AnthropicModelMode::Default => ModelMode::Default,
 48            AnthropicModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens },
 49        }
 50    }
 51}
 52
 53#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 54#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
 55pub enum Model {
 56    #[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
 57    ClaudeOpus4,
 58    #[serde(rename = "claude-opus-4-1", alias = "claude-opus-4-1-latest")]
 59    ClaudeOpus4_1,
 60    #[serde(
 61        rename = "claude-opus-4-thinking",
 62        alias = "claude-opus-4-thinking-latest"
 63    )]
 64    ClaudeOpus4Thinking,
 65    #[serde(
 66        rename = "claude-opus-4-1-thinking",
 67        alias = "claude-opus-4-1-thinking-latest"
 68    )]
 69    ClaudeOpus4_1Thinking,
 70    #[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
 71    ClaudeSonnet4,
 72    #[serde(
 73        rename = "claude-sonnet-4-thinking",
 74        alias = "claude-sonnet-4-thinking-latest"
 75    )]
 76    ClaudeSonnet4Thinking,
 77    #[default]
 78    #[serde(rename = "claude-sonnet-4-5", alias = "claude-sonnet-4-5-latest")]
 79    ClaudeSonnet4_5,
 80    #[serde(
 81        rename = "claude-sonnet-4-5-thinking",
 82        alias = "claude-sonnet-4-5-thinking-latest"
 83    )]
 84    ClaudeSonnet4_5Thinking,
 85    #[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
 86    Claude3_7Sonnet,
 87    #[serde(
 88        rename = "claude-3-7-sonnet-thinking",
 89        alias = "claude-3-7-sonnet-thinking-latest"
 90    )]
 91    Claude3_7SonnetThinking,
 92    #[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
 93    Claude3_5Sonnet,
 94    #[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
 95    Claude3_5Haiku,
 96    #[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
 97    Claude3Opus,
 98    #[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
 99    Claude3Sonnet,
100    #[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-latest")]
101    Claude3Haiku,
102    #[serde(rename = "custom")]
103    Custom {
104        name: String,
105        max_tokens: u64,
106        /// The name displayed in the UI, such as in the assistant panel model dropdown menu.
107        display_name: Option<String>,
108        /// Override this model with a different Anthropic model for tool calls.
109        tool_override: Option<String>,
110        /// Indicates whether this custom model supports caching.
111        cache_configuration: Option<AnthropicModelCacheConfiguration>,
112        max_output_tokens: Option<u64>,
113        default_temperature: Option<f32>,
114        #[serde(default)]
115        extra_beta_headers: Vec<String>,
116        #[serde(default)]
117        mode: AnthropicModelMode,
118    },
119}
120
121impl Model {
122    pub fn default_fast() -> Self {
123        Self::Claude3_5Haiku
124    }
125
126    pub fn from_id(id: &str) -> Result<Self> {
127        if id.starts_with("claude-opus-4-1-thinking") {
128            return Ok(Self::ClaudeOpus4_1Thinking);
129        }
130
131        if id.starts_with("claude-opus-4-thinking") {
132            return Ok(Self::ClaudeOpus4Thinking);
133        }
134
135        if id.starts_with("claude-opus-4-1") {
136            return Ok(Self::ClaudeOpus4_1);
137        }
138
139        if id.starts_with("claude-opus-4") {
140            return Ok(Self::ClaudeOpus4);
141        }
142
143        if id.starts_with("claude-sonnet-4-5-thinking") {
144            return Ok(Self::ClaudeSonnet4_5Thinking);
145        }
146
147        if id.starts_with("claude-sonnet-4-5") {
148            return Ok(Self::ClaudeSonnet4_5);
149        }
150
151        if id.starts_with("claude-sonnet-4-thinking") {
152            return Ok(Self::ClaudeSonnet4Thinking);
153        }
154
155        if id.starts_with("claude-sonnet-4") {
156            return Ok(Self::ClaudeSonnet4);
157        }
158
159        if id.starts_with("claude-3-7-sonnet-thinking") {
160            return Ok(Self::Claude3_7SonnetThinking);
161        }
162
163        if id.starts_with("claude-3-7-sonnet") {
164            return Ok(Self::Claude3_7Sonnet);
165        }
166
167        if id.starts_with("claude-3-5-sonnet") {
168            return Ok(Self::Claude3_5Sonnet);
169        }
170
171        if id.starts_with("claude-3-5-haiku") {
172            return Ok(Self::Claude3_5Haiku);
173        }
174
175        if id.starts_with("claude-3-opus") {
176            return Ok(Self::Claude3Opus);
177        }
178
179        if id.starts_with("claude-3-sonnet") {
180            return Ok(Self::Claude3Sonnet);
181        }
182
183        if id.starts_with("claude-3-haiku") {
184            return Ok(Self::Claude3Haiku);
185        }
186
187        Err(anyhow!("invalid model ID: {id}"))
188    }
189
190    pub fn id(&self) -> &str {
191        match self {
192            Self::ClaudeOpus4 => "claude-opus-4-latest",
193            Self::ClaudeOpus4_1 => "claude-opus-4-1-latest",
194            Self::ClaudeOpus4Thinking => "claude-opus-4-thinking-latest",
195            Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-thinking-latest",
196            Self::ClaudeSonnet4 => "claude-sonnet-4-latest",
197            Self::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
198            Self::ClaudeSonnet4_5 => "claude-sonnet-4-5-latest",
199            Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-thinking-latest",
200            Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
201            Self::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
202            Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
203            Self::Claude3_5Haiku => "claude-3-5-haiku-latest",
204            Self::Claude3Opus => "claude-3-opus-latest",
205            Self::Claude3Sonnet => "claude-3-sonnet-20240229",
206            Self::Claude3Haiku => "claude-3-haiku-20240307",
207            Self::Custom { name, .. } => name,
208        }
209    }
210
211    /// The id of the model that should be used for making API requests
212    pub fn request_id(&self) -> &str {
213        match self {
214            Self::ClaudeOpus4 | Self::ClaudeOpus4Thinking => "claude-opus-4-20250514",
215            Self::ClaudeOpus4_1 | Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-20250805",
216            Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
217            Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-20250929",
218            Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
219            Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
220            Self::Claude3_5Haiku => "claude-3-5-haiku-latest",
221            Self::Claude3Opus => "claude-3-opus-latest",
222            Self::Claude3Sonnet => "claude-3-sonnet-20240229",
223            Self::Claude3Haiku => "claude-3-haiku-20240307",
224            Self::Custom { name, .. } => name,
225        }
226    }
227
228    pub fn display_name(&self) -> &str {
229        match self {
230            Self::ClaudeOpus4 => "Claude Opus 4",
231            Self::ClaudeOpus4_1 => "Claude Opus 4.1",
232            Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
233            Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
234            Self::ClaudeSonnet4 => "Claude Sonnet 4",
235            Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
236            Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
237            Self::ClaudeSonnet4_5Thinking => "Claude Sonnet 4.5 Thinking",
238            Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
239            Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
240            Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
241            Self::Claude3_5Haiku => "Claude 3.5 Haiku",
242            Self::Claude3Opus => "Claude 3 Opus",
243            Self::Claude3Sonnet => "Claude 3 Sonnet",
244            Self::Claude3Haiku => "Claude 3 Haiku",
245            Self::Custom {
246                name, display_name, ..
247            } => display_name.as_ref().unwrap_or(name),
248        }
249    }
250
251    pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
252        match self {
253            Self::ClaudeOpus4
254            | Self::ClaudeOpus4_1
255            | Self::ClaudeOpus4Thinking
256            | Self::ClaudeOpus4_1Thinking
257            | Self::ClaudeSonnet4
258            | Self::ClaudeSonnet4Thinking
259            | Self::ClaudeSonnet4_5
260            | Self::ClaudeSonnet4_5Thinking
261            | Self::Claude3_5Sonnet
262            | Self::Claude3_5Haiku
263            | Self::Claude3_7Sonnet
264            | Self::Claude3_7SonnetThinking
265            | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
266                min_total_token: 2_048,
267                should_speculate: true,
268                max_cache_anchors: 4,
269            }),
270            Self::Custom {
271                cache_configuration,
272                ..
273            } => cache_configuration.clone(),
274            _ => None,
275        }
276    }
277
278    pub fn max_token_count(&self) -> u64 {
279        match self {
280            Self::ClaudeOpus4
281            | Self::ClaudeOpus4_1
282            | Self::ClaudeOpus4Thinking
283            | Self::ClaudeOpus4_1Thinking
284            | Self::ClaudeSonnet4
285            | Self::ClaudeSonnet4Thinking
286            | Self::ClaudeSonnet4_5
287            | Self::ClaudeSonnet4_5Thinking
288            | Self::Claude3_5Sonnet
289            | Self::Claude3_5Haiku
290            | Self::Claude3_7Sonnet
291            | Self::Claude3_7SonnetThinking
292            | Self::Claude3Opus
293            | Self::Claude3Sonnet
294            | Self::Claude3Haiku => 200_000,
295            Self::Custom { max_tokens, .. } => *max_tokens,
296        }
297    }
298
299    pub fn max_output_tokens(&self) -> u64 {
300        match self {
301            Self::ClaudeOpus4
302            | Self::ClaudeOpus4_1
303            | Self::ClaudeOpus4Thinking
304            | Self::ClaudeOpus4_1Thinking
305            | Self::ClaudeSonnet4
306            | Self::ClaudeSonnet4Thinking
307            | Self::ClaudeSonnet4_5
308            | Self::ClaudeSonnet4_5Thinking
309            | Self::Claude3_5Sonnet
310            | Self::Claude3_7Sonnet
311            | Self::Claude3_7SonnetThinking
312            | Self::Claude3_5Haiku => 8_192,
313            Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
314            Self::Custom {
315                max_output_tokens, ..
316            } => max_output_tokens.unwrap_or(4_096),
317        }
318    }
319
320    pub fn default_temperature(&self) -> f32 {
321        match self {
322            Self::ClaudeOpus4
323            | Self::ClaudeOpus4_1
324            | Self::ClaudeOpus4Thinking
325            | Self::ClaudeOpus4_1Thinking
326            | Self::ClaudeSonnet4
327            | Self::ClaudeSonnet4Thinking
328            | Self::ClaudeSonnet4_5
329            | Self::ClaudeSonnet4_5Thinking
330            | Self::Claude3_5Sonnet
331            | Self::Claude3_7Sonnet
332            | Self::Claude3_7SonnetThinking
333            | Self::Claude3_5Haiku
334            | Self::Claude3Opus
335            | Self::Claude3Sonnet
336            | Self::Claude3Haiku => 1.0,
337            Self::Custom {
338                default_temperature,
339                ..
340            } => default_temperature.unwrap_or(1.0),
341        }
342    }
343
344    pub fn mode(&self) -> AnthropicModelMode {
345        match self {
346            Self::ClaudeOpus4
347            | Self::ClaudeOpus4_1
348            | Self::ClaudeSonnet4
349            | Self::ClaudeSonnet4_5
350            | Self::Claude3_5Sonnet
351            | Self::Claude3_7Sonnet
352            | Self::Claude3_5Haiku
353            | Self::Claude3Opus
354            | Self::Claude3Sonnet
355            | Self::Claude3Haiku => AnthropicModelMode::Default,
356            Self::ClaudeOpus4Thinking
357            | Self::ClaudeOpus4_1Thinking
358            | Self::ClaudeSonnet4Thinking
359            | Self::ClaudeSonnet4_5Thinking
360            | Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
361                budget_tokens: Some(4_096),
362            },
363            Self::Custom { mode, .. } => mode.clone(),
364        }
365    }
366
367    pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
368
369    pub fn beta_headers(&self) -> String {
370        let mut headers = Self::DEFAULT_BETA_HEADERS
371            .iter()
372            .map(|header| header.to_string())
373            .collect::<Vec<_>>();
374
375        match self {
376            Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => {
377                // Try beta token-efficient tool use (supported in Claude 3.7 Sonnet only)
378                // https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
379                headers.push("token-efficient-tools-2025-02-19".to_string());
380            }
381            Self::Custom {
382                extra_beta_headers, ..
383            } => {
384                headers.extend(
385                    extra_beta_headers
386                        .iter()
387                        .filter(|header| !header.trim().is_empty())
388                        .cloned(),
389                );
390            }
391            _ => {}
392        }
393
394        headers.join(",")
395    }
396
397    pub fn tool_model_id(&self) -> &str {
398        if let Self::Custom {
399            tool_override: Some(tool_override),
400            ..
401        } = self
402        {
403            tool_override
404        } else {
405            self.request_id()
406        }
407    }
408}
409
410pub async fn complete(
411    client: &dyn HttpClient,
412    api_url: &str,
413    api_key: &str,
414    request: Request,
415    beta_headers: String,
416) -> Result<Response, AnthropicError> {
417    let uri = format!("{api_url}/v1/messages");
418    let request_builder = HttpRequest::builder()
419        .method(Method::POST)
420        .uri(uri)
421        .header("Anthropic-Version", "2023-06-01")
422        .header("Anthropic-Beta", beta_headers)
423        .header("X-Api-Key", api_key.trim())
424        .header("Content-Type", "application/json");
425
426    let serialized_request =
427        serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
428    let request = request_builder
429        .body(AsyncBody::from(serialized_request))
430        .map_err(AnthropicError::BuildRequestBody)?;
431
432    let mut response = client
433        .send(request)
434        .await
435        .map_err(AnthropicError::HttpSend)?;
436    let status_code = response.status();
437    let mut body = String::new();
438    response
439        .body_mut()
440        .read_to_string(&mut body)
441        .await
442        .map_err(AnthropicError::ReadResponse)?;
443
444    if status_code.is_success() {
445        Ok(serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)?)
446    } else {
447        Err(AnthropicError::HttpResponseError {
448            status_code,
449            message: body,
450        })
451    }
452}
453
454pub async fn stream_completion(
455    client: &dyn HttpClient,
456    api_url: &str,
457    api_key: &str,
458    request: Request,
459    beta_headers: String,
460) -> Result<BoxStream<'static, Result<Event, AnthropicError>>, AnthropicError> {
461    stream_completion_with_rate_limit_info(client, api_url, api_key, request, beta_headers)
462        .await
463        .map(|output| output.0)
464}
465
466/// An individual rate limit.
467#[derive(Debug)]
468pub struct RateLimit {
469    pub limit: usize,
470    pub remaining: usize,
471    pub reset: DateTime<Utc>,
472}
473
474impl RateLimit {
475    fn from_headers(resource: &str, headers: &HeaderMap<HeaderValue>) -> Result<Self> {
476        let limit =
477            get_header(&format!("anthropic-ratelimit-{resource}-limit"), headers)?.parse()?;
478        let remaining = get_header(
479            &format!("anthropic-ratelimit-{resource}-remaining"),
480            headers,
481        )?
482        .parse()?;
483        let reset = DateTime::parse_from_rfc3339(get_header(
484            &format!("anthropic-ratelimit-{resource}-reset"),
485            headers,
486        )?)?
487        .to_utc();
488
489        Ok(Self {
490            limit,
491            remaining,
492            reset,
493        })
494    }
495}
496
497/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
498#[derive(Debug)]
499pub struct RateLimitInfo {
500    pub retry_after: Option<Duration>,
501    pub requests: Option<RateLimit>,
502    pub tokens: Option<RateLimit>,
503    pub input_tokens: Option<RateLimit>,
504    pub output_tokens: Option<RateLimit>,
505}
506
507impl RateLimitInfo {
508    fn from_headers(headers: &HeaderMap<HeaderValue>) -> Self {
509        // Check if any rate limit headers exist
510        let has_rate_limit_headers = headers
511            .keys()
512            .any(|k| k == "retry-after" || k.as_str().starts_with("anthropic-ratelimit-"));
513
514        if !has_rate_limit_headers {
515            return Self {
516                retry_after: None,
517                requests: None,
518                tokens: None,
519                input_tokens: None,
520                output_tokens: None,
521            };
522        }
523
524        Self {
525            retry_after: parse_retry_after(headers),
526            requests: RateLimit::from_headers("requests", headers).ok(),
527            tokens: RateLimit::from_headers("tokens", headers).ok(),
528            input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
529            output_tokens: RateLimit::from_headers("output-tokens", headers).ok(),
530        }
531    }
532}
533
534/// Parses the Retry-After header value as an integer number of seconds (anthropic always uses
535/// seconds). Note that other services might specify an HTTP date or some other format for this
536/// header. Returns `None` if the header is not present or cannot be parsed.
537pub fn parse_retry_after(headers: &HeaderMap<HeaderValue>) -> Option<Duration> {
538    headers
539        .get("retry-after")
540        .and_then(|v| v.to_str().ok())
541        .and_then(|v| v.parse::<u64>().ok())
542        .map(Duration::from_secs)
543}
544
545fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> anyhow::Result<&'a str> {
546    Ok(headers
547        .get(key)
548        .with_context(|| format!("missing header `{key}`"))?
549        .to_str()?)
550}
551
552pub async fn stream_completion_with_rate_limit_info(
553    client: &dyn HttpClient,
554    api_url: &str,
555    api_key: &str,
556    request: Request,
557    beta_headers: String,
558) -> Result<
559    (
560        BoxStream<'static, Result<Event, AnthropicError>>,
561        Option<RateLimitInfo>,
562    ),
563    AnthropicError,
564> {
565    let request = StreamingRequest {
566        base: request,
567        stream: true,
568    };
569    let uri = format!("{api_url}/v1/messages");
570
571    let request_builder = HttpRequest::builder()
572        .method(Method::POST)
573        .uri(uri)
574        .header("Anthropic-Version", "2023-06-01")
575        .header("Anthropic-Beta", beta_headers)
576        .header("X-Api-Key", api_key.trim())
577        .header("Content-Type", "application/json");
578    let serialized_request =
579        serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
580    let request = request_builder
581        .body(AsyncBody::from(serialized_request))
582        .map_err(AnthropicError::BuildRequestBody)?;
583
584    let mut response = client
585        .send(request)
586        .await
587        .map_err(AnthropicError::HttpSend)?;
588    let rate_limits = RateLimitInfo::from_headers(response.headers());
589    if response.status().is_success() {
590        let reader = BufReader::new(response.into_body());
591        let stream = reader
592            .lines()
593            .filter_map(|line| async move {
594                match line {
595                    Ok(line) => {
596                        let line = line.strip_prefix("data: ")?;
597                        match serde_json::from_str(line) {
598                            Ok(response) => Some(Ok(response)),
599                            Err(error) => Some(Err(AnthropicError::DeserializeResponse(error))),
600                        }
601                    }
602                    Err(error) => Some(Err(AnthropicError::ReadResponse(error))),
603                }
604            })
605            .boxed();
606        Ok((stream, Some(rate_limits)))
607    } else if response.status().as_u16() == 529 {
608        Err(AnthropicError::ServerOverloaded {
609            retry_after: rate_limits.retry_after,
610        })
611    } else if let Some(retry_after) = rate_limits.retry_after {
612        Err(AnthropicError::RateLimit { retry_after })
613    } else {
614        let mut body = String::new();
615        response
616            .body_mut()
617            .read_to_string(&mut body)
618            .await
619            .map_err(AnthropicError::ReadResponse)?;
620
621        match serde_json::from_str::<Event>(&body) {
622            Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
623            Ok(_) | Err(_) => Err(AnthropicError::HttpResponseError {
624                status_code: response.status(),
625                message: body,
626            }),
627        }
628    }
629}
630
631#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
632#[serde(rename_all = "lowercase")]
633pub enum CacheControlType {
634    Ephemeral,
635}
636
637#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
638pub struct CacheControl {
639    #[serde(rename = "type")]
640    pub cache_type: CacheControlType,
641}
642
643#[derive(Debug, Serialize, Deserialize)]
644pub struct Message {
645    pub role: Role,
646    pub content: Vec<RequestContent>,
647}
648
649#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
650#[serde(rename_all = "lowercase")]
651pub enum Role {
652    User,
653    Assistant,
654}
655
656#[derive(Debug, Serialize, Deserialize)]
657#[serde(tag = "type")]
658pub enum RequestContent {
659    #[serde(rename = "text")]
660    Text {
661        text: String,
662        #[serde(skip_serializing_if = "Option::is_none")]
663        cache_control: Option<CacheControl>,
664    },
665    #[serde(rename = "thinking")]
666    Thinking {
667        thinking: String,
668        signature: String,
669        #[serde(skip_serializing_if = "Option::is_none")]
670        cache_control: Option<CacheControl>,
671    },
672    #[serde(rename = "redacted_thinking")]
673    RedactedThinking { data: String },
674    #[serde(rename = "image")]
675    Image {
676        source: ImageSource,
677        #[serde(skip_serializing_if = "Option::is_none")]
678        cache_control: Option<CacheControl>,
679    },
680    #[serde(rename = "tool_use")]
681    ToolUse {
682        id: String,
683        name: String,
684        input: serde_json::Value,
685        #[serde(skip_serializing_if = "Option::is_none")]
686        cache_control: Option<CacheControl>,
687    },
688    #[serde(rename = "tool_result")]
689    ToolResult {
690        tool_use_id: String,
691        is_error: bool,
692        content: ToolResultContent,
693        #[serde(skip_serializing_if = "Option::is_none")]
694        cache_control: Option<CacheControl>,
695    },
696}
697
698#[derive(Debug, Serialize, Deserialize)]
699#[serde(untagged)]
700pub enum ToolResultContent {
701    Plain(String),
702    Multipart(Vec<ToolResultPart>),
703}
704
705#[derive(Debug, Serialize, Deserialize)]
706#[serde(tag = "type", rename_all = "lowercase")]
707pub enum ToolResultPart {
708    Text { text: String },
709    Image { source: ImageSource },
710}
711
712#[derive(Debug, Serialize, Deserialize)]
713#[serde(tag = "type")]
714pub enum ResponseContent {
715    #[serde(rename = "text")]
716    Text { text: String },
717    #[serde(rename = "thinking")]
718    Thinking { thinking: String },
719    #[serde(rename = "redacted_thinking")]
720    RedactedThinking { data: String },
721    #[serde(rename = "tool_use")]
722    ToolUse {
723        id: String,
724        name: String,
725        input: serde_json::Value,
726    },
727}
728
729#[derive(Debug, Serialize, Deserialize)]
730pub struct ImageSource {
731    #[serde(rename = "type")]
732    pub source_type: String,
733    pub media_type: String,
734    pub data: String,
735}
736
737#[derive(Debug, Serialize, Deserialize)]
738pub struct Tool {
739    pub name: String,
740    pub description: String,
741    pub input_schema: serde_json::Value,
742}
743
744#[derive(Debug, Serialize, Deserialize)]
745#[serde(tag = "type", rename_all = "lowercase")]
746pub enum ToolChoice {
747    Auto,
748    Any,
749    Tool { name: String },
750    None,
751}
752
753#[derive(Debug, Serialize, Deserialize)]
754#[serde(tag = "type", rename_all = "lowercase")]
755pub enum Thinking {
756    Enabled { budget_tokens: Option<u32> },
757}
758
759#[derive(Debug, Serialize, Deserialize)]
760#[serde(untagged)]
761pub enum StringOrContents {
762    String(String),
763    Content(Vec<RequestContent>),
764}
765
766#[derive(Debug, Serialize, Deserialize)]
767pub struct Request {
768    pub model: String,
769    pub max_tokens: u64,
770    pub messages: Vec<Message>,
771    #[serde(default, skip_serializing_if = "Vec::is_empty")]
772    pub tools: Vec<Tool>,
773    #[serde(default, skip_serializing_if = "Option::is_none")]
774    pub thinking: Option<Thinking>,
775    #[serde(default, skip_serializing_if = "Option::is_none")]
776    pub tool_choice: Option<ToolChoice>,
777    #[serde(default, skip_serializing_if = "Option::is_none")]
778    pub system: Option<StringOrContents>,
779    #[serde(default, skip_serializing_if = "Option::is_none")]
780    pub metadata: Option<Metadata>,
781    #[serde(default, skip_serializing_if = "Vec::is_empty")]
782    pub stop_sequences: Vec<String>,
783    #[serde(default, skip_serializing_if = "Option::is_none")]
784    pub temperature: Option<f32>,
785    #[serde(default, skip_serializing_if = "Option::is_none")]
786    pub top_k: Option<u32>,
787    #[serde(default, skip_serializing_if = "Option::is_none")]
788    pub top_p: Option<f32>,
789}
790
791#[derive(Debug, Serialize, Deserialize)]
792struct StreamingRequest {
793    #[serde(flatten)]
794    pub base: Request,
795    pub stream: bool,
796}
797
798#[derive(Debug, Serialize, Deserialize)]
799pub struct Metadata {
800    pub user_id: Option<String>,
801}
802
803#[derive(Debug, Serialize, Deserialize, Default)]
804pub struct Usage {
805    #[serde(default, skip_serializing_if = "Option::is_none")]
806    pub input_tokens: Option<u64>,
807    #[serde(default, skip_serializing_if = "Option::is_none")]
808    pub output_tokens: Option<u64>,
809    #[serde(default, skip_serializing_if = "Option::is_none")]
810    pub cache_creation_input_tokens: Option<u64>,
811    #[serde(default, skip_serializing_if = "Option::is_none")]
812    pub cache_read_input_tokens: Option<u64>,
813}
814
815#[derive(Debug, Serialize, Deserialize)]
816pub struct Response {
817    pub id: String,
818    #[serde(rename = "type")]
819    pub response_type: String,
820    pub role: Role,
821    pub content: Vec<ResponseContent>,
822    pub model: String,
823    #[serde(default, skip_serializing_if = "Option::is_none")]
824    pub stop_reason: Option<String>,
825    #[serde(default, skip_serializing_if = "Option::is_none")]
826    pub stop_sequence: Option<String>,
827    pub usage: Usage,
828}
829
830#[derive(Debug, Serialize, Deserialize)]
831#[serde(tag = "type")]
832pub enum Event {
833    #[serde(rename = "message_start")]
834    MessageStart { message: Response },
835    #[serde(rename = "content_block_start")]
836    ContentBlockStart {
837        index: usize,
838        content_block: ResponseContent,
839    },
840    #[serde(rename = "content_block_delta")]
841    ContentBlockDelta { index: usize, delta: ContentDelta },
842    #[serde(rename = "content_block_stop")]
843    ContentBlockStop { index: usize },
844    #[serde(rename = "message_delta")]
845    MessageDelta { delta: MessageDelta, usage: Usage },
846    #[serde(rename = "message_stop")]
847    MessageStop,
848    #[serde(rename = "ping")]
849    Ping,
850    #[serde(rename = "error")]
851    Error { error: ApiError },
852}
853
854#[derive(Debug, Serialize, Deserialize)]
855#[serde(tag = "type")]
856pub enum ContentDelta {
857    #[serde(rename = "text_delta")]
858    TextDelta { text: String },
859    #[serde(rename = "thinking_delta")]
860    ThinkingDelta { thinking: String },
861    #[serde(rename = "signature_delta")]
862    SignatureDelta { signature: String },
863    #[serde(rename = "input_json_delta")]
864    InputJsonDelta { partial_json: String },
865}
866
867#[derive(Debug, Serialize, Deserialize)]
868pub struct MessageDelta {
869    pub stop_reason: Option<String>,
870    pub stop_sequence: Option<String>,
871}
872
873#[derive(Debug)]
874pub enum AnthropicError {
875    /// Failed to serialize the HTTP request body to JSON
876    SerializeRequest(serde_json::Error),
877
878    /// Failed to construct the HTTP request body
879    BuildRequestBody(http::Error),
880
881    /// Failed to send the HTTP request
882    HttpSend(anyhow::Error),
883
884    /// Failed to deserialize the response from JSON
885    DeserializeResponse(serde_json::Error),
886
887    /// Failed to read from response stream
888    ReadResponse(io::Error),
889
890    /// HTTP error response from the API
891    HttpResponseError {
892        status_code: StatusCode,
893        message: String,
894    },
895
896    /// Rate limit exceeded
897    RateLimit { retry_after: Duration },
898
899    /// Server overloaded
900    ServerOverloaded { retry_after: Option<Duration> },
901
902    /// API returned an error response
903    ApiError(ApiError),
904}
905
906#[derive(Debug, Serialize, Deserialize, Error)]
907#[error("Anthropic API Error: {error_type}: {message}")]
908pub struct ApiError {
909    #[serde(rename = "type")]
910    pub error_type: String,
911    pub message: String,
912}
913
914/// An Anthropic API error code.
915/// <https://docs.anthropic.com/en/api/errors#http-errors>
916#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
917#[strum(serialize_all = "snake_case")]
918pub enum ApiErrorCode {
919    /// 400 - `invalid_request_error`: There was an issue with the format or content of your request.
920    InvalidRequestError,
921    /// 401 - `authentication_error`: There's an issue with your API key.
922    AuthenticationError,
923    /// 403 - `permission_error`: Your API key does not have permission to use the specified resource.
924    PermissionError,
925    /// 404 - `not_found_error`: The requested resource was not found.
926    NotFoundError,
927    /// 413 - `request_too_large`: Request exceeds the maximum allowed number of bytes.
928    RequestTooLarge,
929    /// 429 - `rate_limit_error`: Your account has hit a rate limit.
930    RateLimitError,
931    /// 500 - `api_error`: An unexpected error has occurred internal to Anthropic's systems.
932    ApiError,
933    /// 529 - `overloaded_error`: Anthropic's API is temporarily overloaded.
934    OverloadedError,
935}
936
937impl ApiError {
938    pub fn code(&self) -> Option<ApiErrorCode> {
939        ApiErrorCode::from_str(&self.error_type).ok()
940    }
941
942    pub fn is_rate_limit_error(&self) -> bool {
943        matches!(self.error_type.as_str(), "rate_limit_error")
944    }
945
946    pub fn match_window_exceeded(&self) -> Option<u64> {
947        let Some(ApiErrorCode::InvalidRequestError) = self.code() else {
948            return None;
949        };
950
951        parse_prompt_too_long(&self.message)
952    }
953}
954
955pub fn parse_prompt_too_long(message: &str) -> Option<u64> {
956    message
957        .strip_prefix("prompt is too long: ")?
958        .split_once(" tokens")?
959        .0
960        .parse()
961        .ok()
962}
963
964#[test]
965fn test_match_window_exceeded() {
966    let error = ApiError {
967        error_type: "invalid_request_error".to_string(),
968        message: "prompt is too long: 220000 tokens > 200000".to_string(),
969    };
970    assert_eq!(error.match_window_exceeded(), Some(220_000));
971
972    let error = ApiError {
973        error_type: "invalid_request_error".to_string(),
974        message: "prompt is too long: 1234953 tokens".to_string(),
975    };
976    assert_eq!(error.match_window_exceeded(), Some(1234953));
977
978    let error = ApiError {
979        error_type: "invalid_request_error".to_string(),
980        message: "not a prompt length error".to_string(),
981    };
982    assert_eq!(error.match_window_exceeded(), None);
983
984    let error = ApiError {
985        error_type: "rate_limit_error".to_string(),
986        message: "prompt is too long: 12345 tokens".to_string(),
987    };
988    assert_eq!(error.match_window_exceeded(), None);
989
990    let error = ApiError {
991        error_type: "invalid_request_error".to_string(),
992        message: "prompt is too long: invalid tokens".to_string(),
993    };
994    assert_eq!(error.match_window_exceeded(), None);
995}