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}