1use std::future::Future;
8use std::pin::Pin;
9
10pub mod agent;
11pub mod backend;
12pub mod middleware;
13pub mod protocol;
14
15pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
17
18#[derive(Debug, thiserror::Error)]
20#[error("{message}")]
21pub struct ProviderError {
22 message: String,
23 status: Option<u16>,
24 retryable: bool,
25 retry_after: Option<String>,
26 kind: ProviderErrorKind,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30enum ProviderErrorKind {
31 Other,
32 StreamInterrupted,
33}
34
35impl ProviderError {
36 #[must_use]
38 pub fn new(message: impl Into<String>) -> Self {
39 Self {
40 message: message.into(),
41 status: None,
42 retryable: false,
43 retry_after: None,
44 kind: ProviderErrorKind::Other,
45 }
46 }
47
48 #[must_use]
50 pub fn retryable(message: impl Into<String>) -> Self {
51 Self {
52 retryable: true,
53 ..Self::new(message)
54 }
55 }
56
57 #[must_use]
59 pub fn stream_interrupted(retry_after: Option<String>) -> Self {
60 Self {
61 message: "model response stream was interrupted".into(),
62 status: None,
63 retryable: true,
64 retry_after,
65 kind: ProviderErrorKind::StreamInterrupted,
66 }
67 }
68
69 pub(crate) fn http(
70 message: impl Into<String>,
71 status: u16,
72 retry_after: Option<String>,
73 ) -> Self {
74 Self {
75 message: message.into(),
76 status: Some(status),
77 retryable: status == 408 || status == 429 || (500..=599).contains(&status),
78 retry_after,
79 kind: ProviderErrorKind::Other,
80 }
81 }
82
83 #[must_use]
85 pub fn status(&self) -> Option<u16> {
86 self.status
87 }
88
89 #[must_use]
91 pub fn is_retryable(&self) -> bool {
92 self.retryable
93 }
94
95 #[must_use]
97 pub fn is_stream_interrupted(&self) -> bool {
98 self.kind == ProviderErrorKind::StreamInterrupted
99 }
100
101 #[must_use]
103 pub fn retry_after(&self) -> Option<&str> {
104 self.retry_after.as_deref()
105 }
106}
107
108impl From<String> for ProviderError {
109 fn from(message: String) -> Self {
110 Self::new(message)
111 }
112}
113
114impl From<&str> for ProviderError {
115 fn from(message: &str) -> Self {
116 Self::new(message)
117 }
118}
119
120#[derive(Debug, thiserror::Error)]
122pub enum Error {
123 #[error("configuration error: {0}")]
124 Config(String),
125 #[error("duplicate registration: {0}")]
126 Duplicate(String),
127 #[error("unknown registration: {0}")]
128 Unknown(String),
129 #[error("provider error: {0}")]
130 Provider(#[from] ProviderError),
131 #[error("authentication error: {0}")]
132 Auth(String),
133 #[error("sandbox rejected path: {0}")]
134 Sandbox(String),
135 #[error("tool error: {0}")]
136 Tool(String),
137 #[error("checkpoint error: {0}")]
138 Checkpoint(String),
139 #[error("agent busy: {0}")]
140 Busy(String),
141 #[error("agent stopped: {0}")]
142 Stopped(String),
143 #[error("{primary}; rollback failed: {rollback}")]
144 Rollback {
145 primary: Box<Error>,
146 rollback: Box<Error>,
147 },
148 #[error(transparent)]
149 Io(#[from] std::io::Error),
150 #[error(transparent)]
151 Http(#[from] reqwest::Error),
152 #[error(transparent)]
153 Json(#[from] serde_json::Error),
154 #[error("checkpoint storage error")]
155 Sqlite(
156 #[source]
157 #[from]
158 rusqlite::Error,
159 ),
160}
161
162pub type Result<T> = std::result::Result<T, Error>;
164
165pub(crate) fn preview_json(value: &serde_json::Value) -> String {
166 let value = value.to_string();
167 if value.len() <= 10_000 {
168 return value;
169 }
170 format!("{}…", truncate_utf8(&value, 10_000))
171}
172
173pub(crate) fn truncate_utf8(value: &str, max_bytes: usize) -> &str {
174 let mut end = value.len().min(max_bytes);
175 while !value.is_char_boundary(end) {
176 end -= 1;
177 }
178 &value[..end]
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn sqlite_errors_do_not_expose_engine_messages() {
187 let error = Error::from(rusqlite::Error::InvalidQuery);
188
189 assert_eq!(error.to_string(), "checkpoint storage error");
190 }
191}