Skip to main content

kaynine_core/
error.rs

1use crate::ids::{ModelId, ProviderId};
2use crate::provider::TokenMeasurementSource;
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
7pub enum ProviderError {
8    #[error("authentication failed")]
9    Auth,
10    #[error("rate limited")]
11    RateLimit,
12    #[error("context overflow")]
13    ContextOverflow,
14    #[error("invalid request: {0}")]
15    InvalidRequest(String),
16    #[error("network error: {0}")]
17    Network(String),
18    #[error("protocol error: {0}")]
19    Protocol(String),
20    #[error("cancelled")]
21    Cancelled,
22    #[error("unknown provider error: {0}")]
23    Unknown(String),
24}
25
26#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
27pub enum ToolError {
28    #[error("invalid arguments: {0}")]
29    InvalidArguments(String),
30    #[error("execution failed: {0}")]
31    Execution(String),
32    #[error("cancelled")]
33    Cancelled,
34}
35
36#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
37pub enum CredentialError {
38    #[error("credential resolve failed: {0}")]
39    Resolve(String),
40}
41
42#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
43pub enum PromptError {
44    #[error("prompt render failed: {0}")]
45    Render(String),
46}
47
48#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
49pub enum ContextError {
50    #[error("context load failed: {0}")]
51    Load(String),
52}
53
54#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
55pub enum StoreError {
56    #[error("store io error: {0}")]
57    Io(String),
58    #[error("store internal error: {0}")]
59    Internal(String),
60}
61
62#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
63pub enum PolicyError {
64    #[error("policy evaluation failed: {0}")]
65    Evaluate(String),
66}
67
68#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
69pub enum KaynineError {
70    #[error("invalid request")]
71    InvalidRequest,
72    #[error("session not found")]
73    SessionNotFound,
74    #[error("session busy")]
75    SessionBusy,
76    #[error("run already active")]
77    RunAlreadyActive,
78    #[error("revision conflict")]
79    RevisionConflict,
80    #[error("idempotency conflict")]
81    IdempotencyConflict,
82    #[error("approval expired")]
83    ApprovalExpired,
84    #[error("store error: {0}")]
85    Store(#[from] StoreError),
86    #[error("internal error")]
87    Internal,
88}
89
90#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
91pub enum RunFailureReason {
92    #[error("agent definition unavailable")]
93    DefinitionUnavailable,
94    #[error("model capability mismatch: {requirement} (provider {provider}, model {model})")]
95    ModelCapabilityMismatch {
96        requirement: String,
97        provider: ProviderId,
98        model: ModelId,
99    },
100    #[error("context budget exceeded: {measured_tokens} > {available_tokens}")]
101    ContextBudgetExceeded {
102        measured_tokens: u64,
103        available_tokens: u64,
104        source: TokenMeasurementSource,
105    },
106    #[error("provider failure: {0}")]
107    Provider(#[from] ProviderError),
108    #[error("prompt failure: {0}")]
109    Prompt(#[from] PromptError),
110    #[error("context provider failure: {0}")]
111    Context(#[from] ContextError),
112    #[error("store failure: {0}")]
113    Store(#[from] StoreError),
114    #[error("internal error")]
115    Internal,
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn kaynine_error_roundtrips() {
124        let err = KaynineError::SessionBusy;
125        let json = serde_json::to_string(&err).unwrap();
126        let back: KaynineError = serde_json::from_str(&json).unwrap();
127        assert_eq!(back, err);
128    }
129
130    #[test]
131    fn run_failure_reason_roundtrips_with_payload() {
132        let reason = RunFailureReason::ContextBudgetExceeded {
133            measured_tokens: 100,
134            available_tokens: 50,
135            source: TokenMeasurementSource::Heuristic,
136        };
137        let json = serde_json::to_string(&reason).unwrap();
138        let back: RunFailureReason = serde_json::from_str(&json).unwrap();
139        assert_eq!(back, reason);
140    }
141
142    #[test]
143    fn policy_error_roundtrips() {
144        let err = PolicyError::Evaluate("boom".into());
145        let json = serde_json::to_string(&err).unwrap();
146        let back: PolicyError = serde_json::from_str(&json).unwrap();
147        assert_eq!(back, err);
148    }
149
150    #[test]
151    fn provider_error_covers_spec_categories() {
152        let categories = [
153            ProviderError::Auth,
154            ProviderError::RateLimit,
155            ProviderError::ContextOverflow,
156            ProviderError::InvalidRequest("x".into()),
157            ProviderError::Network("x".into()),
158            ProviderError::Protocol("x".into()),
159            ProviderError::Cancelled,
160            ProviderError::Unknown("x".into()),
161        ];
162        for c in categories {
163            let json = serde_json::to_string(&c).unwrap();
164            let back: ProviderError = serde_json::from_str(&json).unwrap();
165            assert_eq!(back, c);
166        }
167    }
168}