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 or once the turn had produced output.
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; its stream was stopped.
26    DegenerateOutput,
27    /// Any other provider failure: a rejected request, an unknown or different model,
28    /// an incomplete response or a malformed tool call.
29    ProviderError,
30    /// Anything else, such as unusable configuration or tools.
31    AgentError,
32}
33
34/// An Agent failure's code as its saved `errorCode`.
35impl From<Code> for crate::types::FailureCode {
36    fn from(code: Code) -> Self {
37        match code {
38            Code::Authentication => Self::Authentication,
39            Code::Quota => Self::Quota,
40            Code::RateLimit => Self::RateLimit,
41            Code::Transient => Self::Transient,
42            Code::Timeout => Self::Timeout,
43            Code::Cancelled => Self::Cancelled,
44            Code::ProviderBudgetExceeded => Self::ProviderBudgetExceeded,
45            Code::InvalidResult => Self::InvalidResult,
46            Code::DegenerateOutput => Self::DegenerateOutput,
47            Code::ProviderError => Self::ProviderError,
48            Code::AgentError => Self::AgentError,
49        }
50    }
51}
52
53impl Code {
54    pub fn as_str(self) -> &'static str {
55        match self {
56            Self::Authentication => "AUTHENTICATION",
57            Self::Quota => "QUOTA",
58            Self::RateLimit => "RATE_LIMIT",
59            Self::Transient => "TRANSIENT",
60            Self::Timeout => "TIMEOUT",
61            Self::Cancelled => "CANCELLED",
62            Self::ProviderBudgetExceeded => "PROVIDER_BUDGET_EXCEEDED",
63            Self::InvalidResult => "INVALID_RESULT",
64            Self::DegenerateOutput => "DEGENERATE_OUTPUT",
65            Self::ProviderError => "PROVIDER_ERROR",
66            Self::AgentError => "AGENT_ERROR",
67        }
68    }
69
70    /// Failures that a turn without output may retry.
71    pub fn retryable(self) -> bool {
72        matches!(self, Self::RateLimit | Self::Transient)
73    }
74
75    /// Failures that every later review on the backend would repeat: the Run stops
76    /// admitting reviews on it.
77    pub fn stops_backend(code: &str) -> bool {
78        code == Self::Authentication.as_str() || code == Self::Quota.as_str()
79    }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct Failure {
84    pub code: Code,
85    pub message: String,
86}
87
88impl Failure {
89    pub fn new(code: Code, message: impl Into<String>) -> Self {
90        Self {
91            code,
92            message: message.into(),
93        }
94    }
95
96    pub fn cancelled() -> Self {
97        Self::new(Code::Cancelled, "Agent review was cancelled.")
98    }
99
100    pub fn timeout() -> Self {
101        Self::new(Code::Timeout, "Agent review timed out.")
102    }
103}
104
105/// An unclassified failure, such as a tool or configuration error.
106impl From<String> for Failure {
107    fn from(message: String) -> Self {
108        Self::new(Code::AgentError, message)
109    }
110}
111
112impl fmt::Display for Failure {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        formatter.write_str(&self.message)
115    }
116}