1use std::fmt;
2use std::time::Duration;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct RateLimitEvent {
7 pub worker_id: usize,
9 pub attempt: u32,
11 pub delay: Duration,
13 pub retry_after: Option<Duration>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum PoolError {
20 Spawn(String),
22 Rpc(String),
24 RateLimited {
26 retry_after: Option<Duration>,
28 },
29 QuotaExceeded,
31 WorkerCrashed {
33 worker_id: usize,
35 message: String,
37 },
38 ParseVerdict(String),
40 Timeout {
42 worker_id: usize,
44 timeout: Duration,
46 },
47 NoLiveWorkers,
49 Closed,
51}
52
53impl fmt::Display for PoolError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Spawn(message) => write!(f, "spawn codex-acp: {message}"),
57 Self::Rpc(message) => write!(f, "codex-acp rpc error: {message}"),
58 Self::RateLimited { retry_after } => {
59 write!(f, "codex-acp rate limited")?;
60 if let Some(retry_after) = retry_after {
61 write!(f, " retry_after={retry_after:?}")?;
62 }
63 Ok(())
64 }
65 Self::QuotaExceeded => write!(f, "codex-acp usage quota exceeded"),
66 Self::WorkerCrashed { worker_id, message } => {
67 write!(f, "rubric worker {worker_id} crashed: {message}")
68 }
69 Self::ParseVerdict(message) => write!(f, "parse rubric verdict: {message}"),
70 Self::Timeout { worker_id, timeout } => {
71 write!(f, "rubric worker {worker_id} timed out after {timeout:?}")
72 }
73 Self::NoLiveWorkers => write!(f, "no live rubric workers"),
74 Self::Closed => write!(f, "rubric pool is closed"),
75 }
76 }
77}
78
79impl std::error::Error for PoolError {}
80
81#[derive(Debug)]
83#[non_exhaustive]
84pub enum RubricError {
85 ReadPng {
87 path: std::path::PathBuf,
89 source: std::io::Error,
91 },
92 Pool(PoolError),
94 ParseVerdict {
96 text: String,
98 source: serde_json::Error,
100 },
101 Assertion {
103 name: String,
105 reason: String,
107 anomalies: Vec<String>,
109 },
110}
111
112impl fmt::Display for RubricError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::ReadPng { path, source } => {
116 write!(f, "read png {}: {source}", path.display())
117 }
118 Self::Pool(error) => error.fmt(f),
119 Self::ParseVerdict { text, source } => {
120 write!(f, "parse verdict from {text:?}: {source}")
121 }
122 Self::Assertion {
123 name,
124 reason,
125 anomalies,
126 } => {
127 write!(f, "[{name}] {reason} (anomalies: {anomalies:?})")
128 }
129 }
130 }
131}
132
133impl std::error::Error for RubricError {
134 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135 match self {
136 Self::ReadPng { source, .. } => Some(source),
137 Self::Pool(error) => Some(error),
138 Self::ParseVerdict { source, .. } => Some(source),
139 Self::Assertion { .. } => None,
140 }
141 }
142}
143
144impl From<PoolError> for RubricError {
145 fn from(error: PoolError) -> Self {
146 Self::Pool(error)
147 }
148}