Skip to main content

lash_core/provider/
handle.rs

1use super::support::*;
2
3/// Component bundle returned by provider factories.
4#[derive(Debug)]
5pub struct ProviderComponents {
6    pub provider: Box<dyn Provider>,
7    pub model_policy: Arc<dyn ProviderModelPolicy>,
8    pub failure_classifier: Arc<dyn ProviderFailureClassifier>,
9    pub rate_limiter: Arc<ProviderRateLimiter>,
10}
11
12impl ProviderComponents {
13    pub fn new(provider: Box<dyn Provider>, model_policy: Arc<dyn ProviderModelPolicy>) -> Self {
14        let options = provider.options();
15        Self {
16            provider,
17            model_policy,
18            failure_classifier: Arc::new(DefaultProviderFailureClassifier),
19            rate_limiter: Arc::new(ProviderRateLimiter::new(options.reliability.rate_limits)),
20        }
21    }
22
23    /// Install a transport-level decorator that wraps the provider.
24    pub fn map_provider(
25        mut self,
26        map: impl FnOnce(Box<dyn Provider>) -> Box<dyn Provider>,
27    ) -> Self {
28        self.provider = map(self.provider);
29        self
30    }
31
32    pub fn with_failure_classifier(
33        mut self,
34        classifier: Arc<dyn ProviderFailureClassifier>,
35    ) -> Self {
36        self.failure_classifier = classifier;
37        self
38    }
39
40    pub fn with_clock(mut self, clock: Arc<dyn crate::Clock>) -> Self {
41        let options = self.provider.options();
42        self.rate_limiter = Arc::new(ProviderRateLimiter::with_clock(
43            options.reliability.rate_limits,
44            clock,
45        ));
46        self
47    }
48}
49
50impl Clone for ProviderComponents {
51    fn clone(&self) -> Self {
52        Self {
53            provider: self.provider.clone_boxed(),
54            model_policy: Arc::clone(&self.model_policy),
55            failure_classifier: Arc::clone(&self.failure_classifier),
56            rate_limiter: Arc::clone(&self.rate_limiter),
57        }
58    }
59}
60
61/// Owning handle to provider components. This is an executable transport
62/// handle supplied by the host, not a persistence format.
63pub struct ProviderHandle {
64    components: ProviderComponents,
65}
66
67impl ProviderHandle {
68    pub fn new(components: ProviderComponents) -> Self {
69        Self { components }
70    }
71
72    pub fn unconfigured() -> Self {
73        Self::new(UnconfiguredProvider::default().into_components())
74    }
75
76    pub fn components(&self) -> &ProviderComponents {
77        &self.components
78    }
79
80    pub fn components_mut(&mut self) -> &mut ProviderComponents {
81        &mut self.components
82    }
83
84    pub fn with_clock(mut self, clock: Arc<dyn crate::Clock>) -> Self {
85        self.components = self.components.with_clock(clock);
86        self
87    }
88
89    pub fn kind(&self) -> &'static str {
90        self.components.provider.kind()
91    }
92
93    pub fn supported_variants(&self, model: &str) -> &'static [&'static str] {
94        self.components.model_policy.supported_variants(model)
95    }
96
97    pub fn validate_variant(&self, model: &str, variant: &str) -> Result<(), String> {
98        let variants = self.supported_variants(model);
99        if variants.is_empty() {
100            return Err(format!(
101                "Model `{}` on {} does not expose configurable variants.",
102                model,
103                self.kind()
104            ));
105        }
106        if variants.contains(&variant) {
107            return Ok(());
108        }
109        Err(format!(
110            "Unsupported variant `{}` for `{}` on {}. Available: {}",
111            variant,
112            model,
113            self.kind(),
114            variants.join(", ")
115        ))
116    }
117
118    pub fn options(&self) -> ProviderOptions {
119        self.components.provider.options()
120    }
121
122    pub fn set_options(&mut self, options: ProviderOptions) {
123        self.components
124            .rate_limiter
125            .configure(options.reliability.rate_limits.clone());
126        self.components.provider.set_options(options)
127    }
128
129    pub fn requires_streaming(&self) -> bool {
130        self.components.provider.requires_streaming()
131    }
132
133    pub async fn complete(
134        &mut self,
135        request: LlmRequest,
136    ) -> Result<LlmResponse, LlmTransportError> {
137        let reliability = self.options().reliability;
138        let attempts = reliability.retry.attempts();
139        let mut attempt = 0;
140        loop {
141            let _permit = self.components.rate_limiter.admit(&request).await;
142            let result = self.components.provider.complete(request.clone()).await;
143            match result {
144                Ok(response) => return Ok(response),
145                Err(failure) => {
146                    let failure = self.components.failure_classifier.classify(failure);
147                    if attempt + 1 >= attempts || !failure.retryable {
148                        return Err(failure);
149                    }
150                    let delay = reliability
151                        .retry
152                        .delay_for_attempt(attempt, failure.retry_after);
153                    tracing::debug!(
154                        target: "lash_core::provider::reliability",
155                        provider = self.kind(),
156                        attempt = attempt + 1,
157                        max_attempts = attempts,
158                        delay_ms = delay.as_millis() as u64,
159                        err = %failure.message,
160                        "provider call failed with retryable failure; sleeping before retry"
161                    );
162                    if let Some(events) = request.stream_events.as_ref() {
163                        events.send(crate::llm::types::LlmStreamEvent::RetryStatus {
164                            wait_seconds: delay.as_secs(),
165                            attempt: (attempt + 1) as usize,
166                            max_attempts: attempts as usize,
167                            reason: failure.message.clone(),
168                        });
169                    }
170                    self.components.rate_limiter.clock().sleep(delay).await;
171                    attempt += 1;
172                }
173            }
174        }
175    }
176
177    pub fn to_spec(&self) -> ProviderSpec {
178        ProviderSpec {
179            kind: self.kind().to_string(),
180            config: self.components.provider.serialize_config(),
181        }
182    }
183
184    /// Validate model syntax only.
185    pub fn validate_model_name(&self, model: &str) -> Result<(), String> {
186        let m = model.trim();
187        if m.is_empty() {
188            return Err("model cannot be empty".to_string());
189        }
190        if m.contains(char::is_whitespace) {
191            return Err("model cannot contain whitespace".to_string());
192        }
193        Ok(())
194    }
195}
196
197impl std::fmt::Debug for ProviderHandle {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        self.components.fmt(f)
200    }
201}
202
203impl Clone for ProviderHandle {
204    fn clone(&self) -> Self {
205        Self {
206            components: self.components.clone(),
207        }
208    }
209}
210
211impl PartialEq for ProviderHandle {
212    fn eq(&self, other: &Self) -> bool {
213        self.kind() == other.kind() && self.to_spec().config == other.to_spec().config
214    }
215}
216
217impl Eq for ProviderHandle {}
218
219/// Placeholder provider used by runtime policy defaults before a host resolver
220/// installs the executable provider. Every transport-level method errors;
221/// calling code MUST replace this before executing a turn.
222#[derive(Clone, Debug, Default)]
223pub struct UnconfiguredProvider {
224    options: ProviderOptions,
225}
226
227impl UnconfiguredProvider {
228    fn into_components(self) -> ProviderComponents {
229        ProviderComponents::new(Box::new(self), Arc::new(StaticModelPolicy::new()))
230    }
231}
232
233#[async_trait]
234impl Provider for UnconfiguredProvider {
235    fn kind(&self) -> &'static str {
236        "unconfigured"
237    }
238
239    fn options(&self) -> ProviderOptions {
240        self.options.clone()
241    }
242
243    fn set_options(&mut self, options: ProviderOptions) {
244        self.options = options;
245    }
246
247    fn serialize_config(&self) -> serde_json::Value {
248        serde_json::Value::Object(Default::default())
249    }
250
251    async fn complete(&mut self, _request: LlmRequest) -> Result<LlmResponse, LlmTransportError> {
252        Err(LlmTransportError::new(
253            "no provider configured: host must set SessionPolicy.provider before running a turn",
254        ))
255    }
256
257    fn clone_boxed(&self) -> Box<dyn Provider> {
258        Box::new(self.clone())
259    }
260}