linger-openai-sdk 0.1.1

Rust-native async SDK for OpenAI APIs with typed requests, streaming, uploads, retries, and pluggable transports.
Documentation
use crate::error::{parse_retry_after_seconds, HeaderMap};
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;

/// EN: Runtime-neutral sleep boundary used by retry execution.
/// 中文:重试执行使用的运行时无关休眠边界。
pub trait RetrySleeper: Send + Sync + fmt::Debug {
    /// EN: Sleeps for the provided duration before the next retry attempt.
    /// 中文:在下一次重试前按给定时长休眠。
    fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
}

/// EN: No-op retry sleeper for runtime-neutral custom integrations.
/// 中文:用于运行时无关自定义集成的空操作重试休眠器。
#[derive(Clone, Debug, Default)]
pub struct NoopRetrySleeper;

impl RetrySleeper for NoopRetrySleeper {
    fn sleep(&self, _duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
        Box::pin(async {})
    }
}

/// EN: Decision produced by retry policy classification.
/// 中文:重试策略分类产生的决策。
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RetryDecision {
    /// EN: The operation should not be retried.
    /// 中文:不应重试该操作。
    DoNotRetry,
    /// EN: The operation may be retried, optionally after a server-provided delay.
    /// 中文:可以重试该操作,并可选择遵循服务端提供的延迟。
    RetryAfter(Option<Duration>),
}

/// EN: Conservative retry policy for transient OpenAI failures.
/// 中文:针对 OpenAI 瞬时故障的保守重试策略。
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RetryPolicy {
    /// EN: Maximum number of retry attempts after the first request.
    /// 中文:首次请求之后允许的最大重试次数。
    pub max_retries: usize,
    /// EN: Initial exponential backoff delay.
    /// 中文:指数退避的初始延迟。
    pub initial_backoff: Duration,
    /// EN: Maximum exponential backoff delay.
    /// 中文:指数退避的最大延迟。
    pub max_backoff: Duration,
}

impl Default for RetryPolicy {
    fn default() -> Self {
        Self {
            max_retries: 2,
            initial_backoff: Duration::from_millis(500),
            max_backoff: Duration::from_secs(8),
        }
    }
}

impl RetryPolicy {
    /// EN: Classifies an HTTP status according to documented transient retry candidates.
    /// 中文:根据已记录的瞬时重试候选状态码对 HTTP 状态分类。
    pub fn classify_status(&self, status: u16, headers: &HeaderMap) -> RetryDecision {
        match status {
            408 | 409 | 429 | 500..=599 => {
                let retry_after = headers
                    .get("retry-after")
                    .and_then(parse_retry_after_seconds);
                RetryDecision::RetryAfter(retry_after)
            }
            _ => RetryDecision::DoNotRetry,
        }
    }

    /// EN: Computes capped exponential backoff for a zero-based retry attempt.
    /// 中文:为从零开始的重试次数计算有上限的指数退避。
    pub fn backoff_delay(&self, retry_attempt: usize) -> Duration {
        let factor = 1_u32
            .checked_shl(retry_attempt.min(31) as u32)
            .unwrap_or(u32::MAX);
        self.initial_backoff
            .saturating_mul(factor)
            .min(self.max_backoff)
    }
}