use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::typed_id::ProviderId;
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DriverId {
OpenAI,
OpenRouter,
AzureOpenAI,
OpenAICompletions,
Anthropic,
Gemini,
LlmSim,
Bedrock,
Mai,
Fireworks,
External(std::sync::Arc<str>),
}
impl std::fmt::Display for DriverId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl DriverId {
pub fn external(id: impl Into<std::sync::Arc<str>>) -> Self {
let id: std::sync::Arc<str> = id.into();
if id.bytes().any(|b| b.is_ascii_uppercase()) {
DriverId::External(std::sync::Arc::from(id.to_lowercase().as_str()))
} else {
DriverId::External(id)
}
}
pub fn as_str(&self) -> &str {
match self {
DriverId::OpenAI => "openai",
DriverId::OpenRouter => "openrouter",
DriverId::AzureOpenAI => "azure_openai",
DriverId::OpenAICompletions => "openai_completions",
DriverId::Anthropic => "anthropic",
DriverId::Gemini => "gemini",
DriverId::LlmSim => "llmsim",
DriverId::Bedrock => "bedrock",
DriverId::Mai => "mai",
DriverId::Fireworks => "fireworks",
DriverId::External(id) => id.as_ref(),
}
}
pub fn default_trace_templates(&self) -> (Option<String>, Option<String>) {
match self {
DriverId::OpenRouter => (
Some("https://openrouter.ai/logs?id={response_id}".to_string()),
Some("https://openrouter.ai/logs".to_string()),
),
_ => (None, None),
}
}
}
impl std::str::FromStr for DriverId {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.to_lowercase();
Ok(match lower.as_str() {
"openai" => DriverId::OpenAI,
"openrouter" => DriverId::OpenRouter,
"azure_openai" => DriverId::AzureOpenAI,
"openai_completions" => DriverId::OpenAICompletions,
"anthropic" => DriverId::Anthropic,
"gemini" => DriverId::Gemini,
"llmsim" => DriverId::LlmSim,
"bedrock" => DriverId::Bedrock,
"mai" => DriverId::Mai,
"fireworks" => DriverId::Fireworks,
_ => DriverId::External(std::sync::Arc::from(lower.as_str())),
})
}
}
impl Serialize for DriverId {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for DriverId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
Ok(s.parse().unwrap_or_else(|_| unreachable!()))
}
}
#[cfg(feature = "openapi")]
impl utoipa::ToSchema for DriverId {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("DriverId")
}
}
#[cfg(feature = "openapi")]
impl utoipa::PartialSchema for DriverId {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
utoipa::openapi::ObjectBuilder::new()
.schema_type(utoipa::openapi::schema::SchemaType::new(
utoipa::openapi::schema::Type::String,
))
.description(Some(
"LLM provider type. Built-in: openai, openrouter, azure_openai, \
openai_completions, anthropic, gemini, llmsim, bedrock, mai, fireworks. \
Any other string is treated as an embedder-defined external provider.",
))
.build()
.into()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum ProviderStatus {
Active,
Disabled,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ProviderTraceConfig {
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generation_url_template: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_url_template: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct Provider {
#[cfg_attr(feature = "openapi", schema(value_type = String, example = "provider_01933b5a00007000800000000000001"))]
pub id: ProviderId,
pub name: String,
pub provider_type: DriverId,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
pub api_key_set: bool,
pub status: ProviderStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_synced_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace: Option<ProviderTraceConfig>,
}