use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
SessionNotFound,
SessionAlreadyExists,
CommandDenied,
CwdDenied,
PtyStartFailed,
PtyKillFailed,
PtyReadFailed,
PtyWriteFailed,
ResizeFailed,
BackendDisconnected,
SnapshotBusy,
InvalidMessage,
HistoryNotConfigured,
HistoryReadFailed,
StaleTranscriptCursor,
TranscriptSnapshotTooLarge,
HistoryWriteFailed,
ConversationNotFound,
ConversationActive,
CatalogNotConfigured,
CatalogAuthenticationFailed,
CatalogRateLimited,
CatalogUnavailable,
CatalogResponseInvalid,
SkillNotFound,
SkillInvalid,
SkillAlreadyInstalled,
SkillConflict,
SkillScopeDenied,
SkillDownloadFailed,
SkillFilesystemFailed,
PathDenied,
DirectoryNotFound,
NotADirectory,
PairingStorageFailed,
PairingAuthenticationFailed,
}
impl ErrorCode {
pub fn as_str(self) -> &'static str {
match self {
Self::SessionNotFound => "SESSION_NOT_FOUND",
Self::SessionAlreadyExists => "SESSION_ALREADY_EXISTS",
Self::CommandDenied => "COMMAND_DENIED",
Self::CwdDenied => "CWD_DENIED",
Self::PtyStartFailed => "PTY_START_FAILED",
Self::PtyKillFailed => "PTY_KILL_FAILED",
Self::PtyReadFailed => "PTY_READ_FAILED",
Self::PtyWriteFailed => "PTY_WRITE_FAILED",
Self::ResizeFailed => "RESIZE_FAILED",
Self::BackendDisconnected => "BACKEND_DISCONNECTED",
Self::SnapshotBusy => "SNAPSHOT_BUSY",
Self::InvalidMessage => "INVALID_MESSAGE",
Self::HistoryNotConfigured => "HISTORY_NOT_CONFIGURED",
Self::HistoryReadFailed => "HISTORY_READ_FAILED",
Self::StaleTranscriptCursor => "STALE_TRANSCRIPT_CURSOR",
Self::TranscriptSnapshotTooLarge => "TRANSCRIPT_SNAPSHOT_TOO_LARGE",
Self::HistoryWriteFailed => "HISTORY_WRITE_FAILED",
Self::ConversationNotFound => "CONVERSATION_NOT_FOUND",
Self::ConversationActive => "CONVERSATION_ACTIVE",
Self::CatalogNotConfigured => "CATALOG_NOT_CONFIGURED",
Self::CatalogAuthenticationFailed => "CATALOG_AUTHENTICATION_FAILED",
Self::CatalogRateLimited => "CATALOG_RATE_LIMITED",
Self::CatalogUnavailable => "CATALOG_UNAVAILABLE",
Self::CatalogResponseInvalid => "CATALOG_RESPONSE_INVALID",
Self::SkillNotFound => "SKILL_NOT_FOUND",
Self::SkillInvalid => "SKILL_INVALID",
Self::SkillAlreadyInstalled => "SKILL_ALREADY_INSTALLED",
Self::SkillConflict => "SKILL_CONFLICT",
Self::SkillScopeDenied => "SKILL_SCOPE_DENIED",
Self::SkillDownloadFailed => "SKILL_DOWNLOAD_FAILED",
Self::SkillFilesystemFailed => "SKILL_FILESYSTEM_FAILED",
Self::PathDenied => "PATH_DENIED",
Self::DirectoryNotFound => "DIRECTORY_NOT_FOUND",
Self::NotADirectory => "NOT_A_DIRECTORY",
Self::PairingStorageFailed => "PAIRING_STORAGE_FAILED",
Self::PairingAuthenticationFailed => "PAIRING_AUTHENTICATION_FAILED",
}
}
}
#[derive(Debug, Error)]
#[error("{code:?}: {message}")]
pub struct AgentError {
code: ErrorCode,
message: String,
}
impl AgentError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
}
}
pub fn code(&self) -> ErrorCode {
self.code
}
pub fn message(&self) -> &str {
&self.message
}
}
pub type AgentResult<T> = Result<T, AgentError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_code_serializes_as_protocol_string() {
assert_eq!(ErrorCode::InvalidMessage.as_str(), "INVALID_MESSAGE");
assert_eq!(ErrorCode::PtyKillFailed.as_str(), "PTY_KILL_FAILED");
assert_eq!(ErrorCode::SnapshotBusy.as_str(), "SNAPSHOT_BUSY");
assert_eq!(
ErrorCode::StaleTranscriptCursor.as_str(),
"STALE_TRANSCRIPT_CURSOR"
);
assert_eq!(
ErrorCode::TranscriptSnapshotTooLarge.as_str(),
"TRANSCRIPT_SNAPSHOT_TOO_LARGE"
);
assert_eq!(
ErrorCode::ConversationActive.as_str(),
"CONVERSATION_ACTIVE"
);
}
#[test]
fn skill_error_codes_have_stable_protocol_strings() {
let cases = [
(ErrorCode::CatalogNotConfigured, "CATALOG_NOT_CONFIGURED"),
(
ErrorCode::CatalogAuthenticationFailed,
"CATALOG_AUTHENTICATION_FAILED",
),
(ErrorCode::CatalogRateLimited, "CATALOG_RATE_LIMITED"),
(ErrorCode::CatalogUnavailable, "CATALOG_UNAVAILABLE"),
(
ErrorCode::CatalogResponseInvalid,
"CATALOG_RESPONSE_INVALID",
),
(ErrorCode::SkillNotFound, "SKILL_NOT_FOUND"),
(ErrorCode::SkillInvalid, "SKILL_INVALID"),
(ErrorCode::SkillAlreadyInstalled, "SKILL_ALREADY_INSTALLED"),
(ErrorCode::SkillConflict, "SKILL_CONFLICT"),
(ErrorCode::SkillScopeDenied, "SKILL_SCOPE_DENIED"),
(ErrorCode::SkillDownloadFailed, "SKILL_DOWNLOAD_FAILED"),
(ErrorCode::SkillFilesystemFailed, "SKILL_FILESYSTEM_FAILED"),
];
for (code, expected) in cases {
assert_eq!(code.as_str(), expected);
}
}
}