use super::descriptor::ProviderDescriptor;
use super::errors::RouteError;
use super::ids::{LogicalModelRef, ModelId, NamespaceHint, ProviderId, WireModelId};
use super::resolver::{RouteRequest, RouteResolver};
use super::{
LimitField, OverrideSource, RequestProtocol, ResolvedAuthSource, SourcedLimitOverride,
};
use crate::ProviderKind;
use crate::models_dev::ModelsDevCatalog;
fn req(provider: Option<ProviderKind>, model: Option<&str>) -> RouteRequest {
RouteRequest {
explicit_provider: provider,
model_selector: model.map(LogicalModelRef::from),
saved_provider_model: None,
base_url_override: None,
limit_overrides: Vec::new(),
}
}
fn models_dev_route_resolver() -> RouteResolver {
let raw = r#"{
"providers": {
"zai": {
"models": {
"glm-5.2": {
"id": "glm-5.2",
"base_model": "zhipuai/glm-5.2",
"default": true,
"modalities": { "input": ["text"], "output": ["text"] },
"limit": { "context": 1000000, "input": 900000, "output": 131072 }
}
}
},
"openrouter": {
"models": {
"z-ai/glm-5.2": {
"id": "z-ai/glm-5.2",
"base_model": "zhipuai/glm-5.2",
"modalities": { "input": ["text"], "output": ["text"] },
"limit": { "context": 128000, "output": 32768 }
}
}
}
}
}"#;
let catalog = ModelsDevCatalog::parse_json(raw).expect("Models.dev fixture parses");
let mut offerings = catalog
.provider_offerings("zai")
.expect("zai provider offerings");
offerings.extend(
catalog
.provider_offerings("openrouter")
.expect("openrouter provider offerings"),
);
RouteResolver::from_offerings(offerings)
}
#[test]
fn provider_id_from_kind_uses_canonical_id() {
assert_eq!(
ProviderId::from_kind(ProviderKind::Deepseek).as_str(),
"deepseek"
);
assert_eq!(
ProviderId::from_kind(ProviderKind::Openrouter).as_str(),
"openrouter"
);
}
#[test]
fn model_id_and_wire_model_id_are_distinct_types() {
let canonical = ModelId::from("deepseek-v4-pro");
let wire = WireModelId::from("deepseek-ai/DeepSeek-V4-Pro");
assert_eq!(canonical.as_str(), "deepseek-v4-pro");
assert_eq!(wire.as_str(), "deepseek-ai/DeepSeek-V4-Pro");
}
#[test]
fn logical_model_ref_auto_is_sentinel() {
assert!(LogicalModelRef::from("auto").is_auto());
assert!(!LogicalModelRef::from("deepseek-v4-pro").is_auto());
}
#[test]
fn logical_model_ref_namespace_hint_parses_curated_prefixes() {
let cases = [
("deepseek-ai/DeepSeek-V4-Pro", NamespaceHint::DeepseekAi),
("deepseek/deepseek-v4-pro", NamespaceHint::Deepseek),
("anthropic/claude-foo", NamespaceHint::Anthropic),
("openai/gpt-foo", NamespaceHint::Openai),
("qwen/qwen-foo", NamespaceHint::Qwen),
];
for (raw, expected) in cases {
assert_eq!(
LogicalModelRef::from(raw).namespace_hint(),
Some(expected),
"{raw} should parse to {expected:?}"
);
}
assert_eq!(LogicalModelRef::from("plain-model").namespace_hint(), None);
assert_eq!(LogicalModelRef::from("auto").namespace_hint(), None);
}
#[test]
fn no_namespace_hint_or_logical_ref_to_provider_id_conversion() {
let hint = LogicalModelRef::from("deepseek-ai/DeepSeek-V4-Pro").namespace_hint();
assert_eq!(hint, Some(NamespaceHint::DeepseekAi));
let provider = ProviderId::from_kind(ProviderKind::Together);
assert_eq!(provider.as_str(), "together");
}
#[test]
fn newtypes_serialize_transparently() {
let id = ProviderId::from("deepseek");
assert_eq!(serde_json::to_string(&id).unwrap(), "\"deepseek\"");
let wire = WireModelId::from("deepseek-ai/DeepSeek-V4-Pro");
assert_eq!(
serde_json::to_string(&wire).unwrap(),
"\"deepseek-ai/DeepSeek-V4-Pro\""
);
}
#[test]
fn resolver_reports_auth_as_unresolved_until_preflight() {
let candidate = RouteResolver::new()
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("route resolves");
assert_eq!(candidate.auth(), &ResolvedAuthSource::Unresolved);
assert_eq!(
serde_json::to_value(&candidate).expect("candidate serializes")["auth"],
"unresolved"
);
}
#[test]
fn resolver_applies_every_limit_override_field_before_construction() {
let mut request = req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro"));
request.limit_overrides = vec![
SourcedLimitOverride {
field: LimitField::ContextTokens,
value: Some(111),
source: OverrideSource::UserContextWindow,
},
SourcedLimitOverride {
field: LimitField::InputTokens,
value: Some(222),
source: OverrideSource::CodexPublicApiLimitStrip,
},
SourcedLimitOverride {
field: LimitField::OutputTokens,
value: Some(333),
source: OverrideSource::CodexRosterCorrection,
},
];
let candidate = RouteResolver::new()
.resolve(&request)
.expect("route with overrides resolves");
assert_eq!(candidate.limits().context_tokens, Some(111));
assert_eq!(candidate.limits().input_tokens, Some(222));
assert_eq!(candidate.limits().output_tokens, Some(333));
}
#[test]
fn resolver_applies_repeated_limit_overrides_in_request_order() {
let mut request = req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro"));
request.limit_overrides = vec![
SourcedLimitOverride {
field: LimitField::ContextTokens,
value: Some(111),
source: OverrideSource::CodexRosterCorrection,
},
SourcedLimitOverride {
field: LimitField::ContextTokens,
value: None,
source: OverrideSource::CodexPublicApiLimitStrip,
},
SourcedLimitOverride {
field: LimitField::ContextTokens,
value: Some(333),
source: OverrideSource::UserContextWindow,
},
];
let candidate = RouteResolver::new()
.resolve(&request)
.expect("route with ordered overrides resolves");
assert_eq!(candidate.limits().context_tokens, Some(333));
}
#[test]
fn resolver_records_exact_limit_override_provenance() {
let overrides = vec![
SourcedLimitOverride {
field: LimitField::InputTokens,
value: None,
source: OverrideSource::CodexPublicApiLimitStrip,
},
SourcedLimitOverride {
field: LimitField::ContextTokens,
value: Some(128_000),
source: OverrideSource::CodexRosterCorrection,
},
];
let mut request = req(Some(ProviderKind::OpenaiCodex), None);
request.limit_overrides = overrides.clone();
let candidate = RouteResolver::new()
.resolve(&request)
.expect("Codex route with sourced overrides resolves");
assert_eq!(candidate.applied_limit_overrides(), overrides.as_slice());
}
#[test]
fn resolved_auth_source_existing_variants_keep_wire_compatibility() {
let cases = [
(ResolvedAuthSource::Cli, "cli"),
(ResolvedAuthSource::ConfigFile, "config_file"),
(ResolvedAuthSource::Keyring, "keyring"),
(ResolvedAuthSource::Env, "env"),
(ResolvedAuthSource::Command, "command"),
(ResolvedAuthSource::Secret, "secret"),
(ResolvedAuthSource::Missing, "missing"),
];
for (source, wire) in cases {
let encoded = serde_json::to_string(&source).expect("auth source serializes");
assert_eq!(encoded, format!("\"{wire}\""));
let decoded: ResolvedAuthSource =
serde_json::from_str(&encoded).expect("auth source deserializes");
assert_eq!(decoded, source);
}
}
#[test]
fn descriptor_for_every_kind_has_nonempty_transport_facts() {
for kind in ProviderKind::ALL {
let d = ProviderDescriptor::for_kind(kind);
assert!(!d.id().as_str().is_empty(), "{kind:?} id empty");
assert!(
!d.default_base_url().is_empty(),
"{kind:?} default_base_url empty"
);
assert!(
!d.default_wire_model().as_str().is_empty(),
"{kind:?} default_wire_model empty"
);
if d.wire_policy().fixed().is_some() {
let _: RequestProtocol = d
.protocol_for_endpoint("chat")
.expect("fixed provider protocol");
}
}
}
#[test]
fn descriptor_protocol_matches_provider_wire() {
for kind in ProviderKind::ALL {
let d = ProviderDescriptor::for_kind(kind);
if matches!(kind, ProviderKind::Deepseek | ProviderKind::OpencodeZen) {
assert_eq!(d.wire_policy(), crate::provider::WirePolicy::ModelAware);
assert_eq!(
d.protocol_for_endpoint("chat"),
Some(RequestProtocol::ChatCompletions)
);
assert_eq!(
d.protocol_for_endpoint("responses"),
Some(RequestProtocol::Responses)
);
if kind == ProviderKind::Deepseek {
assert_eq!(
d.protocol_for_endpoint("messages"),
Some(RequestProtocol::AnthropicMessages)
);
assert_eq!(d.protocol_for_endpoint("models/gemini"), None);
continue;
}
assert_eq!(
d.protocol_for_endpoint("messages"),
Some(RequestProtocol::AnthropicMessages)
);
assert_eq!(d.protocol_for_endpoint("models/gemini"), None);
continue;
}
assert_eq!(
d.protocol_for_endpoint("chat"),
kind.provider().wire_policy().fixed(),
"{kind:?} protocol must equal the provider wire policy"
);
let expected = match kind {
ProviderKind::OpenaiCodex => Some(RequestProtocol::Responses),
ProviderKind::DeepseekAnthropic
| ProviderKind::Anthropic
| ProviderKind::MinimaxAnthropic
| ProviderKind::Openmodel
| ProviderKind::ModelstudioTokenPlanAnthropic
| ProviderKind::ModelstudioCodingPlanAnthropic => {
Some(RequestProtocol::AnthropicMessages)
}
_ => Some(RequestProtocol::ChatCompletions),
};
assert_eq!(
d.protocol_for_endpoint("chat"),
expected,
"{kind:?} protocol mismatch"
);
}
}
#[test]
fn resolver_explicit_provider_scoped_model_maps_to_wire_id() {
let r = RouteResolver::new();
let out = r
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("should resolve");
assert_eq!(out.provider_kind(), ProviderKind::Deepseek);
assert_eq!(out.wire_model_id().as_str(), "deepseek-v4-pro");
assert_eq!(
out.canonical_model().map(ModelId::as_str),
Some("deepseek-v4-pro")
);
}
#[test]
fn resolver_routes_only_official_deepseek_flash_over_responses() {
let resolver = RouteResolver::new();
let flash = resolver
.resolve(&req(
Some(ProviderKind::Deepseek),
Some("deepseek-v4-flash"),
))
.expect("official Flash route resolves");
assert_eq!(flash.protocol(), RequestProtocol::Responses);
assert_eq!(flash.endpoint().endpoint_key, "responses");
let pro = resolver
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("official Pro route resolves");
assert_eq!(pro.protocol(), RequestProtocol::ChatCompletions);
assert_eq!(pro.endpoint().endpoint_key, "chat");
let future = resolver
.resolve(&req(
Some(ProviderKind::Deepseek),
Some("deepseek-v5-future"),
))
.expect("unknown future model preserves direct-provider pass-through");
assert_eq!(future.protocol(), RequestProtocol::Responses);
assert_eq!(future.endpoint().endpoint_key, "responses");
let legacy_unknown = resolver
.resolve(&req(
Some(ProviderKind::Deepseek),
Some("deepseek-coder-future"),
))
.expect("unknown unversioned model preserves historical Chat pass-through");
assert_eq!(legacy_unknown.protocol(), RequestProtocol::ChatCompletions);
assert_eq!(legacy_unknown.endpoint().endpoint_key, "chat");
let custom = resolver
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Deepseek),
model_selector: Some(LogicalModelRef::from("deepseek-v4-flash")),
saved_provider_model: None,
base_url_override: Some("https://compatible.example/v1".to_string()),
limit_overrides: Vec::new(),
})
.expect("custom compatible Flash route remains pass-through");
assert_eq!(custom.protocol(), RequestProtocol::ChatCompletions);
assert_eq!(custom.endpoint().endpoint_key, "chat");
}
#[test]
fn resolver_aggregator_preserves_prefixed_wire_id_without_inferring_deepseek() {
let r = RouteResolver::new();
let out = r
.resolve(&req(
Some(ProviderKind::Together),
Some("deepseek-ai/DeepSeek-V4-Pro"),
))
.expect("aggregator should resolve");
assert_eq!(out.provider_kind(), ProviderKind::Together);
assert_ne!(out.provider_kind(), ProviderKind::Deepseek);
assert_eq!(out.wire_model_id().as_str(), "deepseek-ai/DeepSeek-V4-Pro");
}
#[test]
fn resolver_openrouter_keeps_provider_for_every_namespace_prefix() {
let r = RouteResolver::new();
let prefixes = [
"deepseek-ai/DeepSeek-V4-Pro",
"deepseek/deepseek-v4-pro",
"anthropic/claude-foo",
"openai/gpt-foo",
"qwen/qwen-foo",
];
for raw in prefixes {
let selector = LogicalModelRef::from(raw);
assert!(
selector.namespace_hint().is_some(),
"{raw} should have a namespace hint"
);
let out = r
.resolve(&req(Some(ProviderKind::Openrouter), Some(raw)))
.unwrap_or_else(|e| panic!("{raw} should resolve on openrouter: {e}"));
assert_eq!(
out.provider_kind(),
ProviderKind::Openrouter,
"{raw} must not change provider"
);
assert_eq!(out.wire_model_id().as_str(), raw, "{raw} wire id verbatim");
}
}
#[test]
fn resolver_no_explicit_provider_does_not_infer_deepseek_from_prefix() {
let r = RouteResolver::new();
let out = r.resolve(&req(None, Some("deepseek/deepseek-v4-pro")));
match out {
Err(RouteError::ForeignModelForDirectProvider { provider, model }) => {
assert_eq!(provider.as_str(), "deepseek");
assert_eq!(model, "deepseek/deepseek-v4-pro");
}
other => panic!("expected ForeignModelForDirectProvider, got {other:?}"),
}
}
#[test]
fn resolver_auto_is_sentinel_not_literal_model() {
let r = RouteResolver::new();
let out = r
.resolve(&req(Some(ProviderKind::Deepseek), Some("auto")))
.expect("auto should resolve");
assert!(out.logical_model().is_auto());
assert_ne!(out.wire_model_id().as_str(), "auto");
assert_eq!(out.wire_model_id().as_str(), "deepseek-v4-pro");
}
#[test]
fn resolver_can_use_models_dev_offering_for_provider_scoped_route() {
let r = models_dev_route_resolver();
let out = r
.resolve(&req(Some(ProviderKind::Zai), Some("glm-5.2")))
.expect("Models.dev-backed Z.ai route should resolve");
assert_eq!(out.provider_kind(), ProviderKind::Zai);
assert_eq!(out.provider_id().as_str(), "zai");
assert_eq!(out.wire_model_id().as_str(), "glm-5.2");
assert_eq!(
out.canonical_model().map(ModelId::as_str),
Some("zhipuai/glm-5.2")
);
}
#[test]
fn resolver_auto_uses_models_dev_default_offering_when_available() {
let r = models_dev_route_resolver();
let out = r
.resolve(&req(Some(ProviderKind::Zai), Some("auto")))
.expect("auto should resolve through catalog default");
assert!(out.logical_model().is_auto());
assert_eq!(
out.wire_model_id().as_str(),
"glm-5.2",
"catalog default should win over the built-in Z.ai spelling"
);
assert_eq!(
out.canonical_model().map(ModelId::as_str),
Some("zhipuai/glm-5.2")
);
}
#[test]
fn resolver_auto_falls_back_to_descriptor_default_without_catalog_default() {
let raw = r#"{
"providers": {
"zai": {
"models": {
"glm-5-turbo": {
"id": "glm-5-turbo",
"modalities": { "input": ["text"], "output": ["text"] }
}
}
}
}
}"#;
let catalog = ModelsDevCatalog::parse_json(raw).expect("Models.dev fixture parses");
let offerings = catalog
.provider_offerings("zai")
.expect("zai provider offerings");
let r = RouteResolver::from_offerings(offerings);
let out = r
.resolve(&req(Some(ProviderKind::Zai), Some("auto")))
.expect("auto should resolve to the descriptor default");
assert!(out.logical_model().is_auto());
assert_eq!(
out.wire_model_id().as_str(),
"GLM-5.2",
"no catalog default → descriptor built-in default wins"
);
assert_eq!(
out.canonical_model(),
None,
"descriptor fallback carries no catalog canonical link"
);
}
#[test]
fn resolver_models_dev_prefixed_wire_id_stays_inside_provider_scope() {
let r = models_dev_route_resolver();
let out = r
.resolve(&req(Some(ProviderKind::Openrouter), Some("z-ai/glm-5.2")))
.expect("OpenRouter Models.dev row should resolve");
assert_eq!(out.provider_kind(), ProviderKind::Openrouter);
assert_ne!(out.provider_kind(), ProviderKind::Zai);
assert_eq!(out.wire_model_id().as_str(), "z-ai/glm-5.2");
assert_eq!(
out.canonical_model().map(ModelId::as_str),
Some("zhipuai/glm-5.2")
);
}
#[test]
fn resolver_carries_models_dev_limits_into_ready_candidate() {
let r = models_dev_route_resolver();
let out = r
.resolve(&req(Some(ProviderKind::Zai), Some("glm-5.2")))
.expect("Z.AI Models.dev row should resolve");
assert_eq!(out.limits().context_tokens, Some(1_000_000));
assert_eq!(out.limits().input_tokens, Some(900_000));
assert_eq!(out.limits().output_tokens, Some(131_072));
assert!(out.limits().has_known_limit());
}
#[test]
fn minimax_anthropic_routes_use_catalog_limits_and_messages_protocol() {
let resolver = RouteResolver::new();
for (model, context) in [("MiniMax-M3", 1_000_000), ("MiniMax-M2.7", 204_800)] {
let route = resolver
.resolve(&req(Some(ProviderKind::MinimaxAnthropic), Some(model)))
.expect("MiniMax Messages route should resolve");
assert_eq!(route.provider_kind(), ProviderKind::MinimaxAnthropic);
assert_eq!(route.wire_model_id().as_str(), model);
assert_eq!(route.protocol(), RequestProtocol::AnthropicMessages);
assert_eq!(
route.endpoint().protocol,
RequestProtocol::AnthropicMessages
);
assert_eq!(
route.endpoint().base_url,
"https://api.minimax.io/anthropic"
);
assert_eq!(route.limits().context_tokens, Some(context));
}
}
#[test]
fn resolver_keeps_limits_provider_scoped_for_same_canonical_model() {
let r = models_dev_route_resolver();
let direct = r
.resolve(&req(Some(ProviderKind::Zai), Some("glm-5.2")))
.expect("direct Z.AI route should resolve");
let hosted = r
.resolve(&req(Some(ProviderKind::Openrouter), Some("z-ai/glm-5.2")))
.expect("hosted OpenRouter route should resolve");
assert_eq!(
direct.canonical_model().map(ModelId::as_str),
hosted.canonical_model().map(ModelId::as_str)
);
assert_eq!(direct.limits().context_tokens, Some(1_000_000));
assert_eq!(hosted.limits().context_tokens, Some(128_000));
assert_eq!(hosted.limits().output_tokens, Some(32_768));
}
#[test]
fn resolver_strict_direct_rejects_clearly_foreign_selector() {
let r = RouteResolver::new();
let out = r.resolve(&req(Some(ProviderKind::Zai), Some("anthropic/claude-foo")));
match out {
Err(RouteError::ForeignModelForDirectProvider { provider, model }) => {
assert_eq!(provider.as_str(), "zai");
assert_eq!(model, "anthropic/claude-foo");
}
other => panic!("expected ForeignModelForDirectProvider, got {other:?}"),
}
}
#[test]
fn resolver_strict_direct_rejects_other_provider_known_bare_offering() {
let r = RouteResolver::new();
let out = r.resolve(&req(Some(ProviderKind::Zai), Some("deepseek-v4-pro")));
match out {
Err(RouteError::ForeignModelForDirectProvider { provider, model }) => {
assert_eq!(provider.as_str(), "zai");
assert_eq!(model, "deepseek-v4-pro");
}
other => panic!("expected ForeignModelForDirectProvider, got {other:?}"),
}
}
#[test]
fn resolver_custom_endpoint_allows_namespaced_selector_for_strict_provider() {
let r = RouteResolver::new();
let request = RouteRequest {
explicit_provider: Some(ProviderKind::Deepseek),
model_selector: Some(LogicalModelRef::from("vendor/custom-coder")),
saved_provider_model: None,
base_url_override: Some("https://example.local/v1".to_string()),
limit_overrides: Vec::new(),
};
let out = r
.resolve(&request)
.expect("custom endpoint should defer model validation upstream");
assert_eq!(out.provider_kind(), ProviderKind::Deepseek);
assert_eq!(out.wire_model_id().as_str(), "vendor/custom-coder");
assert_eq!(out.endpoint().base_url, "https://example.local/v1");
}
#[test]
fn resolver_treats_every_official_deepseek_endpoint_as_strict_direct() {
let resolver = RouteResolver::new();
for base_url in [
"https://api.deepseek.com",
"https://api.deepseek.com/v1/",
"https://api.deepseek.com/beta",
] {
let request = RouteRequest {
explicit_provider: Some(ProviderKind::Deepseek),
model_selector: Some(LogicalModelRef::from("anthropic/claude-foo")),
saved_provider_model: None,
base_url_override: Some(base_url.to_string()),
limit_overrides: Vec::new(),
};
assert!(
matches!(
resolver.resolve(&request),
Err(RouteError::ForeignModelForDirectProvider { .. })
),
"official endpoint {base_url} must retain DeepSeek's strict namespace"
);
}
}
#[test]
fn resolver_does_not_trust_deepseek_hostname_substrings() {
let resolver = RouteResolver::new();
let request = RouteRequest {
explicit_provider: Some(ProviderKind::Deepseek),
model_selector: Some(LogicalModelRef::from("vendor/custom-coder")),
saved_provider_model: None,
base_url_override: Some("https://api.deepseek.com.evil.example/v1".to_string()),
limit_overrides: Vec::new(),
};
let route = resolver
.resolve(&request)
.expect("lookalike host must be treated as a custom endpoint");
assert_eq!(route.wire_model_id().as_str(), "vendor/custom-coder");
}
#[test]
fn resolver_explicit_custom_with_base_url_override_passes_model_through_verbatim() {
let r = RouteResolver::new();
let request = RouteRequest {
explicit_provider: Some(ProviderKind::Custom),
model_selector: Some(LogicalModelRef::from("vendor/custom-model-v1")),
saved_provider_model: None,
base_url_override: Some("https://api.example.com/v1".to_string()),
limit_overrides: Vec::new(),
};
let out = r
.resolve(&request)
.expect("custom provider should resolve via pass-through");
assert_eq!(out.provider_kind(), ProviderKind::Custom);
assert_eq!(out.provider_id().as_str(), "custom");
assert_eq!(out.wire_model_id().as_str(), "vendor/custom-model-v1");
assert_eq!(out.endpoint().base_url, "https://api.example.com/v1");
assert_eq!(
out.protocol(),
crate::route::RequestProtocol::ChatCompletions
);
assert!(out.validation().ok);
assert!(out.validation().messages.is_empty());
}
#[test]
fn resolver_strict_direct_rejects_models_dev_offering_from_another_provider() {
let r = models_dev_route_resolver();
let out = r.resolve(&req(Some(ProviderKind::Deepseek), Some("glm-5.2")));
match out {
Err(RouteError::ForeignModelForDirectProvider { provider, model }) => {
assert_eq!(provider.as_str(), "deepseek");
assert_eq!(model, "glm-5.2");
}
other => panic!("expected ForeignModelForDirectProvider, got {other:?}"),
}
}
#[test]
fn default_resolver_yields_real_facts_from_bundled_catalog() {
let r = RouteResolver::new();
let glm = r
.resolve(&req(Some(ProviderKind::Zai), Some("GLM-5.2")))
.expect("Z.ai GLM-5.2 should resolve from the bundled catalog");
assert_eq!(glm.provider_kind(), ProviderKind::Zai);
assert_eq!(glm.wire_model_id().as_str(), "GLM-5.2");
assert_eq!(
glm.limits().context_tokens,
Some(1_000_000),
"GLM-5.2 must carry its real context window, not the unknown default"
);
assert_eq!(glm.limits().output_tokens, Some(131_072));
assert!(glm.limits().has_known_limit());
let kimi_k27 = r
.resolve(&req(Some(ProviderKind::Moonshot), Some("kimi-k2.7-code")))
.expect("Moonshot kimi-k2.7-code should resolve from the bundled catalog");
assert_eq!(kimi_k27.limits().context_tokens, Some(262_144));
assert_eq!(kimi_k27.limits().output_tokens, Some(262_144));
let kimi_k3 = r
.resolve(&req(Some(ProviderKind::Moonshot), Some("kimi-k3")))
.expect("Moonshot kimi-k3 should resolve from the bundled catalog");
assert_eq!(kimi_k3.limits().context_tokens, Some(1_048_576));
assert_eq!(kimi_k3.limits().output_tokens, Some(131_072));
let glm51 = r
.resolve(&req(Some(ProviderKind::Zai), Some("glm-5.1")))
.expect("Z.ai glm-5.1 should resolve from the bundled catalog");
assert_eq!(glm51.limits().context_tokens, Some(202_752));
assert!(matches!(
glm51.pricing(),
Some(super::candidate::PricingSku::Token { .. })
));
}
#[test]
fn default_resolver_preserves_seam_canonical_joins() {
let r = RouteResolver::new();
let direct = r
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("deepseek-v4-pro resolves");
assert_eq!(
direct.canonical_model().map(ModelId::as_str),
Some("deepseek-v4-pro")
);
let hosted = r
.resolve(&req(
Some(ProviderKind::Together),
Some("deepseek-ai/DeepSeek-V4-Pro"),
))
.expect("together hosted deepseek resolves");
assert_eq!(
hosted.canonical_model().map(ModelId::as_str),
Some("deepseek-v4-pro"),
"seam canonical join must survive the asset merge"
);
assert_eq!(
hosted.wire_model_id().as_str(),
"deepseek-ai/DeepSeek-V4-Pro"
);
}
#[test]
fn together_inkling_aliases_use_the_exact_wire_identity_without_invented_metadata() {
let resolver = RouteResolver::new();
for requested in ["inkling", "together-inkling", "thinkingmachines/inkling"] {
let route = resolver
.resolve(&req(Some(ProviderKind::Together), Some(requested)))
.expect("Together Inkling route should resolve");
assert_eq!(route.provider_kind(), ProviderKind::Together, "{requested}");
assert_eq!(
route.wire_model_id().as_str(),
"thinkingmachines/inkling",
"{requested}"
);
assert!(route.canonical_model().is_none(), "{requested}");
assert!(!route.limits().has_known_limit(), "{requested}");
assert!(matches!(
route.pricing(),
Some(super::candidate::PricingSku::UnknownOrStale)
));
}
}
#[test]
fn together_custom_endpoint_preserves_its_explicit_model_id() {
let resolver = RouteResolver::new();
let route = resolver
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Together),
model_selector: Some(LogicalModelRef::from("inkling")),
saved_provider_model: None,
base_url_override: Some("http://127.0.0.1:8000/v1".to_string()),
limit_overrides: Vec::new(),
})
.expect("custom Together-compatible endpoint should resolve");
assert_eq!(route.wire_model_id().as_str(), "inkling");
}
#[test]
fn openrouter_qwen37_plus_aliases_use_exact_catalog_wire_identity() {
let resolver = RouteResolver::new();
for requested in ["qwen3.7-plus", "qwen-3.7-plus", "qwen/qwen3.7-plus"] {
let route = resolver
.resolve(&req(Some(ProviderKind::Openrouter), Some(requested)))
.expect("OpenRouter Qwen 3.7 Plus route should resolve");
assert_eq!(route.wire_model_id().as_str(), "qwen/qwen3.7-plus");
assert!(!route.limits().has_known_limit());
assert!(matches!(
route.pricing(),
Some(super::candidate::PricingSku::Token {
input_per_mtok: Some(_),
output_per_mtok: Some(_)
})
));
}
}
#[test]
fn openrouter_custom_endpoint_preserves_qwen37_alias() {
let route = RouteResolver::new()
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Openrouter),
model_selector: Some(LogicalModelRef::from("qwen3.7-plus")),
saved_provider_model: None,
base_url_override: Some("https://gateway.example.test/v1".to_string()),
limit_overrides: Vec::new(),
})
.expect("custom OpenRouter-compatible endpoint should resolve");
assert_eq!(route.wire_model_id().as_str(), "qwen3.7-plus");
}
#[test]
fn opencode_go_resolver_accepts_only_chat_completions_models() {
let resolver = RouteResolver::new();
let chat_models = crate::OPENCODE_GO_CHAT_MODELS;
assert!(chat_models.contains(&"grok-4.5"));
assert!(chat_models.contains(&"kimi-k3"));
for &model in chat_models {
for requested in [model.to_string(), format!("opencode-go/{model}")] {
let route = resolver
.resolve(&req(Some(ProviderKind::OpencodeGo), Some(&requested)))
.unwrap_or_else(|error| panic!("{requested} should resolve: {error}"));
assert_eq!(
route.provider_kind(),
ProviderKind::OpencodeGo,
"{requested}"
);
assert_eq!(route.wire_model_id().as_str(), model, "{requested}");
}
}
let automatic = resolver
.resolve(&req(Some(ProviderKind::OpencodeGo), Some("auto")))
.expect("OpenCode Go auto should resolve to its Chat default");
assert_eq!(automatic.wire_model_id().as_str(), "deepseek-v4-pro");
}
#[test]
fn opencode_go_resolver_rejects_messages_models_even_on_custom_base_urls() {
let resolver = RouteResolver::new();
let messages_models = [
"minimax-m3",
"minimax-m2.7",
"minimax-m2.5",
"qwen3.7-max",
"qwen3.7-plus",
"qwen3.6-plus",
];
for model in messages_models {
for requested in [model.to_string(), format!("opencode-go/{model}")] {
for base_url_override in [None, Some("https://go-gateway.example.test/v1".to_string())]
{
let request = RouteRequest {
explicit_provider: Some(ProviderKind::OpencodeGo),
model_selector: Some(LogicalModelRef::from(requested.as_str())),
saved_provider_model: None,
base_url_override,
limit_overrides: Vec::new(),
};
assert!(
matches!(
resolver.resolve(&request),
Err(RouteError::ForeignModelForDirectProvider { .. })
),
"{requested} must not reach OpenCode Go Chat Completions"
);
}
}
}
}
#[test]
fn opencode_zen_resolver_selects_protocol_from_documented_model_catalog() {
use super::offering::{
OPENCODE_ZEN_CHAT_MODELS, OPENCODE_ZEN_MESSAGES_MODELS, OPENCODE_ZEN_RESPONSES_MODELS,
};
let resolver = RouteResolver::new();
let groups = [
(
OPENCODE_ZEN_RESPONSES_MODELS,
"responses",
RequestProtocol::Responses,
),
(
OPENCODE_ZEN_MESSAGES_MODELS,
"messages",
RequestProtocol::AnthropicMessages,
),
(
OPENCODE_ZEN_CHAT_MODELS,
"chat",
RequestProtocol::ChatCompletions,
),
];
for (models, endpoint_key, protocol) in groups {
for model in models {
for requested in [model.to_string(), format!("opencode/{model}")] {
let route = resolver
.resolve(&req(Some(ProviderKind::OpencodeZen), Some(&requested)))
.unwrap_or_else(|error| panic!("{requested} should resolve: {error}"));
assert_eq!(route.provider_kind(), ProviderKind::OpencodeZen);
assert_eq!(route.wire_model_id().as_str(), *model, "{requested}");
assert_eq!(route.endpoint().endpoint_key, endpoint_key, "{requested}");
assert_eq!(route.protocol(), protocol, "{requested}");
}
}
}
let automatic = resolver
.resolve(&req(Some(ProviderKind::OpencodeZen), Some("auto")))
.expect("OpenCode Zen auto should resolve to its documented default");
assert_eq!(automatic.wire_model_id().as_str(), "gpt-5.6-sol");
assert_eq!(automatic.protocol(), RequestProtocol::Responses);
let logical_gpt_5_6 = resolver
.resolve(&req(Some(ProviderKind::OpencodeZen), Some("gpt-5.6")))
.expect("the logical GPT-5.6 picker row resolves to Zen's proven Sol wire route");
assert_eq!(logical_gpt_5_6.wire_model_id().as_str(), "gpt-5.6-sol");
assert_eq!(logical_gpt_5_6.protocol(), RequestProtocol::Responses);
}
#[test]
fn opencode_zen_resolver_fails_closed_for_unproven_protocols() {
let resolver = RouteResolver::new();
for model in [
"gemini-3.1-pro",
"opencode/gemini-3.5-flash",
"unknown-model",
] {
for base_url_override in [
None,
Some("https://zen-gateway.example.test/v1".to_string()),
] {
let request = RouteRequest {
explicit_provider: Some(ProviderKind::OpencodeZen),
model_selector: Some(LogicalModelRef::from(model)),
saved_provider_model: None,
base_url_override,
limit_overrides: Vec::new(),
};
assert!(
matches!(
resolver.resolve(&request),
Err(RouteError::UnsupportedModelProtocol { .. })
),
"{model} must fail closed without a supported protocol mapping"
);
}
}
}
#[test]
fn resolver_deepseek_none_selector_uses_default_wire_id() {
let r = RouteResolver::new();
let out = r
.resolve(&req(Some(ProviderKind::Deepseek), None))
.expect("none selector should use provider default");
assert_eq!(out.provider_kind(), ProviderKind::Deepseek);
assert_eq!(out.wire_model_id().as_str(), "deepseek-v4-pro");
}
#[test]
fn resolver_empty_string_selector_is_empty_model_error() {
let r = RouteResolver::new();
let out = r.resolve(&req(Some(ProviderKind::Deepseek), Some("")));
assert!(matches!(out, Err(RouteError::EmptyModel)));
}
#[test]
fn resolver_empty_saved_provider_model_is_empty_model_error() {
let r = RouteResolver::new();
let request = RouteRequest {
explicit_provider: Some(ProviderKind::Deepseek),
model_selector: None,
saved_provider_model: Some(WireModelId::from("")),
base_url_override: None,
limit_overrides: Vec::new(),
};
assert!(matches!(r.resolve(&request), Err(RouteError::EmptyModel)));
}
#[test]
fn resolver_passthrough_provider_preserves_custom_id_verbatim() {
let r = RouteResolver::new();
let out = r
.resolve(&req(Some(ProviderKind::Ollama), Some("my-local:7b")))
.expect("local passthrough should resolve");
assert_eq!(out.provider_kind(), ProviderKind::Ollama);
assert_eq!(out.wire_model_id().as_str(), "my-local:7b");
assert_eq!(out.limits(), Default::default());
assert!(out.validation().ok);
}
#[test]
fn resolved_candidate_serializes_secret_free() {
let r = RouteResolver::new();
let candidates = [
r.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("direct resolves"),
r.resolve(&req(
Some(ProviderKind::Together),
Some("deepseek-ai/DeepSeek-V4-Pro"),
))
.expect("aggregator resolves"),
r.resolve(&req(Some(ProviderKind::Ollama), Some("my-local:7b")))
.expect("local resolves"),
];
for out in candidates {
let json = serde_json::to_string(&out).expect("candidate serializes");
assert!(json.contains("provider_id"), "{json}");
assert!(json.contains("provider_kind"), "{json}");
assert!(json.contains("wire_model_id"), "{json}");
assert!(json.contains("protocol"), "{json}");
assert!(json.contains("auth"), "{json}");
let lower = json.to_lowercase();
assert!(!lower.contains("api_key"), "leaked api_key: {json}");
assert!(!lower.contains("apikey"), "leaked apikey: {json}");
assert!(!lower.contains("secret_id"), "leaked secret_id: {json}");
assert!(!lower.contains("password"), "leaked password: {json}");
assert!(!lower.contains("bearer"), "leaked bearer: {json}");
assert!(
!lower.contains("authorization"),
"leaked authorization: {json}"
);
}
}
#[test]
fn resolver_protocol_matches_descriptor_for_every_provider() {
let r = RouteResolver::new();
for kind in ProviderKind::ALL {
let default_wire = ProviderDescriptor::for_kind(kind).default_wire_model();
let request = req(Some(kind), Some(default_wire.as_str()));
let out = r
.resolve(&request)
.unwrap_or_else(|e| panic!("{kind:?} should resolve its own default: {e}"));
assert_eq!(
out.protocol(),
ProviderDescriptor::for_kind(kind)
.protocol_for_endpoint(&out.endpoint().endpoint_key)
.expect("resolved endpoint protocol"),
"{kind:?} candidate protocol must match descriptor"
);
assert_eq!(
out.endpoint().protocol,
out.protocol(),
"{kind:?} endpoint protocol"
);
}
}
fn priced_deepseek_resolver() -> RouteResolver {
use crate::catalog::{CatalogOffering, CatalogSource};
use crate::models_dev::ModelsDevCost;
let priced = CatalogOffering {
provider: "deepseek".into(),
wire_model_id: "deepseek-v4-pro".into(),
canonical_model: Some("deepseek-v4-pro".into()),
endpoint_key: "chat".into(),
default_for_provider: true,
cost: Some(ModelsDevCost {
input: Some(0.28),
output: Some(0.42),
cache_read: Some(0.028),
cache_write: None,
}),
source: CatalogSource::Bundled,
..Default::default()
};
RouteResolver::from_offerings(vec![priced.to_offering()])
}
#[test]
fn resolver_carries_exact_offering_capabilities_without_protocol_inference() {
use crate::catalog::{CatalogOffering, CatalogSource};
use crate::route::CapabilityState;
let offering = CatalogOffering {
provider: "deepseek".into(),
wire_model_id: "deepseek-v4-pro".into(),
canonical_model: Some("deepseek-v4-pro".into()),
endpoint_key: "chat".into(),
default_for_provider: true,
attachment: Some(false),
reasoning: Some(true),
tool_call: Some(false),
structured_output: Some(true),
source: CatalogSource::Bundled,
..Default::default()
};
let resolver = RouteResolver::from_offerings(vec![offering.to_offering()]);
let candidate = resolver
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("matching offering resolves");
let capabilities = candidate.capabilities();
assert_eq!(capabilities.attachments, CapabilityState::Unsupported);
assert_eq!(capabilities.reasoning, CapabilityState::Supported);
assert_eq!(capabilities.native_tool_calls, CapabilityState::Unsupported);
assert_eq!(capabilities.structured_output, CapabilityState::Supported);
assert_eq!(capabilities.streaming, CapabilityState::Unknown);
assert_eq!(
capabilities.server_side_web_search,
CapabilityState::Unknown
);
}
#[test]
fn provider_native_web_search_requires_exact_direct_endpoint_offering() {
use crate::route::CapabilityState;
let resolver = RouteResolver::new();
let direct = resolver
.resolve(&req(Some(ProviderKind::Xai), Some("grok-4.5")))
.expect("bundled direct xAI offering resolves");
assert_eq!(
direct.capabilities().server_side_web_search,
CapabilityState::Supported
);
let aggregator = resolver
.resolve(&req(Some(ProviderKind::OpencodeGo), Some("grok-4.5")))
.expect("aggregator offering resolves independently");
assert_eq!(
aggregator.capabilities().server_side_web_search,
CapabilityState::Unknown
);
let custom_endpoint = resolver
.resolve(&RouteRequest {
explicit_provider: Some(ProviderKind::Xai),
model_selector: Some(LogicalModelRef::from("grok-4.5")),
saved_provider_model: None,
base_url_override: Some("https://gateway.example.test/v1".to_string()),
limit_overrides: Vec::new(),
})
.expect("custom compatible endpoint resolves");
assert_eq!(
custom_endpoint.capabilities().server_side_web_search,
CapabilityState::Unknown
);
}
#[test]
fn priced_offering_yields_token_pricing_sku() {
use super::candidate::PricingSku;
let r = priced_deepseek_resolver();
let out = r
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("priced DeepSeek route should resolve");
match out.pricing() {
Some(PricingSku::Token {
input_per_mtok,
output_per_mtok,
}) => {
assert_eq!(*input_per_mtok, Some(0.28));
assert_eq!(*output_per_mtok, Some(0.42));
}
other => panic!("expected Some(Token), got {other:?}"),
}
}
#[test]
fn unpriced_offering_stays_unknown() {
use super::candidate::PricingSku;
let r = RouteResolver::new();
let out = r
.resolve(&req(Some(ProviderKind::Deepseek), Some("deepseek-v4-pro")))
.expect("bundled DeepSeek route should resolve");
assert!(
matches!(out.pricing(), Some(PricingSku::UnknownOrStale)),
"bundled offering carries no price → UnknownOrStale, got {:?}",
out.pricing()
);
let passthrough = r
.resolve(&req(Some(ProviderKind::Ollama), Some("my-local:7b")))
.expect("local passthrough should resolve");
assert!(matches!(
passthrough.pricing(),
Some(PricingSku::UnknownOrStale)
));
}
fn req_with_base(provider: ProviderKind, model: &str, base_url: &str) -> RouteRequest {
RouteRequest {
explicit_provider: Some(provider),
model_selector: Some(LogicalModelRef::from(model)),
saved_provider_model: None,
base_url_override: Some(base_url.to_string()),
limit_overrides: Vec::new(),
}
}
#[test]
fn http_custom_endpoint_emits_insecure_warning() {
let r = RouteResolver::new();
let out = r
.resolve(&req_with_base(
ProviderKind::Openai,
"gpt-whatever",
"http://example.com/v1",
))
.expect("custom http endpoint should still resolve");
assert!(
out.validation().ok,
"insecure http is advisory, not a hard fail"
);
assert!(
out.validation()
.messages
.iter()
.any(|m| m.contains("insecure http")),
"expected an insecure-http advisory, got {:?}",
out.validation().messages
);
}
#[test]
fn loopback_http_endpoint_does_not_warn() {
let r = RouteResolver::new();
for base in [
"http://localhost:11434/v1",
"http://127.0.0.1:8000/v1",
"http://[::1]:8080/v1",
] {
let out = r
.resolve(&req_with_base(ProviderKind::Ollama, "my-local:7b", base))
.unwrap_or_else(|e| panic!("loopback route {base} should resolve: {e}"));
assert!(out.validation().ok);
assert!(
out.validation().messages.is_empty(),
"loopback {base} must not warn, got {:?}",
out.validation().messages
);
}
}
#[test]
fn https_endpoint_has_no_warning() {
let r = RouteResolver::new();
let out = r
.resolve(&req_with_base(
ProviderKind::Openai,
"gpt-whatever",
"https://example.com/v1",
))
.expect("https endpoint should resolve");
assert!(out.validation().ok);
assert!(
out.validation().messages.is_empty(),
"https must not warn, got {:?}",
out.validation().messages
);
}