1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! Retry with exponential backoff and jitter for provider calls.
//!
//! ARCHITECTURE NOTE: Why retry at the agent loop level (not the provider level)?
//!
//! Retrying is a cross-cutting concern — all 7 providers share the same retry logic.
//! By handling it in stream_assistant_response() (agent_loop.rs), we avoid duplicating
//! retry logic in every provider. Providers simply return ProviderError::RateLimited
//! or ProviderError::Network, and this module decides what to do.
use crateProviderError;
use ;
use Duration;
use warn;
/// Configuration for automatic retry of transient provider errors.
///
/// Defaults: 3 retries, 1s initial delay, 2x backoff, 30s max delay.
/// Use `RetryConfig::none()` to disable retries entirely.
/*
ARCHITECTURE: Exponential backoff + jitter — why both?
Exponential backoff (delay doubles each attempt) prevents thundering herd:
if 1000 clients all hit a rate limit at the same time and all retry after
exactly 1s, they'll hit the limit again simultaneously. Doubling adds space.
Jitter (±20% random noise) prevents synchronized retries even with backoff:
two clients with the same delay would still retry at the same moment.
With jitter, their windows are offset, reducing server load spikes.
Attempt 1: 1000ms * (0.8–1.2) = 800–1200ms
Attempt 2: 2000ms * (0.8–1.2) = 1600–2400ms
Attempt 3: 4000ms * (0.8–1.2) = 3200–4800ms → capped at 30s
*/
/*
RUST QUIRK: `impl Default` — explicitly defining the "zero value"
Rust has no constructor syntax. Instead, the `Default` trait provides `::default()`.
We implement it manually here because the defaults are non-trivial constants.
If all fields were 0/false/empty-string, we could use `#[derive(Default)]` to get
it for free. But initial_delay_ms = 1000 and backoff_multiplier = 2.0 are not
the "zero values" of u64 and f64 (which are 0 and 0.0 respectively).
Usage examples:
let cfg = RetryConfig::default(); // 3 retries, 1s delay, 2x backoff
let cfg = RetryConfig { max_retries: 5, ..Default::default() }; // override one field
let cfg = RetryConfig::none(); // 0 retries (disable retries)
*/
/*
RUST QUIRK: Adding methods to a type defined in another module — `impl OtherType`
`ProviderError` is defined in provider/traits.rs, but we add retry-related methods
to it HERE in retry.rs. Rust allows this as long as either:
a) The type is defined in THIS crate (ProviderError is — it's in our crate)
b) You own the trait being implemented
This is different from Python where methods must live in the class definition.
In Rust, you can add methods to your own types from any module in the crate.
The split is intentional: ProviderError is a pure type in provider/traits.rs;
retry logic lives here in retry.rs (separation of concerns).
*/
/// Log a retry attempt.
/*
RUST QUIRK: `pub(crate)` visibility — "public within this crate only"
`pub(crate)` is between `pub` (visible everywhere) and private (visible only in this module).
It means: "any module in this crate can use this function, but external consumers cannot."
Here, log_retry is called from agent_loop.rs (same crate) but shouldn't be part of
the public API that library users see.
`warn!()` is a logging macro from the `tracing` crate (similar to Python's logging.warning()).
It uses the same syntax as println! but routes to the tracing subscriber configured by the user.
`{:.1}` format: float with 1 decimal place → "2.5" not "2.500000"
*/
pub