fix(routing): harden routed pool scheduling

This commit is contained in:
elky
2026-07-27 22:06:28 +08:00
parent 550cc36760
commit 4148ab1931
62 changed files with 5510 additions and 850 deletions
+23
View File
@@ -604,6 +604,10 @@ mod tests {
.iter()
.find(|item| item["name"] == "recent_refresh")
.expect("recent_refresh should exist");
let legacy_free_team = items
.iter()
.find(|item| item["name"] == "free_team_first")
.expect("legacy free_team_first should remain configurable");
assert_eq!(
free_first["providers"],
@@ -613,6 +617,14 @@ mod tests {
recent_refresh["providers"],
json!(["codex", "grok", "kiro", "windsurf"])
);
assert_eq!(free_first["default_enabled"], json!(false));
assert_eq!(recent_refresh["default_enabled"], json!(false));
assert_eq!(
recent_refresh["default_enabled_providers"],
json!(["codex", "windsurf"])
);
assert_eq!(legacy_free_team["default_mode"], json!("both"));
assert_eq!(legacy_free_team["modes"].as_array().map(Vec::len), Some(3));
}
#[test]
@@ -657,6 +669,17 @@ mod tests {
["cache_affinity", "recent_refresh"]
);
let legacy_free_team = service.normalize_scheduling_presets(
"codex",
&[PoolSchedulingPreset {
preset: "free_team_first".to_string(),
enabled: true,
mode: Some("team_only".to_string()),
}],
);
assert_eq!(legacy_free_team[0].preset, "free_team_first");
assert_eq!(legacy_free_team[0].mode.as_deref(), Some("team_only"));
let unsupported = service.normalize_scheduling_presets(
"chatgpt_web",
&[PoolSchedulingPreset {
+33 -1
View File
@@ -110,6 +110,7 @@ pub fn build_admin_pool_scheduling_presets_payload() -> Value {
"依据窗口成本/Token 用量,缺失时回退配额使用率",
&service,
),
legacy_free_team_first_preset_payload(&service),
provider_pool_preset_payload(
"free_first",
"Free 优先",
@@ -201,6 +202,24 @@ pub fn build_admin_pool_scheduling_presets_payload() -> Value {
])
}
fn legacy_free_team_first_preset_payload(service: &ProviderPoolService) -> Value {
let mut payload = provider_pool_preset_payload(
"free_team_first",
"Free/Team 优先",
"兼容旧配置:优先消耗 Free、Team 或两者",
Some(ProviderPoolCapability::PlanTier),
"依据 plan_type,保留旧 free_only/team_only/both 语义",
service,
);
payload["modes"] = json!([
{"value": "free_only", "label": "Free"},
{"value": "team_only", "label": "Team"},
{"value": "both", "label": "Free + Team"}
]);
payload["default_mode"] = json!("both");
payload
}
fn provider_pool_preset_payload(
name: &'static str,
label: &'static str,
@@ -212,11 +231,24 @@ fn provider_pool_preset_payload(
let providers = capability
.map(|capability| service.provider_types_for_capability(capability))
.unwrap_or_default();
let default_enabled_providers = service
.provider_types()
.filter(|provider_type| {
service
.adapter(provider_type)
.default_scheduling_presets()
.iter()
.any(|preset| preset.enabled && preset.preset.eq_ignore_ascii_case(name))
})
.map(str::to_string)
.collect::<Vec<_>>();
json!({
"name": name,
"label": label,
"description": description,
"providers": providers,
"default_enabled": name == "cache_affinity",
"default_enabled_providers": default_enabled_providers,
"modes": Value::Null,
"default_mode": Value::Null,
"mutex_group": provider_pool_preset_mutex_group(name),
@@ -226,7 +258,7 @@ fn provider_pool_preset_payload(
fn provider_pool_supports_preset(adapter: &dyn ProviderPoolAdapter, preset: &str) -> bool {
match preset {
"free_first" | "plus_first" | "pro_first" | "team_first" => adapter
"free_first" | "free_team_first" | "plus_first" | "pro_first" | "team_first" => adapter
.capabilities()
.supports(ProviderPoolCapability::PlanTier),
"recent_refresh" => adapter