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}
28
29impl std::fmt::Display for ErrorKind {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            ErrorKind::Llm => write!(f, "Llm"),
33            ErrorKind::Agent => write!(f, "Agent"),
34            ErrorKind::Runtime => write!(f, "Runtime"),
35            ErrorKind::Tool => write!(f, "Tool"),
36            ErrorKind::User => write!(f, "User"),
37        }
38    }
39}
40
41/// Error type for Sage runtime errors.
42///
43/// RFC-0007: This is exposed to Sage programs as the `Error` type with
44/// `.message` and `.kind` field accessors.
45#[derive(Debug, Error)]
46pub enum SageError {
47    /// Error from LLM inference.
48    #[error("LLM error: {0}")]
49    Llm(String),
50
51    /// Error from agent execution.
52    #[error("Agent error: {0}")]
53    Agent(String),
54
55    /// Type mismatch at runtime.
56    #[error("Type error: expected {expected}, got {got}")]
57    Type { expected: String, got: String },
58
59    /// HTTP request error.
60    #[error("HTTP error: {0}")]
61    Http(#[from] reqwest::Error),
62
63    /// JSON parsing error.
64    #[error("JSON error: {0}")]
65    Json(#[from] serde_json::Error),
66
67    /// Agent task was cancelled or panicked.
68    #[error("Agent task failed: {0}")]
69    JoinError(String),
70
71    /// RFC-0011: Error from tool execution.
72    #[error("Tool error: {0}")]
73    Tool(String),
74
75    /// User-raised error via `fail` expression.
76    #[error("{0}")]
77    User(String),
78}
79
80impl SageError {
81    /// RFC-0007: Get the error message as a String.
82    ///
83    /// This is exposed to Sage programs as `e.message`.
84    #[must_use]
85    pub fn message(&self) -> String {
86        self.to_string()
87    }
88
89    /// RFC-0007: Get the error kind classification.
90    ///
91    /// This is exposed to Sage programs as `e.kind`.
92    #[must_use]
93    pub fn kind(&self) -> ErrorKind {
94        match self {
95            SageError::Llm(_) | SageError::Json(_) => ErrorKind::Llm,
96            SageError::Agent(_) | SageError::JoinError(_) => ErrorKind::Agent,
97            SageError::Type { .. } => ErrorKind::Runtime,
98            // RFC-0011: Http errors are tool errors
99            SageError::Http(_) | SageError::Tool(_) => ErrorKind::Tool,
100            SageError::User(_) => ErrorKind::User,
101        }
102    }
103
104    /// Create an LLM error with a message.
105    #[must_use]
106    pub fn llm(msg: impl Into<String>) -> Self {
107        SageError::Llm(msg.into())
108    }
109
110    /// Create an agent error with a message.
111    #[must_use]
112    pub fn agent(msg: impl Into<String>) -> Self {
113        SageError::Agent(msg.into())
114    }
115
116    /// Create a type error.
117    #[must_use]
118    pub fn type_error(expected: impl Into<String>, got: impl Into<String>) -> Self {
119        SageError::Type {
120            expected: expected.into(),
121            got: got.into(),
122        }
123    }
124
125    /// RFC-0011: Create a tool error with a message.
126    #[must_use]
127    pub fn tool(msg: impl Into<String>) -> Self {
128        SageError::Tool(msg.into())
129    }
130
131    /// Create a user error via `fail` expression.
132    #[must_use]
133    pub fn user(msg: impl Into<String>) -> Self {
134        SageError::User(msg.into())
135    }
136}
137
138impl From<tokio::task::JoinError> for SageError {
139    fn from(e: tokio::task::JoinError) -> Self {
140        SageError::JoinError(e.to_string())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn error_kind_classification() {
150        assert_eq!(SageError::llm("test").kind(), ErrorKind::Llm);
151        assert_eq!(SageError::agent("test").kind(), ErrorKind::Agent);
152        assert_eq!(
153            SageError::type_error("Int", "String").kind(),
154            ErrorKind::Runtime
155        );
156    }
157
158    #[test]
159    fn error_message() {
160        let err = SageError::llm("inference failed");
161        assert_eq!(err.message(), "LLM error: inference failed");
162    }
163
164    #[test]
165    fn error_kind_display() {
166        assert_eq!(format!("{}", ErrorKind::Llm), "Llm");
167        assert_eq!(format!("{}", ErrorKind::Agent), "Agent");
168        assert_eq!(format!("{}", ErrorKind::Runtime), "Runtime");
169        assert_eq!(format!("{}", ErrorKind::Tool), "Tool");
170    }
171
172    #[test]
173    fn tool_error_classification() {
174        assert_eq!(SageError::tool("http failed").kind(), ErrorKind::Tool);
175        assert_eq!(SageError::tool("timeout").message(), "Tool error: timeout");
176    }
177}