use std::path::PathBuf;
#[cfg(feature = "rig")]
use rig_core::completion::CompletionError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Home(#[from] crate::home::HomeError),
#[error(transparent)]
Runtime(#[from] crate::runtime::RuntimeError),
#[error("model directory {path:?} is not loadable: {reason}")]
#[non_exhaustive]
ModelPath {
path: PathBuf,
reason: String,
},
#[error("failed to load model at {path:?}: {message}")]
#[non_exhaustive]
ModelLoad {
path: PathBuf,
message: String,
},
#[error("unsupported content: {0}")]
UnsupportedContent(String),
#[error("invalid generation request: {0}")]
InvalidRequest(String),
#[error("invalid Emelex configuration: {0}")]
InvalidConfiguration(String),
#[error("generation failed: {0}")]
Generation(String),
#[error("generation stream protocol failed: {0}")]
StreamProtocol(String),
#[error(
"context window exceeded: prompt {prompt_tokens} + output {max_output_tokens} > {limit}"
)]
ContextExceeded {
prompt_tokens: usize,
max_output_tokens: usize,
limit: usize,
},
#[error("capability {capability} is unavailable: {reason}")]
#[non_exhaustive]
CapabilityUnavailable {
capability: &'static str,
reason: String,
},
#[error("generation cancelled")]
Cancelled,
#[error("malformed tool call: {0}")]
MalformedToolCall(String),
#[error("invalid additional_params: {0}")]
InvalidParams(#[from] serde_json::Error),
#[error("inference channel {operation} failed")]
#[non_exhaustive]
InferenceChannel {
operation: &'static str,
},
#[error("inference queue is full")]
InferenceBusy,
#[error("inference job panicked")]
InferencePanic,
}
#[cfg(feature = "rig")]
impl From<Error> for CompletionError {
fn from(error: Error) -> Self {
match error {
Error::UnsupportedContent(_)
| Error::InvalidRequest(_)
| Error::InvalidConfiguration(_)
| Error::CapabilityUnavailable { .. }
| Error::InvalidParams(_) => Self::RequestError(Box::new(error)),
Error::Home(_)
| Error::Runtime(_)
| Error::ModelPath { .. }
| Error::ModelLoad { .. }
| Error::Generation(_)
| Error::StreamProtocol(_)
| Error::ContextExceeded { .. }
| Error::Cancelled
| Error::MalformedToolCall(_) => Self::ProviderError(error.to_string()),
Error::InferenceChannel { .. } | Error::InferenceBusy | Error::InferencePanic => {
Self::ProviderError(error.to_string())
}
}
}
}
pub fn from_engine(error: crate::engine::error::Error) -> Error {
match error {
crate::engine::error::Error::ContextExceeded {
prompt_tokens,
max_output_tokens,
limit,
} => Error::ContextExceeded {
prompt_tokens,
max_output_tokens,
limit,
},
crate::engine::error::Error::CapabilityUnavailable { capability, reason } => {
Error::CapabilityUnavailable { capability, reason }
}
crate::engine::error::Error::Cancelled => Error::Cancelled,
other => Error::Generation(other.to_string()),
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "rig")]
use rig_core::completion::CompletionError;
use super::*;
#[test]
fn engine_cancellation_preserves_public_error_identity() {
assert!(matches!(
from_engine(crate::engine::error::Error::Cancelled),
Error::Cancelled
));
}
#[test]
fn inference_channel_display_text_is_exact() {
assert_eq!(
Error::InferenceChannel {
operation: "submit"
}
.to_string(),
"inference channel submit failed"
);
assert_eq!(
Error::InferenceChannel {
operation: "receive"
}
.to_string(),
"inference channel receive failed"
);
}
#[test]
#[cfg(feature = "rig")]
fn inference_channel_converts_to_provider_error() {
let converted = CompletionError::from(Error::InferenceChannel {
operation: "receive",
});
assert!(matches!(
converted,
CompletionError::ProviderError(message)
if message == "inference channel receive failed"
));
}
}