use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProviderSlug(
pub String,
);
impl ProviderSlug {
pub fn new<S: Into<String>>(slug: S) -> Self {
Self(slug.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for ProviderSlug {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ProviderSlug {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CredentialMode {
ApiKey,
OAuthBearer,
Unsupported,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OAuthTokenSet {
pub access_token: String,
pub refresh_token: String,
pub expires_at: Option<SystemTime>,
pub id_token: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StoredCredential {
ApiKey(String),
OAuthBearer(OAuthTokenSet),
}
impl StoredCredential {
pub fn mode(&self) -> CredentialMode {
match self {
StoredCredential::ApiKey(_) => CredentialMode::ApiKey,
StoredCredential::OAuthBearer(_) => CredentialMode::OAuthBearer,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ProviderAuthStatus {
NotConfigured,
ConfiguredApiKey,
ConnectedOAuth {
expires_at: Option<SystemTime>,
},
Refreshing,
Expired,
RefreshFailed {
reason: String,
},
Revoked,
UnsupportedCredential,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ProviderAuthError {
#[error("No credential configured for provider '{0}'")]
NotConfigured(String),
#[error("Provider '{provider}' does not support credential mode {mode:?}")]
UnsupportedCredential {
provider: String,
mode: CredentialMode,
},
#[error("OAuth token expired for provider '{0}'")]
Expired(String),
#[error("OAuth refresh failed for provider '{provider}': {reason}")]
RefreshFailed {
provider: String,
reason: String,
},
#[error("OAuth credential revoked for provider '{0}'")]
Revoked(String),
#[error("Credential store error for provider '{provider}': {reason}")]
StoreError {
provider: String,
reason: String,
},
}
pub type ProviderAuthResult<T> = Result<T, ProviderAuthError>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProviderPromptContext {
pub provider_slug: ProviderSlug,
pub model: String,
#[serde(default, skip_serializing)]
pub api_key: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedCredential {
pub provider_credential: iron_providers::ProviderCredential,
pub mode: CredentialMode,
pub was_refreshed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_slug_from_str() {
let slug = ProviderSlug::from("kimi-code");
assert_eq!(slug.as_str(), "kimi-code");
}
#[test]
fn stored_credential_mode_api_key() {
let cred = StoredCredential::ApiKey("sk-test".into());
assert_eq!(cred.mode(), CredentialMode::ApiKey);
}
#[test]
fn stored_credential_mode_oauth() {
let cred = StoredCredential::OAuthBearer(OAuthTokenSet {
access_token: "at".into(),
refresh_token: "rt".into(),
expires_at: None,
id_token: None,
});
assert_eq!(cred.mode(), CredentialMode::OAuthBearer);
}
#[test]
fn provider_auth_status_variants() {
let _ = ProviderAuthStatus::NotConfigured;
let _ = ProviderAuthStatus::ConfiguredApiKey;
let _ = ProviderAuthStatus::ConnectedOAuth { expires_at: None };
let _ = ProviderAuthStatus::Refreshing;
let _ = ProviderAuthStatus::Expired;
let _ = ProviderAuthStatus::RefreshFailed {
reason: "network".into(),
};
let _ = ProviderAuthStatus::Revoked;
let _ = ProviderAuthStatus::UnsupportedCredential;
}
#[test]
fn provider_auth_error_display() {
let e = ProviderAuthError::NotConfigured("codex".into());
assert!(e.to_string().contains("codex"));
}
}