allama/identity.rs
1use std::any::{Any, TypeId};
2use std::sync::Arc;
3
4/// Shared provider identity surface for chat, embedding, and decision
5/// providers: the provider's name, and typed lookup of the objects it exposes.
6///
7/// [`provider_name`](Self::provider_name) returns the provider identity
8/// string used for logs, tracing, errors, fixtures, and
9/// [`ChatResponse::provider`](crate::ChatResponse::provider) (for example
10/// `"anthropic"` or `"openai"`). It is an open string surface rather than a
11/// closed enum so wrappers and custom providers can preserve their existing
12/// naming, and it borrows from the provider so names can come from
13/// configuration. Callers should not treat it as a stable capability-dispatch
14/// mechanism.
15///
16/// [`downcast_any`](Self::downcast_any) reaches typed objects a provider
17/// exposes (most often the concrete provider itself, for its
18/// provider-specific methods) through wrappers and type erasure. Call it
19/// through [`ProviderIdentityExt::downcast_ref`].
20///
21/// # Provider name and namespace
22///
23/// A provider has two identifiers, and they need not be equal:
24///
25/// - the **provider name** ([`provider_name`](Self::provider_name)) says who
26/// served a call, for people and telemetry;
27/// - the **namespace** ([`ProviderOptions::NAMESPACE`](crate::ProviderOptions::NAMESPACE),
28/// [`ProviderDataEntry::NAMESPACE`](crate::ProviderDataEntry::NAMESPACE))
29/// keys the provider's typed options and the opaque state it stores in
30/// [`ProviderData`](crate::ProviderData). The namespace is the origin of
31/// that state: a provider replays only state under its own namespace.
32///
33/// Gemini is the example where they differ: its provider name is `gemini`
34/// and its namespace is `google-ai`. Several OpenAI-compatible endpoints
35/// share options under one namespace while each reports its own name. Every
36/// built-in provider crate exports both as `PROVIDER_NAME` and `NAMESPACE`.
37///
38/// [`reads_namespace`](Self::reads_namespace) answers which namespaces a
39/// provider reads, so code that holds only a provider (one built from a
40/// model address, behind wrappers) can keep the state it uses, with
41/// [`retain_provider_state_for`](crate::history::retain_provider_state_for).
42///
43/// # Implementing
44///
45/// - [`provider_name`](Self::provider_name) is required: a provider names
46/// itself, and a wrapper forwards the name of the provider it wraps
47/// (`self.inner.provider_name()`).
48/// - [`reads_namespace`](Self::reads_namespace) is required: a provider
49/// answers for its own namespace, and a wrapper forwards the question
50/// (`self.inner.reads_namespace(namespace)`).
51/// - A provider exposes itself, and optionally objects it owns, with
52/// [`expose`].
53/// - A wrapper forwards the query to every provider it wraps:
54/// `self.inner.downcast_any(id)`. A wrapper that does not forward hides
55/// the providers it wraps.
56///
57/// Wrappers are transparent: they forward every question about the provider
58/// (its name, the namespaces it reads, [`downcast_any`](Self::downcast_any),
59/// its capabilities, its limits, and request validation) and do not answer
60/// for themselves. The methods without a sensible wrapper default (the name,
61/// the namespaces and the capability answers) are required, so a wrapper
62/// cannot forget them silently; the rest have defaults that are right for
63/// providers and wrong for wrappers.
64/// `allama_conformance::assert_wrapper` (and its per-trait variants)
65/// checks a wrapper against all of them, and that its calls pass the
66/// provider's answers through unchanged.
67pub trait ProviderIdentity: Send + Sync {
68 /// Returns the provider name: who serves calls made through this
69 /// provider.
70 fn provider_name(&self) -> &str;
71
72 /// Whether this provider reads the [provider data](crate::ProviderData)
73 /// under `namespace`.
74 ///
75 /// A namespace names the only provider that may read an entry: the
76 /// origin of the state a provider replays, and the addressee of
77 /// settings a caller attaches to a request item. A provider answers
78 /// `true` for the namespace it mints and reads state under (Gemini for
79 /// `google-ai`, an OpenAI-compatible preset for `openai-compat:groq`,
80 /// both OpenAI endpoints for `openai`) and `false` for every other
81 /// namespace, including options namespaces that hold no provider data
82 /// (`openai-responses`, `allama-http`) and the `allama` namespace, which
83 /// allama itself reads.
84 ///
85 /// It is a question, not an identifier: a wrapper answers for the
86 /// providers that may serve its calls, which one namespace cannot
87 /// name. A wrapper forwards it, and a
88 /// [`FallbackProvider`](crate::wrappers::FallbackProvider) reads what
89 /// either side reads, since either may serve the next turn.
90 ///
91 /// Required, like [`provider_name`](Self::provider_name): no default is
92 /// right for both a provider and a wrapper, and a wrong answer silently
93 /// removes state a provider needs from a conversation
94 /// ([`retain_provider_state_for`](crate::history::retain_provider_state_for)).
95 fn reads_namespace(&self, namespace: &str) -> bool;
96
97 /// Returns the object of type `id` this provider exposes, if any.
98 ///
99 /// Concrete providers usually expose themselves (see [`expose`]);
100 /// wrappers forward the query to the providers they wrap. The default
101 /// exposes nothing.
102 fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
103 let _ = id;
104 None
105 }
106}
107
108/// Typed access to [`ProviderIdentity::downcast_any`].
109pub trait ProviderIdentityExt: ProviderIdentity {
110 /// Borrow the `T` this provider, or a provider it wraps, exposes.
111 ///
112 /// Like `anyhow::Error::downcast_ref`, this looks through layers: it
113 /// reaches a concrete provider (and its provider-specific methods)
114 /// through wrappers and [`DynChatProvider`](crate::DynChatProvider):
115 ///
116 /// ```
117 /// # #[cfg(feature = "mock")] {
118 /// use allama::mock::MockProvider;
119 /// use allama::wrappers::{RetryPolicy, RetryingProvider};
120 /// use allama::{DynChatProvider, ProviderIdentityExt};
121 ///
122 /// let retrying = RetryingProvider::new(MockProvider::empty(), RetryPolicy::default());
123 /// let provider = DynChatProvider::new(retrying);
124 /// assert!(provider.downcast_ref::<MockProvider>().is_some());
125 /// # }
126 /// ```
127 ///
128 /// - **Calls through the result skip the wrappers.** The reference points
129 /// at the inner provider, so its methods run without the retries,
130 /// timeouts, tracing, or recording around it.
131 /// - **The first match wins.** A [`FallbackProvider`] answers from its
132 /// primary first; when both sides have the same type, reach each one
133 /// with [`primary`] and [`fallback`].
134 /// - **Discovery is for code handed an already-wrapped provider.** An
135 /// application that builds the stack can keep a clone (or an [`Arc`])
136 /// of the concrete provider before wrapping it.
137 ///
138 /// [`FallbackProvider`]: crate::wrappers::FallbackProvider
139 /// [`primary`]: crate::wrappers::FallbackProvider::primary
140 /// [`fallback`]: crate::wrappers::FallbackProvider::fallback
141 fn downcast_ref<T: Any + Send + Sync>(&self) -> Option<&T> {
142 self.downcast_any(TypeId::of::<T>())?.downcast_ref()
143 }
144}
145
146impl<P: ProviderIdentity + ?Sized> ProviderIdentityExt for P {}
147
148/// Answer a [`downcast_any`](ProviderIdentity::downcast_any) query with
149/// `value` when `id` is its type.
150///
151/// A concrete provider exposes itself, and optionally objects it owns:
152///
153/// ```
154/// use std::any::{Any, TypeId};
155/// use allama::{ProviderIdentity, ProviderIdentityExt, expose};
156///
157/// struct MyProvider {
158/// config: MyConfig,
159/// }
160///
161/// struct MyConfig {
162/// region: &'static str,
163/// }
164///
165/// impl ProviderIdentity for MyProvider {
166/// fn provider_name(&self) -> &str {
167/// "mine"
168/// }
169///
170/// fn reads_namespace(&self, namespace: &str) -> bool {
171/// namespace == "mine"
172/// }
173///
174/// fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
175/// expose(self, id).or_else(|| expose(&self.config, id))
176/// }
177/// }
178///
179/// let provider = MyProvider { config: MyConfig { region: "eu" } };
180/// assert_eq!(provider.downcast_ref::<MyConfig>().unwrap().region, "eu");
181/// ```
182///
183/// A wrapper forwards the query instead: `self.inner.downcast_any(id)`. To
184/// make a wrapper discoverable as well, answer
185/// `expose(self, id).or_else(|| self.inner.downcast_any(id))`, which needs
186/// `P: 'static` on the `impl`. This is discouraged: a caller that looks a
187/// wrapper up depends on how the stack was built, which a wrapper should
188/// not make observable.
189pub fn expose<T: Any + Send + Sync>(value: &T, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
190 (id == TypeId::of::<T>()).then_some(value as &(dyn Any + Send + Sync))
191}
192
193impl<T> ProviderIdentity for &T
194where
195 T: ProviderIdentity + ?Sized,
196{
197 fn provider_name(&self) -> &str {
198 T::provider_name(*self)
199 }
200
201 fn reads_namespace(&self, namespace: &str) -> bool {
202 T::reads_namespace(*self, namespace)
203 }
204
205 fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
206 T::downcast_any(*self, id)
207 }
208}
209
210impl<T> ProviderIdentity for Box<T>
211where
212 T: ProviderIdentity + ?Sized,
213{
214 fn provider_name(&self) -> &str {
215 T::provider_name(self.as_ref())
216 }
217
218 fn reads_namespace(&self, namespace: &str) -> bool {
219 T::reads_namespace(self.as_ref(), namespace)
220 }
221
222 fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
223 T::downcast_any(self.as_ref(), id)
224 }
225}
226
227impl<T> ProviderIdentity for Arc<T>
228where
229 T: ProviderIdentity + ?Sized,
230{
231 fn provider_name(&self) -> &str {
232 T::provider_name(self.as_ref())
233 }
234
235 fn reads_namespace(&self, namespace: &str) -> bool {
236 T::reads_namespace(self.as_ref(), namespace)
237 }
238
239 fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
240 T::downcast_any(self.as_ref(), id)
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 struct Named;
249
250 impl ProviderIdentity for Named {
251 fn provider_name(&self) -> &str {
252 "named"
253 }
254
255 fn reads_namespace(&self, namespace: &str) -> bool {
256 namespace == "named-state"
257 }
258 }
259
260 struct Exposed {
261 value: u32,
262 }
263
264 impl ProviderIdentity for Exposed {
265 fn provider_name(&self) -> &str {
266 "exposed"
267 }
268
269 fn reads_namespace(&self, _namespace: &str) -> bool {
270 false
271 }
272
273 fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
274 expose(self, id).or_else(|| expose(&self.value, id))
275 }
276 }
277
278 #[test]
279 fn concrete_impl_returns_configured_name() {
280 assert_eq!(Named.provider_name(), "named");
281 }
282
283 #[test]
284 fn ref_forwarding_delegates() {
285 let named = Named;
286 let borrowed: &Named = &named;
287 assert_eq!(borrowed.provider_name(), "named");
288 }
289
290 #[test]
291 fn box_forwarding_delegates() {
292 let boxed: Box<dyn ProviderIdentity> = Box::new(Named);
293 assert_eq!(boxed.provider_name(), "named");
294 }
295
296 #[test]
297 fn arc_forwarding_delegates() {
298 let arced: Arc<dyn ProviderIdentity> = Arc::new(Named);
299 assert_eq!(arced.provider_name(), "named");
300 }
301
302 #[test]
303 fn pointers_forward_reads_namespace() {
304 let named = Named;
305 let borrowed: &Named = &named;
306 let boxed: Box<dyn ProviderIdentity> = Box::new(Named);
307 let arced: Arc<dyn ProviderIdentity> = Arc::new(Named);
308 for provider in [borrowed as &dyn ProviderIdentity, &boxed, &arced] {
309 assert!(provider.reads_namespace("named-state"));
310 assert!(!provider.reads_namespace("named"));
311 }
312 }
313
314 #[test]
315 fn exposed_objects_are_found_through_pointers() {
316 let exposed = Exposed { value: 7 };
317 assert_eq!(exposed.downcast_ref::<u32>(), Some(&7));
318 assert!(exposed.downcast_ref::<Exposed>().is_some());
319 assert!(exposed.downcast_ref::<String>().is_none());
320 let borrowed = &exposed;
321 assert!(
322 <&Exposed as ProviderIdentity>::downcast_any(&borrowed, TypeId::of::<u32>()).is_some()
323 );
324
325 let arced: Arc<dyn ProviderIdentity> = Arc::new(Exposed { value: 1 });
326 assert_eq!(arced.downcast_ref::<u32>(), Some(&1));
327 let boxed: Box<dyn ProviderIdentity> = Box::new(Exposed { value: 2 });
328 assert_eq!(boxed.downcast_ref::<u32>(), Some(&2));
329
330 assert!(Named.downcast_ref::<Named>().is_none());
331 }
332}