use super::candidate::{
LimitField, PricingSku, ReadyRouteCandidate, ResolvedAuthSource, ResolvedEndpoint,
SourcedLimitOverride, ValidationReport,
};
use super::capabilities::RouteCapabilities;
use super::descriptor::ProviderDescriptor;
use super::errors::RouteError;
use super::ids::{LogicalModelRef, ModelId, ProviderId, WireModelId};
use super::offering::{ProviderModelOffering, RouteLimits, bundled_offerings};
use crate::catalog::{CatalogOffering, bundled_catalog_offerings};
use crate::provider::WirePolicy;
use crate::{ProviderKind, opencode_go_chat_model_id, provider_preserves_custom_base_url_model};
#[derive(Debug, Clone, Default)]
pub struct RouteRequest {
pub explicit_provider: Option<ProviderKind>,
pub model_selector: Option<LogicalModelRef>,
pub saved_provider_model: Option<WireModelId>,
pub base_url_override: Option<String>,
pub limit_overrides: Vec<SourcedLimitOverride>,
}
#[derive(Debug, Clone)]
pub struct RouteResolver {
offerings: Vec<ProviderModelOffering>,
}
struct ResolvedOffering {
wire_model_id: WireModelId,
canonical_model: Option<ModelId>,
endpoint_key: String,
limits: RouteLimits,
capabilities: RouteCapabilities,
pricing: PricingSku,
}
impl ResolvedOffering {
fn unknown(wire_model_id: WireModelId) -> Self {
Self {
wire_model_id,
canonical_model: None,
endpoint_key: "chat".to_string(),
limits: RouteLimits::default(),
capabilities: RouteCapabilities::default(),
pricing: PricingSku::UnknownOrStale,
}
}
fn from_offering(offering: &ProviderModelOffering) -> Self {
Self {
wire_model_id: offering.wire_model_id.clone(),
canonical_model: offering.canonical_model.clone(),
endpoint_key: offering.endpoint_key.clone(),
limits: offering.limits,
capabilities: offering.capabilities,
pricing: offering.pricing.clone(),
}
}
}
impl Default for RouteResolver {
fn default() -> Self {
Self::new()
}
}
impl RouteResolver {
#[must_use]
pub fn new() -> Self {
Self::from_offerings(default_offerings())
}
#[must_use]
pub fn from_offerings(offerings: Vec<ProviderModelOffering>) -> Self {
Self { offerings }
}
pub fn resolve(&self, req: &RouteRequest) -> Result<ReadyRouteCandidate, RouteError> {
let provider_kind = req.explicit_provider.unwrap_or_default();
let descriptor = ProviderDescriptor::for_kind(provider_kind);
let provider_id = descriptor.id();
let default_offering = self.default_offering(&provider_id);
let logical_model = match &req.model_selector {
Some(selector) => selector.clone(),
None => {
let raw = req
.saved_provider_model
.as_ref()
.map(|w| w.as_str().to_string())
.unwrap_or_else(|| {
default_offering.map_or_else(
|| descriptor.default_wire_model().as_str().to_string(),
|offering| offering.wire_model_id.as_str().to_string(),
)
});
LogicalModelRef::from(raw)
}
};
if logical_model.raw().is_empty() {
return Err(RouteError::EmptyModel);
}
let is_auto = logical_model.is_auto();
let custom_endpoint =
request_uses_custom_endpoint(&descriptor, req.base_url_override.as_deref());
let class = if custom_endpoint {
ProviderClass::LocalOrCustom
} else {
classify(provider_kind)
};
let model_aware = descriptor.wire_policy() == WirePolicy::ModelAware;
let require_catalog_match = model_aware && provider_kind != ProviderKind::Deepseek;
let mut selected = if is_auto {
match default_offering {
None if require_catalog_match => {
return Err(RouteError::UnsupportedModelProtocol {
provider: provider_id.clone(),
model: descriptor.default_wire_model().as_str().to_string(),
endpoint_key: "unproven".to_string(),
});
}
None => ResolvedOffering::unknown(descriptor.default_wire_model()),
Some(offering) => ResolvedOffering::from_offering(offering),
}
} else {
self.scope_selector(
provider_kind,
&provider_id,
&logical_model,
class,
require_catalog_match,
)?
};
if provider_kind == ProviderKind::Deepseek {
if custom_endpoint {
selected.endpoint_key = "chat".to_string();
} else if selected.canonical_model.is_none()
&& deepseek_versioned_model_prefers_responses(selected.wire_model_id.as_str())
{
selected.endpoint_key = "responses".to_string();
}
}
if custom_endpoint {
selected.capabilities.server_side_web_search =
super::capabilities::CapabilityState::Unknown;
}
let protocol = descriptor
.protocol_for_endpoint(&selected.endpoint_key)
.ok_or_else(|| RouteError::UnsupportedModelProtocol {
provider: provider_id.clone(),
model: selected.wire_model_id.as_str().to_string(),
endpoint_key: selected.endpoint_key.clone(),
})?;
let endpoint = ResolvedEndpoint {
base_url: req
.base_url_override
.clone()
.unwrap_or_else(|| descriptor.default_base_url().to_string()),
endpoint_key: selected.endpoint_key,
protocol,
};
let mut messages = Vec::new();
if endpoint_uses_insecure_http(&endpoint.base_url) {
messages
.push("endpoint uses insecure http:// (credentials sent in plaintext)".to_string());
}
let validation = ValidationReport { ok: true, messages };
let mut limits = selected.limits;
for limit_override in &req.limit_overrides {
match limit_override.field {
LimitField::ContextTokens => limits.context_tokens = limit_override.value,
LimitField::InputTokens => limits.input_tokens = limit_override.value,
LimitField::OutputTokens => limits.output_tokens = limit_override.value,
}
}
Ok(ReadyRouteCandidate::new(
provider_id,
provider_kind,
logical_model,
selected.canonical_model,
selected.wire_model_id,
endpoint,
ResolvedAuthSource::Unresolved,
protocol,
limits,
selected.capabilities,
Some(selected.pricing),
validation,
req.limit_overrides.clone(),
))
}
fn scope_selector(
&self,
provider_kind: ProviderKind,
provider_id: &ProviderId,
logical_model: &LogicalModelRef,
class: ProviderClass,
require_catalog_match: bool,
) -> Result<ResolvedOffering, RouteError> {
let raw = if provider_kind == ProviderKind::OpencodeGo {
opencode_go_chat_model_id(logical_model.raw()).ok_or_else(|| {
RouteError::ForeignModelForDirectProvider {
provider: provider_id.clone(),
model: logical_model.raw().to_string(),
}
})?
} else if provider_kind == ProviderKind::OpencodeZen {
logical_model
.raw()
.strip_prefix("opencode/")
.or_else(|| logical_model.raw().strip_prefix("opencode-zen/"))
.unwrap_or_else(|| logical_model.raw())
} else {
provider_scoped_wire_alias(provider_kind, logical_model.raw(), class)
};
for offering in &self.offerings {
if offering.provider != *provider_id {
continue;
}
let matches_canonical = offering
.canonical_model
.as_ref()
.is_some_and(|m| m.as_str() == raw);
let matches_wire = offering.wire_model_id.as_str() == raw;
if matches_canonical || matches_wire {
return Ok(ResolvedOffering::from_offering(offering));
}
}
match class {
ProviderClass::StrictDirect => {
if self.selector_matches_other_provider_offering(provider_id, raw) {
return Err(RouteError::ForeignModelForDirectProvider {
provider: provider_id.clone(),
model: raw.to_string(),
});
}
if logical_model.namespace_hint().is_some() {
return Err(RouteError::ForeignModelForDirectProvider {
provider: provider_id.clone(),
model: raw.to_string(),
});
}
if require_catalog_match {
return Err(RouteError::UnsupportedModelProtocol {
provider: provider_id.clone(),
model: raw.to_string(),
endpoint_key: "unproven".to_string(),
});
}
Ok(ResolvedOffering::unknown(WireModelId::from(raw)))
}
ProviderClass::Aggregator | ProviderClass::LocalOrCustom => {
let _ = provider_kind;
if require_catalog_match {
return Err(RouteError::UnsupportedModelProtocol {
provider: provider_id.clone(),
model: raw.to_string(),
endpoint_key: "unproven".to_string(),
});
}
Ok(ResolvedOffering::unknown(WireModelId::from(raw)))
}
}
}
fn default_offering(&self, provider_id: &ProviderId) -> Option<&ProviderModelOffering> {
self.offerings
.iter()
.find(|offering| offering.provider == *provider_id && offering.default_for_provider)
}
fn selector_matches_other_provider_offering(
&self,
provider_id: &ProviderId,
raw: &str,
) -> bool {
self.offerings.iter().any(|offering| {
offering.provider != *provider_id
&& (offering.wire_model_id.as_str() == raw
|| offering
.canonical_model
.as_ref()
.is_some_and(|model| model.as_str() == raw))
})
}
}
fn provider_scoped_wire_alias(
provider_kind: ProviderKind,
raw: &str,
class: ProviderClass,
) -> &str {
if class != ProviderClass::LocalOrCustom {
if provider_kind == ProviderKind::Together
&& (raw.eq_ignore_ascii_case("inkling") || raw.eq_ignore_ascii_case("together-inkling"))
{
return "thinkingmachines/inkling";
}
if provider_kind == ProviderKind::Openrouter
&& (raw.eq_ignore_ascii_case("qwen3.7-plus")
|| raw.eq_ignore_ascii_case("qwen-3.7-plus"))
{
return "qwen/qwen3.7-plus";
}
}
raw
}
fn default_offerings() -> Vec<ProviderModelOffering> {
let mut seen: std::collections::HashSet<(String, String)> = std::collections::HashSet::new();
let mut out = Vec::new();
let asset_rows = bundled_catalog_offerings()
.iter()
.map(CatalogOffering::to_offering)
.collect::<Vec<_>>();
for offering in bundled_offerings().into_iter().chain(asset_rows) {
let key = (
offering.provider.as_str().to_string(),
offering.wire_model_id.as_str().to_string(),
);
if seen.insert(key) {
out.push(offering);
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ProviderClass {
StrictDirect,
Aggregator,
LocalOrCustom,
}
fn classify(kind: ProviderKind) -> ProviderClass {
match kind {
ProviderKind::Deepseek | ProviderKind::Zai => ProviderClass::StrictDirect,
ProviderKind::Ollama | ProviderKind::Vllm | ProviderKind::Sglang | ProviderKind::Openai => {
ProviderClass::LocalOrCustom
}
_ => ProviderClass::Aggregator,
}
}
fn request_uses_custom_endpoint(
descriptor: &ProviderDescriptor,
base_url_override: Option<&str>,
) -> bool {
base_url_override
.is_some_and(|base_url| provider_preserves_custom_base_url_model(descriptor.kind, base_url))
}
fn deepseek_versioned_model_prefers_responses(model: &str) -> bool {
model
.trim()
.to_ascii_lowercase()
.strip_prefix("deepseek-v")
.and_then(|suffix| suffix.chars().next())
.is_some_and(|first| first.is_ascii_digit())
}
fn endpoint_uses_insecure_http(base_url: &str) -> bool {
let trimmed = base_url.trim();
let Some(rest) = strip_http_scheme(trimmed) else {
return false;
};
!is_loopback_host(host_of_authority(rest))
}
fn strip_http_scheme(base_url: &str) -> Option<&str> {
let idx = base_url.find("://")?;
let (scheme, rest) = base_url.split_at(idx);
if scheme.eq_ignore_ascii_case("http") {
Some(&rest[3..])
} else {
None
}
}
fn host_of_authority(rest: &str) -> &str {
let authority = rest.split('/').next().unwrap_or(rest);
let authority = authority.rsplit('@').next().unwrap_or(authority);
if let Some(inner) = authority.strip_prefix('[') {
return inner.split(']').next().unwrap_or(inner);
}
authority.split(':').next().unwrap_or(authority)
}
fn is_loopback_host(host: &str) -> bool {
let host = host.trim().trim_matches(|c| c == '[' || c == ']');
if host.eq_ignore_ascii_case("localhost") {
return true;
}
if let Ok(v4) = host.parse::<std::net::Ipv4Addr>() {
return v4.is_loopback();
}
if let Ok(v6) = host.parse::<std::net::Ipv6Addr>() {
return v6.is_loopback();
}
false
}
#[cfg(test)]
mod loopback_tests {
use super::{endpoint_uses_insecure_http, is_loopback_host};
#[test]
fn loopback_matches_only_real_loopback_addresses() {
assert!(is_loopback_host("localhost"));
assert!(is_loopback_host("LocalHost"));
assert!(is_loopback_host("127.0.0.1"));
assert!(is_loopback_host("127.1.2.3")); assert!(is_loopback_host("::1"));
assert!(is_loopback_host("[::1]"));
assert!(!is_loopback_host("127.evil.example.com"));
assert!(!is_loopback_host("127.0.0.1.evil.com"));
assert!(!is_loopback_host("notlocalhost"));
assert!(!is_loopback_host("10.0.0.1"));
assert!(!is_loopback_host("localhost.evil.com"));
}
#[test]
fn insecure_http_flags_a_hostile_127_lookalike() {
assert!(!endpoint_uses_insecure_http("http://127.0.0.1:11434/v1"));
assert!(!endpoint_uses_insecure_http("http://localhost:8000/v1"));
assert!(endpoint_uses_insecure_http(
"http://127.evil.example.com/v1"
));
}
}