Skip to main content

roder_api/
provider_error.rs

1//! Typed provider failures shared by transports and the sampling loop.
2use std::fmt;
3use tokio::time::Instant;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ProviderFailureKind {
7    Transport,
8    StreamInterrupted,
9    RateLimited,
10    ServerOverloaded,
11    ContextWindowExceeded,
12    UsageLimit,
13    QuotaExceeded,
14    Authentication,
15    InvalidRequest,
16    Protocol,
17    ToolSearchExhausted,
18    RequestTooLarge,
19}
20
21impl ProviderFailureKind {
22    pub fn is_retryable(self) -> bool {
23        matches!(
24            self,
25            Self::Transport | Self::StreamInterrupted | Self::RateLimited | Self::ServerOverloaded
26        )
27    }
28
29    pub fn retry_cause(self) -> &'static str {
30        match self {
31            Self::Transport => "transport_error",
32            Self::StreamInterrupted => "stream_interrupted",
33            Self::RateLimited => "rate_limit",
34            Self::ServerOverloaded => "server_overloaded",
35            Self::ContextWindowExceeded => "context_window_exceeded",
36            Self::UsageLimit => "usage_limit",
37            Self::QuotaExceeded => "quota_exceeded",
38            Self::Authentication => "authentication",
39            Self::InvalidRequest => "invalid_request",
40            Self::Protocol => "protocol_error",
41            Self::ToolSearchExhausted => "tool_search_exhausted",
42            Self::RequestTooLarge => "request_too_large",
43        }
44    }
45}
46
47#[derive(Debug, Clone)]
48pub struct ProviderFailure {
49    pub kind: ProviderFailureKind,
50    pub message: String,
51    pub provider_code: Option<String>,
52    pub status: Option<u16>,
53    pub request_id: Option<String>,
54    pub response_id: Option<String>,
55    /// Absolute monotonic deadline captured on receipt of provider advice.
56    pub retry_not_before: Option<Instant>,
57    /// Transport already spent this sampling step's request retry budget.
58    pub retry_budget_exhausted: bool,
59}
60
61impl ProviderFailure {
62    pub fn metadata(&self) -> serde_json::Value {
63        serde_json::json!({"kind":"provider_failure","cause":self.kind.retry_cause(),
64            "providerCode":self.provider_code,"status":self.status,"requestId":self.request_id,
65            "responseId":self.response_id,"retryable":self.kind.is_retryable(),
66            "retryBudgetExhausted":self.retry_budget_exhausted,
67            "retryAfterMs":self.retry_not_before.map(|deadline|deadline.saturating_duration_since(Instant::now()).as_millis().min(u64::MAX as u128) as u64)})
68    }
69
70    pub fn new(kind: ProviderFailureKind, message: impl Into<String>) -> Self {
71        Self {
72            kind,
73            message: message.into(),
74            provider_code: None,
75            status: None,
76            request_id: None,
77            response_id: None,
78            retry_not_before: None,
79            retry_budget_exhausted: false,
80        }
81    }
82}
83
84impl fmt::Display for ProviderFailure {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        self.message.fmt(f)
87    }
88}
89impl std::error::Error for ProviderFailure {}