1use std::time::Duration;
6
7pub type Result<T> = std::result::Result<T, CognisError>;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum InterruptKind {
13 Before,
15 After,
17}
18
19impl std::fmt::Display for InterruptKind {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 InterruptKind::Before => write!(f, "before"),
23 InterruptKind::After => write!(f, "after"),
24 }
25 }
26}
27
28#[derive(Debug, thiserror::Error)]
30pub enum CognisError {
31 #[error("provider `{provider}` error: {message}")]
33 Provider {
34 provider: String,
36 message: String,
38 },
39
40 #[error("rate limited; retry after {retry_after_ms}ms")]
42 RateLimited {
43 retry_after_ms: u64,
45 },
46
47 #[error("authentication failed: {0}")]
49 AuthenticationFailed(String),
50
51 #[error("tool `{name}` failed: {reason}")]
53 Tool {
54 name: String,
56 reason: String,
58 },
59
60 #[error("tool validation: {0}")]
62 ToolValidationError(String),
63
64 #[error("configuration: {0}")]
66 Configuration(String),
67
68 #[error("network error{}: {message}", status_code.map(|c| format!(" (status {c})")).unwrap_or_default())]
70 Network {
71 status_code: Option<u16>,
73 message: String,
75 },
76
77 #[error("`{operation}` timed out after {timeout_ms}ms")]
79 Timeout {
80 operation: String,
82 timeout_ms: u64,
84 },
85
86 #[error("operation cancelled")]
88 Cancelled,
89
90 #[error("graph recursion limit ({limit}) exceeded")]
92 RecursionLimit {
93 limit: u32,
95 },
96
97 #[error("graph interrupted {kind} node `{node}` at step {step} (run_id {run_id})")]
100 GraphInterrupted {
101 run_id: uuid::Uuid,
103 step: u64,
105 node: String,
107 kind: InterruptKind,
109 },
110
111 #[error("serialization error: {0}")]
113 Serialization(String),
114
115 #[error("internal error: {0}")]
117 Internal(String),
118}
119
120impl CognisError {
121 pub fn category(&self) -> &'static str {
123 match self {
124 Self::Provider { .. } => "provider",
125 Self::RateLimited { .. } => "rate_limit",
126 Self::AuthenticationFailed(_) => "auth",
127 Self::Tool { .. } => "tool",
128 Self::ToolValidationError(_) => "tool_validation",
129 Self::Configuration(_) => "config",
130 Self::Network { .. } => "network",
131 Self::Timeout { .. } => "timeout",
132 Self::Cancelled => "cancelled",
133 Self::RecursionLimit { .. } => "recursion_limit",
134 Self::GraphInterrupted { .. } => "graph_interrupted",
135 Self::Serialization(_) => "serialization",
136 Self::Internal(_) => "internal",
137 }
138 }
139
140 pub fn is_retryable(&self) -> bool {
142 matches!(
143 self,
144 Self::RateLimited { .. }
145 | Self::Network { .. }
146 | Self::Timeout { .. }
147 | Self::Provider { .. }
148 )
149 }
150
151 pub fn retry_delay(&self) -> Option<Duration> {
153 match self {
154 Self::RateLimited { retry_after_ms } => Some(Duration::from_millis(*retry_after_ms)),
155 Self::Timeout { timeout_ms, .. } => Some(Duration::from_millis(*timeout_ms / 2)),
156 _ => None,
157 }
158 }
159}
160
161impl From<serde_json::Error> for CognisError {
162 fn from(e: serde_json::Error) -> Self {
163 Self::Serialization(e.to_string())
164 }
165}