Skip to main content

artifactize_core/agent/
error.rs

1//! Why an Agent review failed, as a request `errorCode`.
2
3use std::fmt;
4
5/// The `errorCode` of a request whose Agent review failed.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
8pub enum Code {
9    /// Credentials are missing, expired or rejected (HTTP 401 or 403).
10    Authentication,
11    /// A billing, credit or plan usage limit.
12    Quota,
13    /// Rate limited (HTTP 429) after the retries, or asked to wait past the deadline.
14    RateLimit,
15    /// A connection failure, an overloaded or failing server, or an interrupted stream,
16    /// after the retries.
17    Transient,
18    /// The review's `timeoutMs` deadline passed.
19    Timeout,
20    Cancelled,
21    /// `maxTokens` or `maxToolCalls` was exceeded.
22    ProviderBudgetExceeded,
23    /// No valid verdict after the one format repair.
24    InvalidResult,
25    /// The response's text degenerated into a long run of whitespace, after the retries; its
26    /// stream was stopped.
27    DegenerateOutput,
28    /// Any other provider failure: a rejected request, an unknown or different model,
29    /// an incomplete response or a malformed tool call.
30    ProviderError,
31    /// Anything else, such as unusable configuration or tools.
32    AgentError,
33}
34
35/// An Agent failure's code as its saved `errorCode`.
36impl From<Code> for crate::types::FailureCode {
37    fn from(code: Code) -> Self {
38        match code {
39            Code::Authentication => Self::Authentication,
40            Code::Quota => Self::Quota,
41            Code::RateLimit => Self::RateLimit,
42            Code::Transient => Self::Transient,
43            Code::Timeout => Self::Timeout,
44            Code::Cancelled => Self::Cancelled,
45            Code::ProviderBudgetExceeded => Self::ProviderBudgetExceeded,
46            Code::InvalidResult => Self::InvalidResult,
47            Code::DegenerateOutput => Self::DegenerateOutput,
48            Code::ProviderError => Self::ProviderError,
49            Code::AgentError => Self::AgentError,
50        }
51    }
52}
53
54impl Code {
55    pub fn as_str(self) -> &'static str {
56        match self {
57            Self::Authentication => "AUTHENTICATION",
58            Self::Quota => "QUOTA",
59            Self::RateLimit => "RATE_LIMIT",
60            Self::Transient => "TRANSIENT",
61            Self::Timeout => "TIMEOUT",
62            Self::Cancelled => "CANCELLED",
63            Self::ProviderBudgetExceeded => "PROVIDER_BUDGET_EXCEEDED",
64            Self::InvalidResult => "INVALID_RESULT",
65            Self::DegenerateOutput => "DEGENERATE_OUTPUT",
66            Self::ProviderError => "PROVIDER_ERROR",
67            Self::AgentError => "AGENT_ERROR",
68        }
69    }
70
71    /// Failures that a turn without output may retry.
72    pub fn retryable(self) -> bool {
73        matches!(self, Self::RateLimit | Self::Transient)
74    }
75
76    /// Failures that every later review on the backend would repeat: the Run stops
77    /// admitting reviews on it.
78    pub fn stops_backend(code: &str) -> bool {
79        code == Self::Authentication.as_str() || code == Self::Quota.as_str()
80    }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Failure {
85    pub code: Code,
86    pub message: String,
87    /// The provider's output failed once it had begun: it degenerated, stopped at the output
88    /// cap or broke off. The request was sound, so its turn may be sent again.
89    pub output_failed: bool,
90}
91
92impl Failure {
93    pub fn new(code: Code, message: impl Into<String>) -> Self {
94        Self {
95            code,
96            message: message.into(),
97            output_failed: false,
98        }
99    }
100
101    /// The same failure, marked as one of the provider's output.
102    pub fn of_output(self) -> Self {
103        Self {
104            output_failed: true,
105            ..self
106        }
107    }
108
109    pub fn cancelled() -> Self {
110        Self::new(Code::Cancelled, "Agent review was cancelled.")
111    }
112
113    pub fn timeout() -> Self {
114        Self::new(Code::Timeout, "Agent review timed out.")
115    }
116}
117
118/// An unclassified failure, such as a tool or configuration error.
119impl From<String> for Failure {
120    fn from(message: String) -> Self {
121        Self::new(Code::AgentError, message)
122    }
123}
124
125impl fmt::Display for Failure {
126    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127        formatter.write_str(&self.message)
128    }
129}