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
//! The `Provider` trait — a factory that creates `LanguageModel` instances.
use std::future::Future;
use std::pin::Pin;
use crate::error::AiMuxError;
use crate::language_model::LanguageModel;
use crate::model_catalogue::RuntimeModel;
/// A provider factory.
///
/// Holds API keys / config and creates `LanguageModel` instances by model name.
pub trait Provider: Send + Sync {
/// Unique provider name (e.g. `"openai"`).
fn name(&self) -> &str;
/// Create a model instance by its name string (e.g. `"gpt-4o"`).
///
/// Non-language-model providers (image/video/speech/search/… — e.g. Tavily,
/// Stability, Recraft) do not implement this and get the default
/// `Unsupported` error. Only providers that actually expose a language
/// model override it (issue M9).
///
/// # Errors
///
/// The default implementation returns `AiMuxError::UnsupportedFunctionality`;
/// implementations return lookup/auth errors for model ids they cannot serve.
fn language_model(&self, _model_id: &str) -> Result<Box<dyn LanguageModel>, AiMuxError> {
Err(AiMuxError::UnsupportedFunctionality(format!(
"provider '{}' does not provide language models",
self.name()
)))
}
/// List the models this account can call on this provider, via the
/// provider's `/models` endpoint (runtime discovery).
///
/// Returns **only the provider's official data** (`RuntimeModel`: id,
/// owned_by, created) — no community catalogue enrichment. To supplement
/// with model specs (context length, capabilities, reasoning portrait),
/// call `get_model_specs` separately and merge in the host (RFC-0027).
///
/// Default returns [`AiMuxError::UnsupportedFunctionality`] — providers that expose a
/// model-list endpoint override this.
///
/// Implemented as a `Pin<Box<Future>>` (rather than `#[async_trait]`) so that
/// the dozens of existing `Provider` impls need no changes — only providers
/// that actually support `/models` override this.
fn list_models(
&self,
) -> Pin<Box<dyn Future<Output = Result<Vec<RuntimeModel>, AiMuxError>> + Send + '_>> {
let name = self.name().to_string();
Box::pin(async move {
Err(AiMuxError::UnsupportedFunctionality(format!(
"list_models not implemented for provider '{name}'"
)))
})
}
}