llmkit-core 0.1.0

Core traits, types, and errors for llmkit-rs — no I/O, runtime-agnostic
Documentation
//! Unified error type. Every fallible llmkit operation returns [`LlmResult<T>`].

use std::fmt;

/// `Result` alias used throughout llmkit.
pub type LlmResult<T> = Result<T, LlmError>;

/// The single error type returned by every llmkit operation.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LlmError {
    /// Request rejected before sending (missing model, empty messages, …).
    #[error("invalid request: {0}")]
    InvalidRequest(String),

    /// Authentication failed (missing or invalid API key).
    #[error("authentication failed: {0}")]
    Auth(String),

    /// Provider returned HTTP 429.
    #[error("rate limited{}", .retry_after.map(|d| format!(" (retry after {}s)", d.as_secs())).unwrap_or_default())]
    RateLimited {
        /// Suggested delay before retrying, if provided.
        retry_after: Option<std::time::Duration>,
        /// Provider message.
        message: String,
    },

    /// Provider returned a non-success status with a body.
    #[error("provider error (status {status}): {message}")]
    Provider {
        /// HTTP status code.
        status: u16,
        /// Extracted message or body.
        message: String,
    },

    /// Network / transport failure.
    #[error("transport error: {0}")]
    Transport(String),

    /// Request exceeded the configured timeout.
    #[error("request timed out")]
    Timeout,

    /// Failed to (de)serialize a body.
    #[error("serialization error: {0}")]
    Serialization(String),

    /// Error while parsing or driving a streaming response.
    #[error("stream error: {0}")]
    Stream(String),

    /// A configured budget (e.g. session cost cap) was exceeded.
    #[error("budget exceeded: {0}")]
    BudgetExceeded(String),

    /// A tool invocation failed during execution.
    #[error("tool `{tool}` failed: {message}")]
    Tool {
        /// Tool name.
        tool: String,
        /// Handler error message.
        message: String,
    },

    /// Capability not supported by this provider.
    #[error("unsupported: {0}")]
    Unsupported(String),

    /// Every provider in a fallback chain failed.
    #[error("all providers failed ({} attempted)", .0.len())]
    AllProvidersFailed(Vec<LlmError>),

    /// Catch-all.
    #[error("{0}")]
    Other(String),
}

impl LlmError {
    /// Whether retrying might succeed. Used by the retry layer.
    pub fn is_retryable(&self) -> bool {
        match self {
            LlmError::Timeout
            | LlmError::Transport(_)
            | LlmError::Stream(_)
            | LlmError::RateLimited { .. } => true,
            LlmError::Provider { status, .. } => *status >= 500 || *status == 408,
            _ => false,
        }
    }

    /// Construct an [`LlmError::InvalidRequest`].
    pub fn invalid(msg: impl fmt::Display) -> Self {
        LlmError::InvalidRequest(msg.to_string())
    }

    /// Construct an [`LlmError::Serialization`].
    pub fn serde(msg: impl fmt::Display) -> Self {
        LlmError::Serialization(msg.to_string())
    }
}

impl From<serde_json::Error> for LlmError {
    fn from(e: serde_json::Error) -> Self {
        LlmError::Serialization(e.to_string())
    }
}