use router_protocol::CanonicalRequest;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FailureClass {
Authentication,
Authorization,
SubscriptionExhaustion,
RollingRateExhaustion,
ModelAccess,
InvalidRequest,
AdapterInvalidRequest,
UnsupportedCapability,
ModelNotFoundOrRetired,
Overload,
Timeout,
Network,
MalformedStream,
Cancelled,
#[serde(rename = "upstream_5xx")]
Upstream5xx,
Unknown,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProviderFailureEvidence {
Authentication {
account_ref: String,
},
Authorization {
account_ref: String,
},
SubscriptionExhaustion {
capacity_domain_ref: String,
},
RollingRateExhaustion {
retry_window_ref: String,
},
ModelAccess {
model_ref: String,
},
InvalidCanonicalRequest {
request_ref: String,
},
AdapterInvalidRequest {
adapter_version: String,
},
UnsupportedCapability {
capability_ref: String,
},
ModelNotFoundOrRetired {
catalog_snapshot_ref: String,
},
Overload {
endpoint_ref: String,
},
Timeout {
visibility_ref: String,
},
Network {
visibility_ref: String,
},
MalformedStream {
visibility_ref: String,
},
Cancelled {
visibility_ref: String,
},
Upstream5xx {
endpoint_ref: String,
},
Unknown {
bounded_fingerprint: String,
scope_ref: String,
},
}
pub const fn classify_failure(evidence: &ProviderFailureEvidence) -> FailureClass {
match evidence {
ProviderFailureEvidence::Authentication { .. } => FailureClass::Authentication,
ProviderFailureEvidence::Authorization { .. } => FailureClass::Authorization,
ProviderFailureEvidence::SubscriptionExhaustion { .. } => {
FailureClass::SubscriptionExhaustion
}
ProviderFailureEvidence::RollingRateExhaustion { .. } => {
FailureClass::RollingRateExhaustion
}
ProviderFailureEvidence::ModelAccess { .. } => FailureClass::ModelAccess,
ProviderFailureEvidence::InvalidCanonicalRequest { .. } => FailureClass::InvalidRequest,
ProviderFailureEvidence::AdapterInvalidRequest { .. } => {
FailureClass::AdapterInvalidRequest
}
ProviderFailureEvidence::UnsupportedCapability { .. } => {
FailureClass::UnsupportedCapability
}
ProviderFailureEvidence::ModelNotFoundOrRetired { .. } => {
FailureClass::ModelNotFoundOrRetired
}
ProviderFailureEvidence::Overload { .. } => FailureClass::Overload,
ProviderFailureEvidence::Timeout { .. } => FailureClass::Timeout,
ProviderFailureEvidence::Network { .. } => FailureClass::Network,
ProviderFailureEvidence::MalformedStream { .. } => FailureClass::MalformedStream,
ProviderFailureEvidence::Cancelled { .. } => FailureClass::Cancelled,
ProviderFailureEvidence::Upstream5xx { .. } => FailureClass::Upstream5xx,
ProviderFailureEvidence::Unknown { .. } => FailureClass::Unknown,
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawProviderResponse {
pub response_ref: String,
pub transport: String,
pub status_code: Option<u16>,
pub provider_code: Option<String>,
pub retry_after_seconds: Option<u64>,
pub stream_terminal: bool,
pub cancelled: bool,
pub body_fingerprint: String,
}
pub fn classify_raw_provider_response(raw: &RawProviderResponse) -> FailureClass {
if raw.cancelled || raw.transport == "cancellation" {
return FailureClass::Cancelled;
}
match raw.transport.as_str() {
"timeout" => return FailureClass::Timeout,
"network" => return FailureClass::Network,
"sse" if raw.status_code == Some(200) && !raw.stream_terminal => {
return FailureClass::MalformedStream;
}
"adapter" if raw.status_code == Some(422) => {
return FailureClass::UnsupportedCapability;
}
"adapter" => return FailureClass::AdapterInvalidRequest,
_ => {}
}
match (raw.status_code, raw.provider_code.as_deref()) {
(Some(401), _) => FailureClass::Authentication,
(Some(403), Some("fixture_model_entitlement")) => FailureClass::ModelAccess,
(Some(403), _) => FailureClass::Authorization,
(Some(429), Some("fixture_subscription_limit")) => FailureClass::SubscriptionExhaustion,
(Some(429), _) => FailureClass::RollingRateExhaustion,
(Some(400), _) => FailureClass::InvalidRequest,
(Some(404), _) => FailureClass::ModelNotFoundOrRetired,
(Some(503), Some("fixture_overloaded")) => FailureClass::Overload,
(Some(500..=599), Some("fixture_upstream")) => FailureClass::Upstream5xx,
_ => FailureClass::Unknown,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdapterDisposition {
SupportedFixture,
Unsupported,
NotApplicable,
BlockedPendingLiveEvidence,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdapterCapabilityDispositions {
pub authentication: AdapterDisposition,
pub catalog_discovery: AdapterDisposition,
pub capability_discovery: AdapterDisposition,
pub request_transform: AdapterDisposition,
pub stream_transform: AdapterDisposition,
pub tools: AdapterDisposition,
pub reasoning_mapping: AdapterDisposition,
pub usage: AdapterDisposition,
pub quota_cost: AdapterDisposition,
pub probes: AdapterDisposition,
pub idempotency: AdapterDisposition,
pub privacy: AdapterDisposition,
pub support_status: AdapterDisposition,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderRoute {
pub route_ref: String,
pub provider_id: String,
pub model_id: String,
pub endpoint_ref: String,
pub fixture_only: bool,
pub automatic_live_eligible: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreparedProviderDispatch {
pub request_ref: String,
pub route_ref: String,
pub adapter_id: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdapterBoundaryError {
LiveRouteProhibited,
InvalidRoute,
}
impl fmt::Display for AdapterBoundaryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::LiveRouteProhibited => "provider route is not eligible for the offline adapter",
Self::InvalidRoute => "provider route is invalid",
})
}
}
impl std::error::Error for AdapterBoundaryError {}
pub trait ProviderAdapter: Send + Sync {
fn adapter_id(&self) -> &'static str;
fn prepare(
&self,
route: &ProviderRoute,
request_ref: &str,
request: &CanonicalRequest,
) -> Result<PreparedProviderDispatch, AdapterBoundaryError>;
fn normalize_failure(&self, evidence: &ProviderFailureEvidence) -> FailureClass {
classify_failure(evidence)
}
fn classify_raw_response(&self, raw: &RawProviderResponse) -> FailureClass {
classify_raw_provider_response(raw)
}
fn capability_dispositions(&self) -> AdapterCapabilityDispositions;
fn transform_fixture(&self, _category: &str, canonical_input: &str) -> String {
canonical_input.to_owned()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FixtureContinuityAdapter;
impl ProviderAdapter for FixtureContinuityAdapter {
fn adapter_id(&self) -> &'static str {
"fixture-continuity"
}
fn prepare(
&self,
route: &ProviderRoute,
request_ref: &str,
_request: &CanonicalRequest,
) -> Result<PreparedProviderDispatch, AdapterBoundaryError> {
if !fixture_ref(&route.route_ref)
|| !route.route_ref.starts_with("route.fixture.")
|| !fixture_ref(&route.provider_id)
|| !fixture_ref(&route.model_id)
|| !fixture_ref(&route.endpoint_ref)
|| !route.endpoint_ref.starts_with("endpoint.fixture.")
|| !fixture_ref(request_ref)
|| !request_ref.starts_with("request.")
{
return Err(AdapterBoundaryError::InvalidRoute);
}
if !route.fixture_only || route.automatic_live_eligible {
return Err(AdapterBoundaryError::LiveRouteProhibited);
}
Ok(PreparedProviderDispatch {
request_ref: request_ref.to_owned(),
route_ref: route.route_ref.clone(),
adapter_id: self.adapter_id().to_owned(),
})
}
fn capability_dispositions(&self) -> AdapterCapabilityDispositions {
use AdapterDisposition::{
BlockedPendingLiveEvidence as Blocked, SupportedFixture as Supported,
};
AdapterCapabilityDispositions {
authentication: Blocked,
catalog_discovery: Supported,
capability_discovery: Supported,
request_transform: Supported,
stream_transform: Supported,
tools: Supported,
reasoning_mapping: Supported,
usage: Supported,
quota_cost: Blocked,
probes: Supported,
idempotency: Supported,
privacy: Blocked,
support_status: Blocked,
}
}
fn transform_fixture(&self, category: &str, canonical_input: &str) -> String {
match (category, canonical_input) {
("usage", "usage.request") => "usage.normalized".to_owned(),
("cancellation", "cancel.request") => "cancelled.no_retry".to_owned(),
("errors", "error.matrix") => "typed.failure".to_owned(),
("context", input) if input.starts_with("context.") => "context.accepted".to_owned(),
_ => canonical_input.to_owned(),
}
}
}
impl FixtureContinuityAdapter {
pub fn prepare_reference(
&self,
route: &ProviderRoute,
request_ref: &str,
) -> Result<PreparedProviderDispatch, AdapterBoundaryError> {
if !fixture_ref(&route.route_ref)
|| !route.route_ref.starts_with("route.fixture.")
|| !fixture_ref(&route.provider_id)
|| !fixture_ref(&route.model_id)
|| !fixture_ref(&route.endpoint_ref)
|| !route.endpoint_ref.starts_with("endpoint.fixture.")
|| !fixture_ref(request_ref)
|| !request_ref.starts_with("request.")
{
return Err(AdapterBoundaryError::InvalidRoute);
}
if !route.fixture_only || route.automatic_live_eligible {
return Err(AdapterBoundaryError::LiveRouteProhibited);
}
Ok(PreparedProviderDispatch {
request_ref: request_ref.to_owned(),
route_ref: route.route_ref.clone(),
adapter_id: self.adapter_id().to_owned(),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FixtureAdapterKind {
OpenAi,
Kimi,
OllamaCloud,
OpenRouter,
LocalOllama,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BoundFixtureContinuityAdapter {
kind: FixtureAdapterKind,
}
impl BoundFixtureContinuityAdapter {
pub fn bind(
adapter_ref: &str,
provider_ref: &str,
provider_id: &str,
deployment_profile: &str,
adapter_version: &str,
) -> Result<Self, AdapterBoundaryError> {
let kind = match (
adapter_ref,
provider_ref,
provider_id,
deployment_profile,
adapter_version,
) {
(
"adapter.fixture.openai",
"provider.fixture.openai",
"openai",
"hosted_subscription",
"fixture-1.0.1",
) => FixtureAdapterKind::OpenAi,
(
"adapter.fixture.kimi",
"provider.fixture.kimi",
"kimi",
"hosted_subscription",
"fixture-1.0.1",
) => FixtureAdapterKind::Kimi,
(
"adapter.fixture.ollama_cloud",
"provider.fixture.ollama_cloud",
"ollama_cloud",
"hosted_cloud",
"fixture-1.0.1",
) => FixtureAdapterKind::OllamaCloud,
(
"adapter.fixture.openrouter",
"provider.fixture.openrouter",
"openrouter",
"hosted_gateway",
"fixture-1.0.1",
) => FixtureAdapterKind::OpenRouter,
(
"adapter.fixture.local_ollama",
"provider.fixture.local_ollama",
"ollama_local",
"local_compute",
"fixture-1.0.1",
) => FixtureAdapterKind::LocalOllama,
_ => return Err(AdapterBoundaryError::InvalidRoute),
};
Ok(Self { kind })
}
pub const fn kind(self) -> FixtureAdapterKind {
self.kind
}
pub fn probe_fixture(&self, probe: &str) -> Result<&'static str, AdapterBoundaryError> {
if probe != "catalog_or_status" {
return Err(AdapterBoundaryError::InvalidRoute);
}
Ok("catalog_or_status.probed_fixture")
}
pub fn prepare_reference(
&self,
route: &ProviderRoute,
request_ref: &str,
) -> Result<PreparedProviderDispatch, AdapterBoundaryError> {
FixtureContinuityAdapter.prepare_reference(route, request_ref)?;
if route.provider_id != self.provider_id() {
return Err(AdapterBoundaryError::InvalidRoute);
}
Ok(PreparedProviderDispatch {
request_ref: request_ref.to_owned(),
route_ref: route.route_ref.clone(),
adapter_id: self.adapter_id().to_owned(),
})
}
const fn provider_id(self) -> &'static str {
match self.kind {
FixtureAdapterKind::OpenAi => "openai",
FixtureAdapterKind::Kimi => "kimi",
FixtureAdapterKind::OllamaCloud => "ollama_cloud",
FixtureAdapterKind::OpenRouter => "openrouter",
FixtureAdapterKind::LocalOllama => "ollama_local",
}
}
}
impl ProviderAdapter for BoundFixtureContinuityAdapter {
fn adapter_id(&self) -> &'static str {
match self.kind {
FixtureAdapterKind::OpenAi => "adapter.fixture.openai",
FixtureAdapterKind::Kimi => "adapter.fixture.kimi",
FixtureAdapterKind::OllamaCloud => "adapter.fixture.ollama_cloud",
FixtureAdapterKind::OpenRouter => "adapter.fixture.openrouter",
FixtureAdapterKind::LocalOllama => "adapter.fixture.local_ollama",
}
}
fn prepare(
&self,
route: &ProviderRoute,
request_ref: &str,
_request: &CanonicalRequest,
) -> Result<PreparedProviderDispatch, AdapterBoundaryError> {
self.prepare_reference(route, request_ref)
}
fn capability_dispositions(&self) -> AdapterCapabilityDispositions {
use AdapterDisposition::{
BlockedPendingLiveEvidence as Blocked, NotApplicable, SupportedFixture as Supported,
Unsupported,
};
let local = self.kind == FixtureAdapterKind::LocalOllama;
AdapterCapabilityDispositions {
authentication: if local { NotApplicable } else { Blocked },
catalog_discovery: Supported,
capability_discovery: Supported,
request_transform: Supported,
stream_transform: Supported,
tools: if local { Unsupported } else { Supported },
reasoning_mapping: Supported,
usage: Supported,
quota_cost: if local { NotApplicable } else { Blocked },
probes: Supported,
idempotency: Supported,
privacy: if local { Supported } else { Blocked },
support_status: Blocked,
}
}
fn transform_fixture(&self, category: &str, canonical_input: &str) -> String {
match (category, canonical_input) {
("tools", "tool.required") => "unsupported".to_owned(),
_ => FixtureContinuityAdapter.transform_fixture(category, canonical_input),
}
}
}
fn fixture_ref(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 128
&& value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
}