Skip to main content

unified_agent_sdk/
error.rs

1//! Typed errors used by the unified SDK.
2//!
3//! [`ExecutorError`] is designed to preserve the phase of failure: configuration,
4//! spawn, execution, session lookup, availability, or serialization. This keeps
5//! orchestration code explicit and easier to recover from.
6
7use std::fmt;
8
9use thiserror::Error;
10
11/// Convenience result alias used by the unified executor APIs.
12pub type Result<T> = std::result::Result<T, ExecutorError>;
13
14/// Error type for executor lifecycle, transport, session, and normalization failures.
15///
16/// The variants are intentionally coarse enough to stay stable across providers,
17/// while still allowing callers to branch on the failure phase.
18#[derive(Error, Debug)]
19pub enum ExecutorError {
20    /// Underlying I/O error while interacting with local files or subprocess pipes.
21    #[error("IO error: {0}")]
22    Io(#[from] std::io::Error),
23
24    /// Executor process or SDK client could not be created.
25    #[error("Process spawn failed: {0}")]
26    SpawnFailed(String),
27
28    /// Executor process ran but the request failed during execution.
29    #[error("Process execution failed: {0}")]
30    ExecutionFailed(String),
31
32    /// Requested session identifier is unknown to the executor backend.
33    #[error("Session not found: {0}")]
34    SessionNotFound(String),
35
36    /// User-provided or resolved configuration is invalid.
37    #[error("Invalid configuration: {0}")]
38    InvalidConfig(String),
39
40    /// Executor binary or required runtime dependency is unavailable.
41    #[error("Executor unavailable: {0}")]
42    Unavailable(String),
43
44    /// JSON serialization or deserialization failed.
45    #[error("Serialization error: {0}")]
46    Serialization(#[from] serde_json::Error),
47
48    /// Miscellaneous error not covered by the typed variants above.
49    #[error("{0}")]
50    Other(String),
51}
52
53impl ExecutorError {
54    /// Returns a stable machine-readable error category.
55    ///
56    /// This is useful when logging or exporting metrics across different provider
57    /// backends without depending on formatted error messages.
58    pub fn error_type(&self) -> &'static str {
59        match self {
60            ExecutorError::Io(_) => "io",
61            ExecutorError::SpawnFailed(_) => "spawn_failed",
62            ExecutorError::ExecutionFailed(_) => "execution_failed",
63            ExecutorError::SessionNotFound(_) => "session_not_found",
64            ExecutorError::InvalidConfig(_) => "invalid_config",
65            ExecutorError::Unavailable(_) => "unavailable",
66            ExecutorError::Serialization(_) => "serialization",
67            ExecutorError::Other(_) => "other",
68        }
69    }
70
71    /// Builds [`ExecutorError::SpawnFailed`] with contextual operation details.
72    pub fn spawn_failed(context: impl AsRef<str>, error: impl fmt::Display) -> Self {
73        Self::SpawnFailed(format_with_context(context.as_ref(), error))
74    }
75
76    /// Builds [`ExecutorError::ExecutionFailed`] with contextual operation details.
77    pub fn execution_failed(context: impl AsRef<str>, error: impl fmt::Display) -> Self {
78        Self::ExecutionFailed(format_with_context(context.as_ref(), error))
79    }
80
81    /// Builds [`ExecutorError::InvalidConfig`] with contextual operation details.
82    pub fn invalid_config(context: impl AsRef<str>, error: impl fmt::Display) -> Self {
83        Self::InvalidConfig(format_with_context(context.as_ref(), error))
84    }
85
86    /// Builds [`ExecutorError::Unavailable`] with contextual operation details.
87    pub fn unavailable(context: impl AsRef<str>, error: impl fmt::Display) -> Self {
88        Self::Unavailable(format_with_context(context.as_ref(), error))
89    }
90
91    /// Builds [`ExecutorError::Other`] with contextual operation details.
92    pub fn other(context: impl AsRef<str>, error: impl fmt::Display) -> Self {
93        Self::Other(format_with_context(context.as_ref(), error))
94    }
95}
96
97fn format_with_context(context: &str, error: impl fmt::Display) -> String {
98    if context.is_empty() {
99        error.to_string()
100    } else {
101        format!("{context}: {error}")
102    }
103}