Normalize canonical API formats

This commit is contained in:
fawney19
2026-04-29 09:25:19 +08:00
parent 02ad67fe33
commit 07a319259b
171 changed files with 2460 additions and 1962 deletions

View File

@@ -19,44 +19,5 @@ pub(crate) fn read_cached_scheduler_affinity_target(
api_format,
global_model_name,
)?;
state
.read_cached_scheduler_affinity_target(&cache_key, SCHEDULER_AFFINITY_TTL)
.or_else(|| {
openai_responses_affinity_alias(api_format)
.and_then(|alias| {
legacy_raw_scheduler_affinity_cache_key(api_key_id, alias, global_model_name)
})
.and_then(|alias_cache_key| {
state.read_cached_scheduler_affinity_target(
&alias_cache_key,
SCHEDULER_AFFINITY_TTL,
)
})
})
}
fn openai_responses_affinity_alias(api_format: &str) -> Option<&'static str> {
match api_format.trim().to_ascii_lowercase().as_str() {
"openai:responses" => Some("openai:cli"),
"openai:responses:compact" => Some("openai:compact"),
"openai:cli" => Some("openai:responses"),
"openai:compact" => Some("openai:responses:compact"),
_ => None,
}
}
fn legacy_raw_scheduler_affinity_cache_key(
api_key_id: &str,
api_format: &str,
global_model_name: &str,
) -> Option<String> {
let api_key_id = api_key_id.trim();
let api_format = api_format.trim().to_ascii_lowercase();
let global_model_name = global_model_name.trim();
if api_key_id.is_empty() || api_format.is_empty() || global_model_name.is_empty() {
return None;
}
Some(format!(
"scheduler_affinity:{api_key_id}:{api_format}:{global_model_name}"
))
state.read_cached_scheduler_affinity_target(&cache_key, SCHEDULER_AFFINITY_TTL)
}

View File

@@ -75,9 +75,9 @@ async fn exclusive_required_capability_keeps_hard_filtering_only_matching_keys()
incompatible.provider_id = "provider-a".to_string();
incompatible.provider_name = "provider-a".to_string();
incompatible.endpoint_id = "endpoint-a".to_string();
incompatible.endpoint_api_format = "gemini:chat".to_string();
incompatible.endpoint_api_format = "gemini:generate_content".to_string();
incompatible.endpoint_api_family = Some("gemini".to_string());
incompatible.key_api_formats = Some(vec!["gemini:chat".to_string()]);
incompatible.key_api_formats = Some(vec!["gemini:generate_content".to_string()]);
incompatible.key_id = "key-a".to_string();
incompatible.key_name = "alpha".to_string();
incompatible.global_model_name = "gemini-2.5-pro".to_string();
@@ -87,9 +87,9 @@ async fn exclusive_required_capability_keeps_hard_filtering_only_matching_keys()
compatible.provider_id = "provider-b".to_string();
compatible.provider_name = "provider-b".to_string();
compatible.endpoint_id = "endpoint-b".to_string();
compatible.endpoint_api_format = "gemini:chat".to_string();
compatible.endpoint_api_format = "gemini:generate_content".to_string();
compatible.endpoint_api_family = Some("gemini".to_string());
compatible.key_api_formats = Some(vec!["gemini:chat".to_string()]);
compatible.key_api_formats = Some(vec!["gemini:generate_content".to_string()]);
compatible.key_id = "key-b".to_string();
compatible.key_name = "beta".to_string();
compatible.global_model_name = "gemini-2.5-pro".to_string();
@@ -109,7 +109,7 @@ async fn exclusive_required_capability_keeps_hard_filtering_only_matching_keys()
let selection = list_selectable_candidates_for_required_capability_without_requested_model(
state.data.as_ref(),
&state,
"gemini:chat",
"gemini:generate_content",
"gemini_files",
false,
None,

View File

@@ -1385,9 +1385,11 @@ async fn keeps_refreshable_kiro_candidate_selectable_with_runtime_oauth_invalid_
row.provider_name = "kiro".to_string();
row.provider_type = "kiro".to_string();
row.endpoint_id = "endpoint-kiro".to_string();
row.endpoint_api_format = "claude:messages".to_string();
row.key_id = "key-kiro".to_string();
row.key_name = "kiro-refreshable".to_string();
row.key_auth_type = "oauth".to_string();
row.key_api_formats = Some(vec!["claude:messages".to_string()]);
let candidates = Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
row,
@@ -1422,7 +1424,7 @@ async fn keeps_refreshable_kiro_candidate_selectable_with_runtime_oauth_invalid_
let (selected, skipped) = collect_selectable_candidates_with_skip_reasons(
state.data.as_ref(),
&state,
"openai:chat",
"claude:messages",
"gpt-4.1",
false,
None,
@@ -1443,9 +1445,11 @@ async fn keeps_refreshable_kiro_candidate_selectable_when_oauth_token_expired()
row.provider_name = "kiro".to_string();
row.provider_type = "kiro".to_string();
row.endpoint_id = "endpoint-kiro".to_string();
row.endpoint_api_format = "claude:messages".to_string();
row.key_id = "key-kiro".to_string();
row.key_name = "kiro-expired-refreshable".to_string();
row.key_auth_type = "oauth".to_string();
row.key_api_formats = Some(vec!["claude:messages".to_string()]);
let candidates = Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
row,
@@ -1479,7 +1483,7 @@ async fn keeps_refreshable_kiro_candidate_selectable_when_oauth_token_expired()
let (selected, skipped) = collect_selectable_candidates_with_skip_reasons(
state.data.as_ref(),
&state,
"openai:chat",
"claude:messages",
"gpt-4.1",
false,
None,
@@ -1500,9 +1504,11 @@ async fn skips_kiro_candidate_after_refresh_failure_requires_reauth() {
row.provider_name = "kiro".to_string();
row.provider_type = "kiro".to_string();
row.endpoint_id = "endpoint-kiro".to_string();
row.endpoint_api_format = "claude:messages".to_string();
row.key_id = "key-kiro".to_string();
row.key_name = "kiro-refresh-failed".to_string();
row.key_auth_type = "oauth".to_string();
row.key_api_formats = Some(vec!["claude:messages".to_string()]);
let candidates = Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
row,
@@ -1540,7 +1546,7 @@ async fn skips_kiro_candidate_after_refresh_failure_requires_reauth() {
let (selected, skipped) = collect_selectable_candidates_with_skip_reasons(
state.data.as_ref(),
&state,
"openai:chat",
"claude:messages",
"gpt-4.1",
false,
None,
@@ -1562,9 +1568,11 @@ async fn skips_refreshable_kiro_candidate_when_oauth_marker_is_account_block() {
row.provider_name = "kiro".to_string();
row.provider_type = "kiro".to_string();
row.endpoint_id = "endpoint-kiro".to_string();
row.endpoint_api_format = "claude:messages".to_string();
row.key_id = "key-kiro".to_string();
row.key_name = "kiro-account-blocked".to_string();
row.key_auth_type = "oauth".to_string();
row.key_api_formats = Some(vec!["claude:messages".to_string()]);
let candidates = Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
row,
@@ -1599,7 +1607,7 @@ async fn skips_refreshable_kiro_candidate_when_oauth_marker_is_account_block() {
let (selected, skipped) = collect_selectable_candidates_with_skip_reasons(
state.data.as_ref(),
&state,
"openai:chat",
"claude:messages",
"gpt-4.1",
false,
None,
@@ -1702,22 +1710,22 @@ async fn skips_kiro_candidate_when_account_quota_is_exhausted_and_pool_flag_enab
first.provider_name = "kiro".to_string();
first.provider_type = "kiro".to_string();
first.endpoint_id = "endpoint-kiro".to_string();
first.endpoint_api_format = "claude:cli".to_string();
first.endpoint_api_format = "claude:messages".to_string();
first.key_id = "key-kiro".to_string();
first.key_name = "kiro-exhausted".to_string();
first.key_auth_type = "oauth".to_string();
first.key_api_formats = Some(vec!["claude:cli".to_string()]);
first.key_global_priority_by_format = Some(serde_json::json!({"claude:cli": 1}));
first.key_api_formats = Some(vec!["claude:messages".to_string()]);
first.key_global_priority_by_format = Some(serde_json::json!({"claude:messages": 1}));
let mut second = sample_row();
second.provider_id = "provider-openai".to_string();
second.provider_name = "openai".to_string();
second.endpoint_id = "endpoint-openai".to_string();
second.endpoint_api_format = "claude:cli".to_string();
second.endpoint_api_format = "claude:messages".to_string();
second.key_id = "key-openai".to_string();
second.key_name = "fallback".to_string();
second.key_api_formats = Some(vec!["claude:cli".to_string()]);
second.key_global_priority_by_format = Some(serde_json::json!({"claude:cli": 2}));
second.key_api_formats = Some(vec!["claude:messages".to_string()]);
second.key_global_priority_by_format = Some(serde_json::json!({"claude:messages": 2}));
let candidates = Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
first, second,
@@ -1762,7 +1770,7 @@ async fn skips_kiro_candidate_when_account_quota_is_exhausted_and_pool_flag_enab
let (selected, skipped) = collect_selectable_candidates_with_skip_reasons(
state.data.as_ref(),
&state,
"claude:cli",
"claude:messages",
"gpt-4.1",
false,
None,
@@ -1878,14 +1886,14 @@ async fn same_priority_candidates_use_aggregate_health_score_when_api_format_spe
sample_key("key-a", "provider-a", Some(10)).with_health_fields(
Some(serde_json::json!({
"openai:responses": {"health_score": 0.40},
"claude:chat": {"health_score": 0.55}
"claude:messages": {"health_score": 0.55}
})),
None,
),
sample_key("key-b", "provider-b", Some(10)).with_health_fields(
Some(serde_json::json!({
"openai:responses": {"health_score": 0.90},
"claude:chat": {"health_score": 0.92}
"claude:messages": {"health_score": 0.92}
})),
None,
),