lash_core/provider/
handle.rs1use super::support::*;
2
3#[derive(Debug)]
5pub struct ProviderComponents {
6 pub provider: Box<dyn Provider>,
7 pub failure_classifier: Arc<dyn ProviderFailureClassifier>,
8 pub rate_limiter: Arc<ProviderRateLimiter>,
9}
10
11impl ProviderComponents {
12 pub fn new(provider: Box<dyn Provider>) -> Self {
13 let options = provider.options();
14 Self {
15 provider,
16 failure_classifier: Arc::new(DefaultProviderFailureClassifier),
17 rate_limiter: Arc::new(ProviderRateLimiter::new(options.reliability.rate_limits)),
18 }
19 }
20
21 pub fn map_provider(
23 mut self,
24 map: impl FnOnce(Box<dyn Provider>) -> Box<dyn Provider>,
25 ) -> Self {
26 self.provider = map(self.provider);
27 self
28 }
29
30 pub fn with_failure_classifier(
31 mut self,
32 classifier: Arc<dyn ProviderFailureClassifier>,
33 ) -> Self {
34 self.failure_classifier = classifier;
35 self
36 }
37
38 pub fn with_clock(mut self, clock: Arc<dyn crate::Clock>) -> Self {
39 let options = self.provider.options();
40 self.rate_limiter = Arc::new(ProviderRateLimiter::with_clock(
41 options.reliability.rate_limits,
42 clock,
43 ));
44 self
45 }
46}
47
48impl Clone for ProviderComponents {
49 fn clone(&self) -> Self {
50 Self {
51 provider: self.provider.clone_boxed(),
52 failure_classifier: Arc::clone(&self.failure_classifier),
53 rate_limiter: Arc::clone(&self.rate_limiter),
54 }
55 }
56}
57
58pub struct ProviderHandle {
61 components: ProviderComponents,
62}
63
64impl ProviderHandle {
65 pub fn new(components: ProviderComponents) -> Self {
66 Self { components }
67 }
68
69 pub fn unconfigured() -> Self {
70 Self::new(UnconfiguredProvider::default().into_components())
71 }
72
73 pub fn components(&self) -> &ProviderComponents {
74 &self.components
75 }
76
77 pub fn components_mut(&mut self) -> &mut ProviderComponents {
78 &mut self.components
79 }
80
81 pub fn with_clock(mut self, clock: Arc<dyn crate::Clock>) -> Self {
82 self.components = self.components.with_clock(clock);
83 self
84 }
85
86 pub fn kind(&self) -> &'static str {
87 self.components.provider.kind()
88 }
89
90 pub fn options(&self) -> ProviderOptions {
91 self.components.provider.options()
92 }
93
94 pub fn set_options(&mut self, options: ProviderOptions) {
95 self.components
96 .rate_limiter
97 .configure(options.reliability.rate_limits.clone());
98 self.components.provider.set_options(options)
99 }
100
101 pub fn requires_streaming(&self) -> bool {
102 self.components.provider.requires_streaming()
103 }
104
105 pub async fn complete(
106 &mut self,
107 request: LlmRequest,
108 ) -> Result<LlmResponse, LlmTransportError> {
109 let reliability = self.options().reliability;
110 let attempts = reliability.retry.attempts();
111 let mut attempt = 0;
112 let throttle_budget = Duration::from_millis(reliability.retry.throttle_wait_budget_ms);
115 let mut throttle_waited = Duration::ZERO;
116 loop {
117 let _permit = self.components.rate_limiter.admit(&request).await;
118 let result = self.components.provider.complete(request.clone()).await;
119 match result {
120 Ok(response) => return Ok(response),
121 Err(failure) => {
122 let failure = self.components.failure_classifier.classify(failure);
123 if failure.retryable
135 && failure.kind == ProviderFailureKind::Quota
136 && let Some(retry_after) = failure.retry_after
137 {
138 let wait = reliability.retry.cap_retry_after(retry_after);
139 let charge = wait.max(MIN_THROTTLE_BUDGET_CHARGE);
140 if throttle_waited.saturating_add(charge) <= throttle_budget {
143 throttle_waited += charge;
144 tracing::debug!(
145 target: "lash_core::provider::reliability",
146 provider = self.kind(),
147 attempt = attempt + 1,
148 max_attempts = attempts,
149 wait_ms = wait.as_millis() as u64,
150 throttle_waited_ms = throttle_waited.as_millis() as u64,
151 err = %failure.message,
152 "provider throttled with retry-after; waiting without consuming a retry attempt"
153 );
154 if let Some(events) = request.stream_events.as_ref() {
155 events.send(crate::llm::types::LlmStreamEvent::RetryStatus {
156 wait_seconds: wait.as_secs(),
157 attempt: (attempt + 1) as usize,
158 max_attempts: attempts as usize,
159 reason: failure.message.clone(),
160 });
161 }
162 self.components.rate_limiter.clock().sleep(wait).await;
163 continue;
164 }
165 }
166 if attempt + 1 >= attempts || !failure.retryable {
167 return Err(failure);
168 }
169 let delay = reliability
170 .retry
171 .delay_for_attempt(attempt, failure.retry_after);
172 tracing::debug!(
173 target: "lash_core::provider::reliability",
174 provider = self.kind(),
175 attempt = attempt + 1,
176 max_attempts = attempts,
177 delay_ms = delay.as_millis() as u64,
178 err = %failure.message,
179 "provider call failed with retryable failure; sleeping before retry"
180 );
181 if let Some(events) = request.stream_events.as_ref() {
182 events.send(crate::llm::types::LlmStreamEvent::RetryStatus {
183 wait_seconds: delay.as_secs(),
184 attempt: (attempt + 1) as usize,
185 max_attempts: attempts as usize,
186 reason: failure.message.clone(),
187 });
188 }
189 self.components.rate_limiter.clock().sleep(delay).await;
190 attempt += 1;
191 }
192 }
193 }
194 }
195
196 pub async fn close(&self) -> Result<(), LlmTransportError> {
204 self.components.provider.close().await
205 }
206
207 pub fn to_spec(&self) -> ProviderSpec {
208 ProviderSpec {
209 kind: self.kind().to_string(),
210 config: self.components.provider.serialize_config(),
211 }
212 }
213
214 pub fn validate_model_name(&self, model: &str) -> Result<(), String> {
216 let m = model.trim();
217 if m.is_empty() {
218 return Err("model cannot be empty".to_string());
219 }
220 if m.contains(char::is_whitespace) {
221 return Err("model cannot contain whitespace".to_string());
222 }
223 Ok(())
224 }
225}
226
227impl std::fmt::Debug for ProviderHandle {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 self.components.fmt(f)
230 }
231}
232
233impl Clone for ProviderHandle {
234 fn clone(&self) -> Self {
235 Self {
236 components: self.components.clone(),
237 }
238 }
239}
240
241impl PartialEq for ProviderHandle {
242 fn eq(&self, other: &Self) -> bool {
243 self.kind() == other.kind() && self.to_spec().config == other.to_spec().config
244 }
245}
246
247impl Eq for ProviderHandle {}
248
249#[derive(Clone, Debug, Default)]
253pub struct UnconfiguredProvider {
254 options: ProviderOptions,
255}
256
257impl UnconfiguredProvider {
258 fn into_components(self) -> ProviderComponents {
259 ProviderComponents::new(Box::new(self))
260 }
261}
262
263#[async_trait]
264impl Provider for UnconfiguredProvider {
265 fn kind(&self) -> &'static str {
266 "unconfigured"
267 }
268
269 fn options(&self) -> ProviderOptions {
270 self.options.clone()
271 }
272
273 fn set_options(&mut self, options: ProviderOptions) {
274 self.options = options;
275 }
276
277 fn serialize_config(&self) -> serde_json::Value {
278 serde_json::Value::Object(Default::default())
279 }
280
281 async fn complete(&mut self, _request: LlmRequest) -> Result<LlmResponse, LlmTransportError> {
282 Err(LlmTransportError::new(
283 "no provider configured: host must set SessionPolicy.provider before running a turn",
284 ))
285 }
286
287 fn clone_boxed(&self) -> Box<dyn Provider> {
288 Box::new(self.clone())
289 }
290}