use crate::adapter::{LmBuilder, ProviderLM};
use crate::auth::{access_policy, AccessPolicy, CredentialProvider};
use crate::cloud::hosts::HostSettings;
use crate::errors::Lm15Error;
use crate::wire::Clock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DialectId {
OpenaiResponses,
OpenaiChat,
Anthropic,
Gemini,
Typesafe,
}
impl DialectId {
pub fn as_str(self) -> &'static str {
match self {
DialectId::OpenaiResponses => "openai-responses",
DialectId::OpenaiChat => "openai-chat",
DialectId::Anthropic => "anthropic",
DialectId::Gemini => "gemini",
DialectId::Typesafe => "typesafe",
}
}
pub fn default_base_url(self) -> &'static str {
match self {
DialectId::OpenaiResponses | DialectId::OpenaiChat => "https://api.openai.com/v1",
DialectId::Anthropic => "https://api.anthropic.com/v1",
DialectId::Gemini => "https://generativelanguage.googleapis.com/v1beta",
DialectId::Typesafe => "https://api.typesafe.ai",
}
}
pub fn default_preset(self) -> &'static str {
match self {
DialectId::OpenaiResponses | DialectId::OpenaiChat => "openai",
DialectId::Anthropic => "anthropic",
DialectId::Gemini | DialectId::Typesafe => "",
}
}
pub fn wire_name(self) -> &'static str {
match self {
DialectId::OpenaiResponses => "Responses",
DialectId::OpenaiChat => "Chat Completions",
DialectId::Anthropic => "Messages",
DialectId::Gemini => "generateContent",
DialectId::Typesafe => "System One",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
AdapterOwned,
Bound,
Hosted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProviderDefinition {
pub id: &'static str,
pub dialect: DialectId,
pub kind: EntryKind,
pub compat: Option<&'static str>,
pub placeholder_key: Option<&'static str>,
pub note: &'static str,
}
pub const PROVIDERS: &[ProviderDefinition] = crate::generated::tables::PROVIDERS;
pub fn canonical_provider(name: &str) -> String {
name.replace('_', "-")
}
pub fn lookup(name: &str) -> Option<&'static ProviderDefinition> {
let canonical = canonical_provider(name);
PROVIDERS.iter().find(|d| d.id == canonical)
}
impl ProviderDefinition {
pub fn preset(&self) -> Option<&'static str> {
match (self.compat, self.kind, self.id) {
(Some(name), _, _) => Some(name),
(None, EntryKind::AdapterOwned, "xai") => Some("xai"),
_ => None,
}
}
pub fn access(&self) -> &'static AccessPolicy {
access_policy(self.id).expect("every registry entry has an access policy")
}
pub fn hosted(&self) -> bool {
self.access().host.is_some()
}
}
pub fn adapter_for(
provider: &str,
credential: impl CredentialProvider + Send + Sync + 'static,
base_url: Option<&str>,
settings: Option<HostSettings>,
clock: Option<Box<dyn Clock + Send + Sync>>,
) -> Result<ProviderLM, Lm15Error> {
let definition = lookup(provider).ok_or_else(|| {
Lm15Error::not_configured(format!(
"unknown provider {provider:?}; known providers: {}",
PROVIDERS
.iter()
.map(|d| d.id)
.collect::<Vec<_>>()
.join(", ")
))
})?;
let mut builder = LmBuilder::for_entry(definition)
.api_key(credential)
.env(Default::default());
if let Some(base_url) = base_url {
builder = builder.base_url(base_url);
}
if let Some(settings) = settings {
builder = builder.settings(settings);
}
if let Some(clock) = clock {
builder = builder.clock_boxed(clock);
}
builder.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_are_unique_and_hyphenated() {
for (i, d) in PROVIDERS.iter().enumerate() {
assert_eq!(d.id, canonical_provider(d.id));
assert!(
PROVIDERS[..i].iter().all(|other| other.id != d.id),
"{}",
d.id
);
if d.kind == EntryKind::Bound {
assert!(d.compat.is_some(), "{} names its preset", d.id);
}
if d.placeholder_key.is_some() {
assert_eq!(d.kind, EntryKind::Bound);
}
}
}
#[test]
fn lookup_accepts_both_spellings() {
assert_eq!(lookup("openai_chat").unwrap().id, "openai-chat");
assert_eq!(
lookup("deepseek-anthropic").unwrap().dialect,
DialectId::Anthropic
);
assert!(lookup("nope").is_none());
assert_eq!(PROVIDERS.len(), 36); }
#[test]
fn entries_agree_with_the_policy_table() {
for d in PROVIDERS {
assert_eq!(d.access().provider, d.id);
assert_eq!(d.kind == EntryKind::Hosted, d.hosted(), "{}", d.id);
assert_eq!(d.placeholder_key, d.access().placeholder_key, "{}", d.id);
if let Some(name) = d.compat {
let known = match d.dialect {
DialectId::Anthropic => crate::compat::AnthropicCompat::preset(name).is_some(),
DialectId::OpenaiResponses => {
crate::compat::OpenAIResponsesCompat::preset(name).is_some()
}
DialectId::OpenaiChat => {
crate::compat::OpenAIChatCompat::preset(name).is_some()
}
DialectId::Gemini | DialectId::Typesafe => false,
};
assert!(known, "{}: preset {name}", d.id);
}
}
assert_eq!(crate::auth::ACCESS_POLICIES.len(), PROVIDERS.len());
}
}