Skip to main content

sage_runtime/
error.rs

1//! Error types for the Sage runtime.
2//!
3//! RFC-0007: This module provides the `SageError` type and `ErrorKind` enum
4//! that are exposed to Sage programs through the `Error` type.
5
6use thiserror::Error;
7
8/// Result type for Sage operations.
9pub type SageResult<T> = Result<T, SageError>;
10
11/// RFC-0007: Error kind classification for Sage errors.
12///
13/// This enum is exposed to Sage programs as `ErrorKind` and can be matched
14/// in `on error(e)` handlers via `e.kind`.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum ErrorKind {
17    /// Error from LLM inference (network, parsing, rate limits).
18    Llm,
19    /// Error from agent execution (panics, message failures).
20    Agent,
21    /// Runtime errors (type mismatches, I/O, etc.).
22    Runtime,
23    /// RFC-0011: Error from tool execution (Http, Fs, etc.).
24    Tool,
25    /// User-raised error via `fail` expression.
26    User,
27    /// Phase 3: Protocol violation in session types.
28    Protocol,
29}
30
31impl std::fmt::Display for ErrorKind {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            ErrorKind::Llm => write!(f, "Llm"),
35            ErrorKind::Agent => write!(f, "Agent"),
36            ErrorKind::Runtime => write!(f, "Runtime"),
37            ErrorKind::Tool => write!(f, "Tool"),
38            ErrorKind::User => write!(f, "User"),
39            ErrorKind::Protocol => write!(f, "Protocol"),
40        }
41    }
42}
43
44/// Error type for Sage runtime errors.
45///
46/// RFC-0007: This is exposed to Sage programs as the `Error` type with
47/// `.message` and `.kind` field accessors.
48#[derive(Debug, Error)]
49pub enum SageError {
50    /// Error from LLM inference.
51    #[error("LLM error: {0}")]
52    Llm(String),
53
54    /// Error from agent execution.
55    #[error("Agent error: {0}")]
56    Agent(String),
57
58    /// Type mismatch at runtime.
59    #[error("Type error: expected {expected}, got {got}")]
60    Type { expected: String, got: String },
61
62    /// HTTP request error.
63    #[error("HTTP error: {0}")]
64    Http(#[from] reqwest::Error),
65
66    /// JSON parsing error.
67    #[error("JSON error: {0}")]
68    Json(#[from] serde_json::Error),
69
70    /// Agent task was cancelled or panicked.
71    #[error("Agent task failed: {0}")]
72    JoinError(String),
73
74    /// RFC-0011: Error from tool execution.
75    #[error("Tool error: {0}")]
76    Tool(String),
77
78    /// I/O error (file operations, etc.).
79    #[error("I/O error: {0}")]
80    Io(#[from] std::io::Error),
81
82    /// User-raised error via `fail` expression.
83    #[error("{0}")]
84    User(String),
85
86    /// Error from supervisor (restart intensity exceeded, etc.).
87    #[error("Supervisor error: {0}")]
88    Supervisor(String),
89
90    /// Phase 3: Protocol violation in session types.
91    #[error("Protocol error: {0}")]
92    Protocol(String),
93}
94
95impl SageError {
96    /// RFC-0007: Get the error message as a String.
97    ///
98    /// This is exposed to Sage programs as `e.message`.
99    #[must_use]
100    pub fn message(&self) -> String {
101        self.to_string()
102    }
103
104    /// RFC-0007: Get the error kind classification.
105    ///
106    /// This is exposed to Sage programs as `e.kind`.
107    #[must_use]
108    pub fn kind(&self) -> ErrorKind {
109        match self {
110            SageError::Llm(_) | SageError::Json(_) => ErrorKind::Llm,
111            SageError::Agent(_) | SageError::JoinError(_) | SageError::Supervisor(_) => {
112                ErrorKind::Agent
113            }
114            SageError::Type { .. } => ErrorKind::Runtime,
115            // RFC-0011: Http, Io, and Tool errors are tool errors
116            SageError::Http(_) | SageError::Tool(_) | SageError::Io(_) => ErrorKind::Tool,
117            SageError::User(_) => ErrorKind::User,
118            // Phase 3: Protocol errors
119            SageError::Protocol(_) => ErrorKind::Protocol,
120        }
121    }
122
123    /// Create an LLM error with a message.
124    #[must_use]
125    pub fn llm(msg: impl Into<String>) -> Self {
126        SageError::Llm(msg.into())
127    }
128
129    /// Create an agent error with a message.
130    #[must_use]
131    pub fn agent(msg: impl Into<String>) -> Self {
132        SageError::Agent(msg.into())
133    }
134
135    /// Create a type error.
136    #[must_use]
137    pub fn type_error(expected: impl Into<String>, got: impl Into<String>) -> Self {
138        SageError::Type {
139            expected: expected.into(),
140            got: got.into(),
141        }
142    }
143
144    /// RFC-0011: Create a tool error with a message.
145    #[must_use]
146    pub fn tool(msg: impl Into<String>) -> Self {
147        SageError::Tool(msg.into())
148    }
149
150    /// Create a user error via `fail` expression.
151    #[must_use]
152    pub fn user(msg: impl Into<String>) -> Self {
153        SageError::User(msg.into())
154    }
155
156    /// Phase 3: Create a protocol violation error.
157    #[must_use]
158    pub fn protocol(msg: impl Into<String>) -> Self {
159        SageError::Protocol(msg.into())
160    }
161}
162
163impl From<tokio::task::JoinError> for SageError {
164    fn from(e: tokio::task::JoinError) -> Self {
165        SageError::JoinError(e.to_string())
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn error_kind_classification() {
175        assert_eq!(SageError::llm("test").kind(), ErrorKind::Llm);
176        assert_eq!(SageError::agent("test").kind(), ErrorKind::Agent);
177        assert_eq!(
178            SageError::type_error("Int", "String").kind(),
179            ErrorKind::Runtime
180        );
181    }
182
183    #[test]
184    fn error_message() {
185        let err = SageError::llm("inference failed");
186        assert_eq!(err.message(), "LLM error: inference failed");
187    }
188
189    #[test]
190    fn error_kind_display() {
191        assert_eq!(format!("{}", ErrorKind::Llm), "Llm");
192        assert_eq!(format!("{}", ErrorKind::Agent), "Agent");
193        assert_eq!(format!("{}", ErrorKind::Runtime), "Runtime");
194        assert_eq!(format!("{}", ErrorKind::Tool), "Tool");
195        assert_eq!(format!("{}", ErrorKind::Protocol), "Protocol");
196    }
197
198    #[test]
199    fn tool_error_classification() {
200        assert_eq!(SageError::tool("http failed").kind(), ErrorKind::Tool);
201        assert_eq!(SageError::tool("timeout").message(), "Tool error: timeout");
202    }
203
204    #[test]
205    fn protocol_error_classification() {
206        assert_eq!(SageError::protocol("unexpected message").kind(), ErrorKind::Protocol);
207        assert_eq!(
208            SageError::protocol("wrong sender").message(),
209            "Protocol error: wrong sender"
210        );
211    }
212}