providers-kit 0.0.2

Portable enum-dispatch Kit over multiple model provider APIs.
Documentation
use std::sync::Arc;

use reqwest::Method;
use url::Url;

use super::wire::AnthropicErrorEnvelope;
use crate::KitError;
use crate::config::TransportConfig;
use crate::config::{AnthropicConfig, DEFAULT_ANTHROPIC_VERSION};
use crate::error::ErrorKind;
use crate::transport::{AuthMode, AuthScheme, CheckedRequestSpec, ProviderHttpContext};

#[derive(Clone)]
pub(crate) struct AnthropicApiContext {
    transport: ProviderHttpContext,
}

pub(super) const ANTHROPIC_REQUESTS: CheckedRequestSpec =
    CheckedRequestSpec::new(AnthropicErrorEnvelope::parse);

impl AnthropicApiContext {
    pub(crate) fn shared(
        config: AnthropicConfig,
        transport: TransportConfig,
    ) -> Result<Arc<Self>, KitError> {
        let transport = ProviderHttpContext::new(
            config.base_url().clone(),
            AuthScheme::Header {
                name: "x-api-key".to_string(),
                value: config.api_key().clone(),
                prefix: None,
            },
            transport,
        )?;

        Ok(Arc::new(Self { transport }))
    }

    pub(super) fn classify_error(kind: &str) -> ErrorKind {
        match kind {
            "invalid_request_error" => ErrorKind::InvalidRequest,
            "authentication_error" => ErrorKind::Authentication,
            "permission_error" => ErrorKind::PermissionDenied,
            "not_found_error" => ErrorKind::NotFound,
            "request_too_large" => ErrorKind::RequestTooLarge,
            "rate_limit_error" => ErrorKind::RateLimited,
            "api_error" => ErrorKind::Internal,
            "billing_error" => ErrorKind::Billing,
            "conflict_error" => ErrorKind::Conflict,
            "timeout_error" => ErrorKind::DeadlineExceeded,
            "overloaded_error" => ErrorKind::ServiceUnavailable,
            _ => ErrorKind::ProviderRejected,
        }
    }

    pub(crate) fn transport(&self) -> &ProviderHttpContext {
        &self.transport
    }

    pub(crate) fn request(
        &self,
        method: Method,
        path: &str,
    ) -> Result<reqwest::RequestBuilder, KitError> {
        Ok(self.decorate_request(self.transport.request(method, path)?))
    }

    pub(crate) fn request_absolute(&self, method: Method, url: Url) -> reqwest::RequestBuilder {
        let auth = if self.transport.is_same_origin(&url) {
            AuthMode::Apply
        } else {
            AuthMode::Skip
        };
        self.decorate_request(self.transport.request_absolute(method, url, auth))
    }

    fn decorate_request(&self, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
        builder.header("anthropic-version", DEFAULT_ANTHROPIC_VERSION)
    }
}