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}
23
24impl ProviderError {
25 #[must_use]
27 pub fn new(message: impl Into<String>) -> Self {
28 Self {
29 message: message.into(),
30 status: None,
31 retryable: false,
32 retry_after: None,
33 }
34 }
35
36 #[must_use]
38 pub fn retryable(message: impl Into<String>) -> Self {
39 Self {
40 retryable: true,
41 ..Self::new(message)
42 }
43 }
44
45 pub(crate) fn http(
46 message: impl Into<String>,
47 status: u16,
48 retry_after: Option<String>,
49 ) -> Self {
50 Self {
51 message: message.into(),
52 status: Some(status),
53 retryable: status == 408 || status == 429 || (500..=599).contains(&status),
54 retry_after,
55 }
56 }
57
58 #[must_use]
60 pub fn status(&self) -> Option<u16> {
61 self.status
62 }
63
64 #[must_use]
66 pub fn is_retryable(&self) -> bool {
67 self.retryable
68 }
69
70 #[must_use]
72 pub fn retry_after(&self) -> Option<&str> {
73 self.retry_after.as_deref()
74 }
75}
76
77impl From<String> for ProviderError {
78 fn from(message: String) -> Self {
79 Self::new(message)
80 }
81}
82
83impl From<&str> for ProviderError {
84 fn from(message: &str) -> Self {
85 Self::new(message)
86 }
87}
88
89#[derive(Debug, thiserror::Error)]
91pub enum Error {
92 #[error("configuration error: {0}")]
93 Config(String),
94 #[error("duplicate registration: {0}")]
95 Duplicate(String),
96 #[error("unknown registration: {0}")]
97 Unknown(String),
98 #[error("provider error: {0}")]
99 Provider(#[from] ProviderError),
100 #[error("authentication error: {0}")]
101 Auth(String),
102 #[error("sandbox rejected path: {0}")]
103 Sandbox(String),
104 #[error("tool error: {0}")]
105 Tool(String),
106 #[error("checkpoint error: {0}")]
107 Checkpoint(String),
108 #[error("agent busy: {0}")]
109 Busy(String),
110 #[error("agent stopped: {0}")]
111 Stopped(String),
112 #[error("{primary}; rollback failed: {rollback}")]
113 Rollback {
114 primary: Box<Error>,
115 rollback: Box<Error>,
116 },
117 #[error(transparent)]
118 Io(#[from] std::io::Error),
119 #[error(transparent)]
120 Http(#[from] reqwest::Error),
121 #[error(transparent)]
122 Json(#[from] serde_json::Error),
123 #[error("checkpoint storage error")]
124 Sqlite(
125 #[source]
126 #[from]
127 rusqlite::Error,
128 ),
129}
130
131pub type Result<T> = std::result::Result<T, Error>;
133
134pub(crate) fn preview_json(value: &serde_json::Value) -> String {
135 let value = value.to_string();
136 if value.len() <= 10_000 {
137 return value;
138 }
139 format!("{}…", truncate_utf8(&value, 10_000))
140}
141
142pub(crate) fn truncate_utf8(value: &str, max_bytes: usize) -> &str {
143 let mut end = value.len().min(max_bytes);
144 while !value.is_char_boundary(end) {
145 end -= 1;
146 }
147 &value[..end]
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn sqlite_errors_do_not_expose_engine_messages() {
156 let error = Error::from(rusqlite::Error::InvalidQuery);
157
158 assert_eq!(error.to_string(), "checkpoint storage error");
159 }
160}