feat(models): surface DeepSeek alias retirement metadata
Summary: - Add structured retirement metadata for deepseek-chat and deepseek-reasoner aliases. - Keep aliases valid while reporting V4 Flash capability metadata and replacement guidance. - Surface the warning in doctor human output and doctor JSON. Test plan: - cargo test -p deepseek-tui provider_capability_deepseek_chat_alias_has_v4_flash_caps_and_metadata --locked - cargo test -p deepseek-tui provider_capability_deepseek_reasoner_alias_has_v4_flash_caps_and_metadata --locked - cargo test -p deepseek-tui provider_capability_report_exposes_alias_deprecation_for_deepseek_chat --locked - cargo test -p deepseek-tui provider_capability_report_leaves_canonical_flash_alias_metadata_null --locked - cargo test -p deepseek-tui provider_capability_ --locked - cargo test -p deepseek-tui doctor_endpoint_tests --locked - cargo test -p deepseek-tui --locked - cargo clippy -p deepseek-tui --all-targets --all-features --locked -- -D warnings - cargo fmt --all -- --check - git diff --check origin/main...HEAD Closes #940
This commit is contained in:
@@ -160,6 +160,23 @@ pub struct ProviderCapability {
|
||||
pub cache_telemetry_supported: bool,
|
||||
/// Which request-payload dialect the provider uses.
|
||||
pub request_payload_mode: RequestPayloadMode,
|
||||
/// Deprecation metadata for compatibility aliases that are still accepted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub alias_deprecation: Option<ModelAliasDeprecation>,
|
||||
}
|
||||
|
||||
pub const DEEPSEEK_ALIAS_RETIREMENT_DATE: &str = "2026-07-24";
|
||||
pub const DEEPSEEK_ALIAS_RETIREMENT_UTC: &str = "2026-07-24T15:59:00Z";
|
||||
pub const DEEPSEEK_ALIAS_REPLACEMENT: &str = "deepseek-v4-flash";
|
||||
|
||||
/// Upstream retirement metadata for a model alias that remains compatible.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct ModelAliasDeprecation {
|
||||
pub alias: String,
|
||||
pub replacement: String,
|
||||
pub retirement_date: String,
|
||||
pub retirement_utc: String,
|
||||
pub notice: String,
|
||||
}
|
||||
|
||||
/// Which request-payload dialect the provider speaks.
|
||||
@@ -185,14 +202,21 @@ pub fn provider_capability(provider: ApiProvider, resolved_model: &str) -> Provi
|
||||
thinking_supported: false,
|
||||
cache_telemetry_supported: false,
|
||||
request_payload_mode: RequestPayloadMode::ChatCompletions,
|
||||
alias_deprecation: None,
|
||||
};
|
||||
}
|
||||
|
||||
let model_lower = resolved_model.to_ascii_lowercase();
|
||||
let alias_deprecation = if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) {
|
||||
deepseek_alias_deprecation(&model_lower)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let is_v4_pro = model_lower.contains("v4-pro") || model_lower == "deepseek-v4pro";
|
||||
let is_v4_flash = model_lower.contains("v4-flash")
|
||||
|| model_lower == "deepseek-v4flash"
|
||||
|| model_lower == "deepseek-v4";
|
||||
|| model_lower == "deepseek-v4"
|
||||
|| alias_deprecation.is_some();
|
||||
|
||||
// Context window: V4-class models get 1M, everything else falls through
|
||||
// to the model's own lookup or a default.
|
||||
@@ -232,6 +256,22 @@ pub fn provider_capability(provider: ApiProvider, resolved_model: &str) -> Provi
|
||||
thinking_supported,
|
||||
cache_telemetry_supported,
|
||||
request_payload_mode,
|
||||
alias_deprecation,
|
||||
}
|
||||
}
|
||||
|
||||
fn deepseek_alias_deprecation(model_lower: &str) -> Option<ModelAliasDeprecation> {
|
||||
match model_lower {
|
||||
"deepseek-chat" | "deepseek-reasoner" => Some(ModelAliasDeprecation {
|
||||
alias: model_lower.to_string(),
|
||||
replacement: DEEPSEEK_ALIAS_REPLACEMENT.to_string(),
|
||||
retirement_date: DEEPSEEK_ALIAS_RETIREMENT_DATE.to_string(),
|
||||
retirement_utc: DEEPSEEK_ALIAS_RETIREMENT_UTC.to_string(),
|
||||
notice: format!(
|
||||
"{model_lower} is a compatibility alias for {DEEPSEEK_ALIAS_REPLACEMENT} and is scheduled to retire on {DEEPSEEK_ALIAS_RETIREMENT_DATE}."
|
||||
),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4799,6 +4839,52 @@ model = "deepseek-v4-pro"
|
||||
assert!(cap.cache_telemetry_supported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_deepseek_chat_alias_has_v4_flash_caps_and_metadata() {
|
||||
let cap = provider_capability(ApiProvider::Deepseek, "deepseek-chat");
|
||||
assert_eq!(
|
||||
cap.context_window,
|
||||
crate::models::DEEPSEEK_V4_CONTEXT_WINDOW_TOKENS
|
||||
);
|
||||
assert_eq!(cap.max_output, 384_000);
|
||||
assert!(cap.thinking_supported);
|
||||
assert!(cap.cache_telemetry_supported);
|
||||
|
||||
let deprecation = cap
|
||||
.alias_deprecation
|
||||
.as_ref()
|
||||
.expect("alias deprecation metadata");
|
||||
assert_eq!(deprecation.alias, "deepseek-chat");
|
||||
assert_eq!(deprecation.replacement, "deepseek-v4-flash");
|
||||
assert_eq!(deprecation.retirement_date, "2026-07-24");
|
||||
assert_eq!(deprecation.retirement_utc, "2026-07-24T15:59:00Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_deepseek_reasoner_alias_has_v4_flash_caps_and_metadata() {
|
||||
let cap = provider_capability(ApiProvider::Deepseek, "deepseek-reasoner");
|
||||
assert_eq!(
|
||||
cap.context_window,
|
||||
crate::models::DEEPSEEK_V4_CONTEXT_WINDOW_TOKENS
|
||||
);
|
||||
assert_eq!(cap.max_output, 384_000);
|
||||
assert!(cap.thinking_supported);
|
||||
assert!(cap.cache_telemetry_supported);
|
||||
|
||||
let deprecation = cap
|
||||
.alias_deprecation
|
||||
.as_ref()
|
||||
.expect("alias deprecation metadata");
|
||||
assert_eq!(deprecation.alias, "deepseek-reasoner");
|
||||
assert_eq!(deprecation.replacement, "deepseek-v4-flash");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_deepseek_v4_flash_has_no_alias_deprecation() {
|
||||
let cap = provider_capability(ApiProvider::Deepseek, "deepseek-v4-flash");
|
||||
assert!(cap.alias_deprecation.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_nvidia_nim_v4_pro_maps_correctly() {
|
||||
let cap = provider_capability(ApiProvider::NvidiaNim, DEFAULT_NVIDIA_NIM_MODEL);
|
||||
|
||||
@@ -1684,6 +1684,13 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
|
||||
println!(" · provider: {}", api_target.provider);
|
||||
println!(" · base_url: {}", api_target.base_url);
|
||||
println!(" · model: {}", api_target.model);
|
||||
let capability = crate::config::provider_capability(config.api_provider(), &api_target.model);
|
||||
if let Some(alias) = capability.alias_deprecation.as_ref() {
|
||||
println!(
|
||||
" ! model alias {} retires {}; switch to {}",
|
||||
alias.alias, alias.retirement_date, alias.replacement
|
||||
);
|
||||
}
|
||||
if has_api_key {
|
||||
print!(" {} Testing connection...", "·".dimmed());
|
||||
use std::io::Write;
|
||||
@@ -2314,6 +2321,7 @@ fn provider_capability_report(config: &Config) -> serde_json::Value {
|
||||
"thinking_supported": cap.thinking_supported,
|
||||
"cache_telemetry_supported": cap.cache_telemetry_supported,
|
||||
"request_payload_mode": serde_json::to_value(cap.request_payload_mode).unwrap_or_default(),
|
||||
"alias_deprecation": cap.alias_deprecation,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4290,6 +4298,41 @@ mod doctor_endpoint_tests {
|
||||
assert_eq!(target.model, crate::config::DEFAULT_TEXT_MODEL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_report_exposes_alias_deprecation_for_deepseek_chat() {
|
||||
let config = Config {
|
||||
default_text_model: Some("deepseek-chat".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let report = provider_capability_report(&config);
|
||||
|
||||
assert_eq!(report["resolved_model"], "deepseek-chat");
|
||||
assert_eq!(report["context_window"], 1_000_000);
|
||||
assert_eq!(report["thinking_supported"], true);
|
||||
assert_eq!(
|
||||
report["alias_deprecation"]["replacement"],
|
||||
"deepseek-v4-flash"
|
||||
);
|
||||
assert_eq!(
|
||||
report["alias_deprecation"]["retirement_utc"],
|
||||
"2026-07-24T15:59:00Z"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_report_leaves_canonical_flash_alias_metadata_null() {
|
||||
let config = Config {
|
||||
default_text_model: Some("deepseek-v4-flash".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let report = provider_capability_report(&config);
|
||||
|
||||
assert_eq!(report["resolved_model"], "deepseek-v4-flash");
|
||||
assert!(report["alias_deprecation"].is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timeout_recovery_points_global_deepseek_users_to_cn_endpoint() {
|
||||
let config = Config::default();
|
||||
|
||||
Reference in New Issue
Block a user