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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Provider entity types (specs/providers.md)
//
// A Provider is an org-scoped instance of a driver: a configured vendor
// account (credentials, endpoint) that powers services like chat. DriverId
// names the driver implementation a provider uses.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::typed_id::ProviderId;
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
/// LLM provider type identifier.
///
/// Built-in variants cover providers shipped with everruns. Any string that
/// does not match a built-in id becomes `External(id)`, so embedders can store
/// and use custom provider ids without schema migrations.
///
/// Serializes to/from a plain string (e.g. `"anthropic"`, `"openai-codex"`).
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DriverId {
/// OpenAI using Open Responses API (<https://www.openresponses.org/>)
OpenAI,
/// OpenRouter using the OpenAI-compatible Responses API
OpenRouter,
/// Azure OpenAI using the Azure-hosted OpenAI v1 API
AzureOpenAI,
/// OpenAI using Chat Completions API (for backward compatibility)
OpenAICompletions,
Anthropic,
/// Google Gemini API
Gemini,
/// LLM simulator for testing
LlmSim,
/// AWS Bedrock Runtime (ConverseStream API)
Bedrock,
/// Microsoft MAI models (e.g. MAI-Code-1-Flash) served via Azure AI Foundry.
/// Uses an OpenAI-compatible Chat Completions API and supports either an
/// Azure AI Foundry API key or Microsoft Entra ID (OAuth) authentication.
Mai,
/// Fireworks AI — open-model inference (Llama, Qwen, DeepSeek, GLM, ...)
/// served via an OpenAI-compatible Chat Completions API.
Fireworks,
/// Embedder-defined provider not compiled into everruns-core. The inner id
/// is the canonical wire string (e.g. `"openai-codex"`).
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 {
/// Construct an external driver id from its canonical wire id.
///
/// The id is normalized to lowercase so registration and lookup match
/// case-insensitively, consistent with built-in parsing.
pub fn external(id: impl Into<std::sync::Arc<str>>) -> Self {
let id: std::sync::Arc<str> = id.into();
// Avoid reallocating when the id is already lowercase.
if id.bytes().any(|b| b.is_ascii_uppercase()) {
DriverId::External(std::sync::Arc::from(id.to_lowercase().as_str()))
} else {
DriverId::External(id)
}
}
/// Return the canonical string identifier for this provider.
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(),
}
}
}
impl std::str::FromStr for DriverId {
// Parsing never fails: unknown ids become `External`.
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Normalize once so built-in matching and the External id share the
// same lowercased form; casing variance never yields duplicate ids.
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)?;
// FromStr is infallible (unknown ids become External).
Ok(s.parse().unwrap_or_else(|_| unreachable!()))
}
}
// `Arc<str>` does not implement `ToSchema`, so the schema is written by hand.
// It is a plain string at the wire level regardless of the variant.
#[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()
}
}
/// LLM provider status
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum ProviderStatus {
Active,
Disabled,
}
/// LLM Provider entity (API keys never exposed)
/// Note: This is the entity struct, separate from the Provider trait in llm.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct Provider {
/// Prefixed public identifier. See [ID Schema](https://docs.everruns.com/advanced/id-schema/).
#[cfg_attr(feature = "openapi", schema(value_type = String, example = "provider_01933b5a00007000800000000000001"))]
pub id: ProviderId,
/// Human-readable provider name. Safe to render in user-facing messages.
pub name: String,
/// Provider implementation type (OpenAI, Anthropic, Gemini, etc.).
pub provider_type: DriverId,
/// Custom base URL for self-hosted / proxied providers. `None` means use the provider's default endpoint.
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
/// Whether an API key is configured. The key itself is never returned.
pub api_key_set: bool,
/// Current lifecycle status of this provider.
pub status: ProviderStatus,
/// Timestamp of the most recent successful model sync from the provider's API (RFC 3339).
#[serde(skip_serializing_if = "Option::is_none")]
pub last_synced_at: Option<DateTime<Utc>>,
/// Timestamp when this provider was created (RFC 3339).
pub created_at: DateTime<Utc>,
/// Timestamp when this provider was last updated (RFC 3339).
pub updated_at: DateTime<Utc>,
}