feat: add multi-source downloads with automatic fallback

- support GitHub, GitCode, gh-proxy, and ghfast sources
- automatically try other sources when version checks fail
- persist the working fallback source and sync the UI immediately
- update translations, configuration migration, and source tests
This commit is contained in:
lzt404 committed 2026-08-26 01:56:46 +08:00
1 parent e8093928c0
commit 03be4acf93
16 files changed
+507 -157

No files matched your search

+3
View File
@@ -18,6 +18,7 @@ fn gui_field_edit_preserves_comments_and_unknown_configuration() {
auth_dir: path_to_string(&home.join("custom-auth")),
management_secret_key: "custom-secret".to_string(),
usage_statistics_enabled: false,
download_source: VersionDownloadSource::Gitcode,
prefer_gitcode_downloads: true,
..GuiConfigFile::default()
};
@@ -32,6 +33,7 @@ fn gui_field_edit_preserves_comments_and_unknown_configuration() {
assert!(content.contains("silent-start = true"));
assert!(content.contains("management-secret-key = \"custom-secret\""));
assert!(content.contains("usage-statistics-enabled = false"));
assert!(content.contains("download-source = \"gitcode\""));
assert!(content.contains("prefer-gitcode-downloads = true"));
assert!(!content.contains("codex-session-repair-on-launch"));
assert!(!content.contains("claude-code-working-directory"));
@@ -91,6 +93,7 @@ fn gui_config_defaults_are_stable() {
assert!(content.contains("management-secret-key = \"\""));
assert!(content.contains("plugins-enabled = false"));
assert!(content.contains("routing-strategy = \"round-robin\""));
assert!(content.contains("download-source = \"github\""));
assert!(content.contains("prefer-gitcode-downloads = false"));
assert!(content.contains("request-retry = 3"));
assert!(content.contains("max-retry-credentials = 0"));
+70 -7
View File
@@ -212,13 +212,42 @@ fn portable_update_download_order_respects_gitcode_preference() {
&filename,
)];
let github_first = portable_update_download_urls(&asset, false);
assert_eq!(update_download_source_name(github_first[0]), "GitHub");
assert_eq!(update_download_source_name(github_first[1]), "GitCode");
let github_first = portable_update_download_urls(&asset, VersionDownloadSource::Github);
assert_eq!(update_download_source_name(&github_first[0]), "GitHub");
assert_eq!(update_download_source_name(&github_first[1]), "GitCode");
let gitcode_first = portable_update_download_urls(&asset, true);
assert_eq!(update_download_source_name(gitcode_first[0]), "GitCode");
assert_eq!(update_download_source_name(gitcode_first[1]), "GitHub");
let gitcode_first = portable_update_download_urls(&asset, VersionDownloadSource::Gitcode);
assert_eq!(update_download_source_name(&gitcode_first[0]), "GitCode");
assert_eq!(update_download_source_name(&gitcode_first[1]), "GitHub");
let mirror_first = portable_update_download_urls(&asset, VersionDownloadSource::GhProxy);
assert_eq!(
update_download_source_name(&mirror_first[0]),
"gh-proxy.com"
);
assert_eq!(update_download_source_name(&mirror_first[1]), "GitHub");
assert_eq!(update_download_source_name(&mirror_first[2]), "GitCode");
}
#[test]
fn version_detection_candidates_try_every_available_source_once() {
assert_eq!(
version_download_source_candidates(VersionDownloadSource::GhFast, true),
[
VersionDownloadSource::GhFast,
VersionDownloadSource::Github,
VersionDownloadSource::Gitcode,
VersionDownloadSource::GhProxy,
]
);
assert_eq!(
version_download_source_candidates(VersionDownloadSource::Gitcode, false),
[
VersionDownloadSource::Github,
VersionDownloadSource::GhProxy,
VersionDownloadSource::GhFast,
]
);
}
#[test]
@@ -592,7 +621,11 @@ fn synthetic_release_uses_official_asset_names_and_urls() {
#[test]
fn synthetic_core_release_uses_gitcode_as_download_fallback() {
let repository = "lzt404/CLIProxyAPI";
let release = release_from_tag_for_repositories("7.2.80", Some(repository), false);
let release = release_from_tag_for_repositories(
"7.2.80",
Some(repository),
VersionDownloadSource::Github,
);
let platform = CorePlatform {
os: "windows".to_string(),
arch: "x86_64".to_string(),
@@ -634,6 +667,36 @@ fn gitcode_discovered_core_release_downloads_from_gitcode_first() {
);
}
#[test]
fn github_proxy_core_release_uses_proxy_then_official_and_gitcode() {
let release = release_from_tag_for_repositories(
"v7.2.80",
Some("lzt404/CLIProxyAPI"),
VersionDownloadSource::GhFast,
);
let platform = CorePlatform {
os: "windows".to_string(),
arch: "x86_64".to_string(),
asset_os: "windows".to_string(),
asset_arch: "amd64".to_string(),
archive_kind: "zip".to_string(),
};
let asset = select_release_asset(&release, &platform).unwrap();
assert_eq!(
core_download_source_name(&asset.browser_download_url),
"ghfast.top"
);
assert_eq!(
core_download_source_name(&asset.fallback_download_urls[0]),
"GitHub"
);
assert_eq!(
core_download_source_name(&asset.fallback_download_urls[1]),
"GitCode"
);
}
#[test]
fn release_asset_names_cover_the_six_supported_gui_targets() {
let targets = [
+1
View File
@@ -1039,6 +1039,7 @@ fn startup_preserves_all_user_owned_yaml_and_only_applies_gui_managed_values() {
plugins_enabled: true,
routing_strategy: "fill-first".to_string(),
proxy_url: "socks5://127.0.0.1:7890".to_string(),
download_source: VersionDownloadSource::Github,
prefer_gitcode_downloads: false,
routing_session_affinity: true,
routing_session_affinity_ttl: "1h".to_string(),