Skip to main content

runifold_model/
error.rs

1use std::collections::BTreeMap;
2
3use runifold_core::RetrySafety;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use thiserror::Error;
7
8/// A normalized model-layer error category.
9#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
10#[non_exhaustive]
11pub enum ModelErrorKind {
12    /// Request data failed local validation.
13    InvalidRequest,
14    /// The selected model or provider does not support a required feature.
15    UnsupportedFeature,
16    /// A provider transport failed.
17    Transport,
18    /// A provider response violated its protocol.
19    Protocol,
20    /// Stream events violated the canonical lifecycle.
21    StreamState,
22    /// Accumulated tool arguments were not valid JSON.
23    MalformedToolArguments,
24    /// A provider rejected or failed a request.
25    Provider,
26    /// The model call was cancelled.
27    Cancelled,
28    /// The model call exceeded its deadline.
29    DeadlineExceeded,
30}
31
32/// A structured model-layer error.
33#[derive(Clone, Debug, Deserialize, Error, PartialEq, Serialize)]
34#[error("{kind:?}: {message}")]
35pub struct ModelError {
36    /// Normalized category.
37    pub kind: ModelErrorKind,
38    /// Safe human-readable explanation.
39    pub message: String,
40    /// Provider namespace, when known.
41    pub provider: Option<String>,
42    /// Retry-safety classification.
43    pub retry_safety: RetrySafety,
44    /// Namespaced diagnostic metadata.
45    pub metadata: BTreeMap<String, Value>,
46}
47
48impl ModelError {
49    /// Returns a stable diagnostic identifier without exposing error payloads.
50    ///
51    /// Resolve causes and corrective actions with `runifold ai explain <code>`.
52    /// This does not change the error's retry safety, Display or serialization.
53    pub const fn diagnostic_code(&self) -> &'static str {
54        match self.kind {
55            ModelErrorKind::InvalidRequest => "RF-PROVIDER-001",
56            ModelErrorKind::UnsupportedFeature => "RF-PROVIDER-002",
57            ModelErrorKind::Transport => "RF-PROVIDER-003",
58            ModelErrorKind::Protocol => "RF-PROVIDER-004",
59            ModelErrorKind::StreamState => "RF-PROVIDER-005",
60            ModelErrorKind::MalformedToolArguments => "RF-PROVIDER-006",
61            ModelErrorKind::Provider => "RF-PROVIDER-007",
62            ModelErrorKind::Cancelled => "RF-PROVIDER-008",
63            ModelErrorKind::DeadlineExceeded => "RF-PROVIDER-009",
64        }
65    }
66
67    /// Creates a non-retryable local validation or state error.
68    pub fn local(kind: ModelErrorKind, message: impl Into<String>) -> Self {
69        Self {
70            kind,
71            message: message.into(),
72            provider: None,
73            retry_safety: RetrySafety::Unknown,
74            metadata: BTreeMap::new(),
75        }
76    }
77}