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