Skip to main content

rpi_ai/
error.rs

1//! Mirrors the error surfaces implied by `packages/ai/src` — no single TS file;
2//! the variants collect every failure mode the ai crate can surface to a caller.
3//!
4//! Providers convert their wire-level failures into `AiError` before encoding
5//! them as an `AssistantMessageEvent::Error` (so the stream never panics). The
6//! agent loop and harness see `AiError` only when they themselves drive a
7//! provider call directly (e.g. compaction's standalone stream).
8
9use std::fmt;
10
11/// Every failure mode the `pi-ai` crate can surface.
12#[derive(Debug, Clone, PartialEq)]
13pub enum AiError {
14    /// HTTP transport failure (connection, DNS, TLS, non-retryable status).
15    Http {
16        status: Option<u16>,
17        message: String,
18    },
19    /// SSE parse / stream-corruption failure.
20    Sse { message: String },
21    /// Auth failure (401/403, missing/expired key).
22    Auth { message: String },
23    /// Operation cancelled via `CancellationToken`.
24    Abort { message: String },
25    /// JSON-Schema validation of tool arguments failed (after coercion).
26    Schema { message: String },
27    /// Provider returned a structured error body.
28    Provider { code: String, message: String },
29    /// Usage / cost computation failed (malformed provider usage payload).
30    Usage { message: String },
31}
32
33impl fmt::Display for AiError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            AiError::Http {
37                status: Some(s),
38                message,
39            } => {
40                write!(f, "http error {s}: {message}")
41            }
42            AiError::Http {
43                status: None,
44                message,
45            } => write!(f, "http error: {message}"),
46            AiError::Sse { message } => write!(f, "sse error: {message}"),
47            AiError::Auth { message } => write!(f, "auth error: {message}"),
48            AiError::Abort { message } => write!(f, "abort error: {message}"),
49            AiError::Schema { message } => write!(f, "schema error: {message}"),
50            AiError::Provider { code, message } => write!(f, "provider error [{code}]: {message}"),
51            AiError::Usage { message } => write!(f, "usage error: {message}"),
52        }
53    }
54}
55
56impl std::error::Error for AiError {}
57
58impl AiError {
59    /// True if this error originated from cancellation (not a provider fault).
60    pub fn is_abort(&self) -> bool {
61        matches!(self, AiError::Abort { .. })
62    }
63
64    /// True if retryable under `retry_provider_request` — matches the TS retry
65    /// predicate (408/409/429/>=500 plus transport-level Http with no status).
66    pub fn is_retryable(&self) -> bool {
67        match self {
68            AiError::Http {
69                status: Some(s), ..
70            } => matches!(*s, 408 | 409 | 429) || *s >= 500,
71            AiError::Http { status: None, .. } => true,
72            _ => false,
73        }
74    }
75}