use thiserror::Error;
pub type SageResult<T> = Result<T, SageError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorKind {
Llm,
Agent,
Runtime,
Tool,
User,
Protocol,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Llm => write!(f, "Llm"),
ErrorKind::Agent => write!(f, "Agent"),
ErrorKind::Runtime => write!(f, "Runtime"),
ErrorKind::Tool => write!(f, "Tool"),
ErrorKind::User => write!(f, "User"),
ErrorKind::Protocol => write!(f, "Protocol"),
}
}
}
#[derive(Debug, Error)]
pub enum SageError {
#[error("LLM error: {0}")]
Llm(String),
#[error("Agent error: {0}")]
Agent(String),
#[error("Type error: expected {expected}, got {got}")]
Type { expected: String, got: String },
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Agent task failed: {0}")]
JoinError(String),
#[error("Tool error: {0}")]
Tool(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
User(String),
#[error("Supervisor error: {0}")]
Supervisor(String),
#[error("Protocol error: {0}")]
Protocol(String),
}
impl SageError {
#[must_use]
pub fn message(&self) -> String {
self.to_string()
}
#[must_use]
pub fn kind(&self) -> ErrorKind {
match self {
SageError::Llm(_) | SageError::Json(_) => ErrorKind::Llm,
SageError::Agent(_) | SageError::JoinError(_) | SageError::Supervisor(_) => {
ErrorKind::Agent
}
SageError::Type { .. } => ErrorKind::Runtime,
SageError::Http(_) | SageError::Tool(_) | SageError::Io(_) => ErrorKind::Tool,
SageError::User(_) => ErrorKind::User,
SageError::Protocol(_) => ErrorKind::Protocol,
}
}
#[must_use]
pub fn llm(msg: impl Into<String>) -> Self {
SageError::Llm(msg.into())
}
#[must_use]
pub fn agent(msg: impl Into<String>) -> Self {
SageError::Agent(msg.into())
}
#[must_use]
pub fn type_error(expected: impl Into<String>, got: impl Into<String>) -> Self {
SageError::Type {
expected: expected.into(),
got: got.into(),
}
}
#[must_use]
pub fn tool(msg: impl Into<String>) -> Self {
SageError::Tool(msg.into())
}
#[must_use]
pub fn user(msg: impl Into<String>) -> Self {
SageError::User(msg.into())
}
#[must_use]
pub fn protocol(msg: impl Into<String>) -> Self {
SageError::Protocol(msg.into())
}
}
#[cfg(not(target_arch = "wasm32"))]
impl From<tokio::task::JoinError> for SageError {
fn from(e: tokio::task::JoinError) -> Self {
SageError::JoinError(e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_kind_classification() {
assert_eq!(SageError::llm("test").kind(), ErrorKind::Llm);
assert_eq!(SageError::agent("test").kind(), ErrorKind::Agent);
assert_eq!(
SageError::type_error("Int", "String").kind(),
ErrorKind::Runtime
);
}
#[test]
fn error_message() {
let err = SageError::llm("inference failed");
assert_eq!(err.message(), "LLM error: inference failed");
}
#[test]
fn error_kind_display() {
assert_eq!(format!("{}", ErrorKind::Llm), "Llm");
assert_eq!(format!("{}", ErrorKind::Agent), "Agent");
assert_eq!(format!("{}", ErrorKind::Runtime), "Runtime");
assert_eq!(format!("{}", ErrorKind::Tool), "Tool");
assert_eq!(format!("{}", ErrorKind::Protocol), "Protocol");
}
#[test]
fn tool_error_classification() {
assert_eq!(SageError::tool("http failed").kind(), ErrorKind::Tool);
assert_eq!(SageError::tool("timeout").message(), "Tool error: timeout");
}
#[test]
fn protocol_error_classification() {
assert_eq!(
SageError::protocol("unexpected message").kind(),
ErrorKind::Protocol
);
assert_eq!(
SageError::protocol("wrong sender").message(),
"Protocol error: wrong sender"
);
}
}