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