Skip to main content

rightkit_agent_engine_protocol/
retry_notice.rs

1//! Provider retry notices: a plain-data hook so a provider adapter can tell
2//! its caller "I am backing off and retrying" without owning any session
3//! state. The engine installs a notifier on the request metadata and turns
4//! each notice into a `providerRetrying` protocol event.
5
6use std::fmt;
7use std::sync::Arc;
8
9/// One pending provider retry.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct RetryNotice {
12    /// Short provider label (route host).
13    pub provider: String,
14    /// Why the previous attempt failed: `rate limited`, `HTTP 503`,
15    /// `connection error`, `stream interrupted`.
16    pub reason: String,
17    /// The retry about to run (1-based).
18    pub attempt: u32,
19    /// Retries the ladder allows.
20    pub max_attempts: u32,
21    /// Backoff the adapter sleeps before the retry.
22    pub delay_ms: u64,
23}
24
25type NotifyFn = dyn Fn(RetryNotice) + Send + Sync;
26
27/// Cloneable, optional retry sink. Default is a no-op.
28#[derive(Clone, Default)]
29pub struct RetryNotifier(Option<Arc<NotifyFn>>);
30
31impl RetryNotifier {
32    pub fn new(f: impl Fn(RetryNotice) + Send + Sync + 'static) -> Self {
33        Self(Some(Arc::new(f)))
34    }
35
36    pub fn notify(&self, notice: RetryNotice) {
37        if let Some(f) = &self.0 {
38            f(notice);
39        }
40    }
41}
42
43impl fmt::Debug for RetryNotifier {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(if self.0.is_some() {
46            "RetryNotifier(set)"
47        } else {
48            "RetryNotifier(none)"
49        })
50    }
51}
52
53impl PartialEq for RetryNotifier {
54    fn eq(&self, other: &Self) -> bool {
55        match (&self.0, &other.0) {
56            (None, None) => true,
57            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
58            _ => false,
59        }
60    }
61}
62
63impl Eq for RetryNotifier {}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use std::sync::Mutex;
69
70    #[test]
71    fn notifier_delivers_and_default_is_silent() {
72        RetryNotifier::default().notify(RetryNotice {
73            provider: "p".into(),
74            reason: "r".into(),
75            attempt: 1,
76            max_attempts: 3,
77            delay_ms: 10,
78        });
79        let seen = Arc::new(Mutex::new(Vec::new()));
80        let sink = seen.clone();
81        let n = RetryNotifier::new(move |notice| sink.lock().unwrap().push(notice.attempt));
82        n.notify(RetryNotice {
83            provider: "p".into(),
84            reason: "r".into(),
85            attempt: 2,
86            max_attempts: 3,
87            delay_ms: 10,
88        });
89        assert_eq!(*seen.lock().unwrap(), vec![2]);
90        assert_eq!(n.clone(), n);
91    }
92}