feat(gateway): harden provider request execution

Preserve exact request payloads and model client surface and API operation explicitly.

Add Anthropic compatibility profiles, bounded stream commitment, and scoped OAuth retry behavior across provider transports.
This commit is contained in:
elky
2026-07-27 09:36:31 +08:00
parent 79b70f7b5c
commit 531cf11025
152 changed files with 13984 additions and 2075 deletions
@@ -335,16 +335,13 @@ fn key_auth_channel_matches(row: &StoredMinimalCandidateSelectionRow, api_format
&& api_format == "openai:chat"
}
"vertex_ai" => {
(auth_type == "api_key"
&& matches!(
api_format.as_str(),
"gemini:generate_content" | "gemini:embedding"
))
|| (matches!(auth_type.as_str(), "service_account" | "vertex_ai")
&& matches!(
api_format.as_str(),
"claude:messages" | "gemini:generate_content" | "gemini:embedding"
))
matches!(
auth_type.as_str(),
"api_key" | "service_account" | "vertex_ai"
) && matches!(
api_format.as_str(),
"gemini:generate_content" | "gemini:embedding"
)
}
_ => auth_type != "oauth",
}
@@ -640,6 +637,48 @@ mod tests {
);
}
#[tokio::test]
async fn vertex_auth_matrix_rejects_claude_and_keeps_gemini_candidates() {
let mut candidates = Vec::new();
for auth_type in ["api_key", "service_account", "vertex_ai"] {
for api_format in [
"claude:messages",
"gemini:generate_content",
"gemini:embedding",
] {
let provider_id = format!(
"vertex-{}-{}",
auth_type,
api_format.replace(':', "-").replace('_', "-")
);
let mut row = sample_row(&provider_id, api_format, "vertex-model", 10);
row.provider_type = "vertex_ai".to_string();
row.key_auth_type = auth_type.to_string();
candidates.push(row);
}
}
let repository = InMemoryMinimalCandidateSelectionReadRepository::seed(candidates);
let claude_rows = repository
.list_for_exact_api_format("claude:messages")
.await
.expect("list should succeed");
assert!(claude_rows.is_empty());
for api_format in ["gemini:generate_content", "gemini:embedding"] {
let rows = repository
.list_for_exact_api_format(api_format)
.await
.expect("list should succeed");
let mut auth_types = rows
.into_iter()
.map(|row| row.key_auth_type)
.collect::<Vec<_>>();
auth_types.sort();
assert_eq!(auth_types, ["api_key", "service_account", "vertex_ai"]);
}
}
#[tokio::test]
async fn filters_by_exact_api_format_only() {
let repository = InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
@@ -779,6 +779,15 @@ impl ProviderCatalogWriteRepository for InMemoryProviderCatalogReadRepository {
.encrypted_api_key_update
.as_deref()
.is_some_and(|value| value.trim().is_empty())
|| update.expected_credential.as_ref().is_some_and(|expected| {
expected
.encrypted_api_key
.as_deref()
.is_some_and(|value| value.trim().is_empty())
|| expected.auth_type.trim().is_empty()
|| expected.provider_id.trim().is_empty()
|| expected.provider_type.trim().is_empty()
})
|| !update.status_snapshot_patch.is_object()
|| update
.upstream_metadata_patch
@@ -799,13 +808,30 @@ impl ProviderCatalogWriteRepository for InMemoryProviderCatalogReadRepository {
.index
.write()
.expect("provider catalog repository lock");
let Some(key) = index.keys.get_mut(&update.key_id) else {
let Some(key) = index.keys.get(&update.key_id) else {
return Ok(false);
};
if key.encrypted_auth_config.as_deref() != update.expected_encrypted_auth_config.as_deref()
{
return Ok(false);
}
if let Some(expected) = update.expected_credential.as_ref() {
let provider_type_matches = index
.providers
.get(&key.provider_id)
.is_some_and(|provider| provider.provider_type == expected.provider_type);
if key.encrypted_api_key != expected.encrypted_api_key
|| key.auth_type != expected.auth_type
|| key.provider_id != expected.provider_id
|| !provider_type_matches
{
return Ok(false);
}
}
let key = index
.keys
.get_mut(&update.key_id)
.expect("provider catalog key was checked under the same write lock");
if let Some(encrypted_api_key) = update.encrypted_api_key_update.as_ref() {
key.encrypted_api_key = Some(encrypted_api_key.clone());
}
@@ -1170,10 +1196,10 @@ mod tests {
use crate::repository::provider_catalog::{
ProviderCatalogKeyAdaptiveState, ProviderCatalogKeyAdaptiveStateUpdate,
ProviderCatalogKeyHealthStateUpdate, ProviderCatalogKeyListOrder,
ProviderCatalogKeyListQuery, ProviderCatalogKeyOAuthRuntimeStateCasUpdate,
ProviderCatalogKeyRuntimeMetadataUpdate, ProviderCatalogReadRepository,
ProviderCatalogWriteRepository, StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
StoredProviderCatalogProvider,
ProviderCatalogKeyListQuery, ProviderCatalogKeyOAuthCredentialFence,
ProviderCatalogKeyOAuthRuntimeStateCasUpdate, ProviderCatalogKeyRuntimeMetadataUpdate,
ProviderCatalogReadRepository, ProviderCatalogWriteRepository,
StoredProviderCatalogEndpoint, StoredProviderCatalogKey, StoredProviderCatalogProvider,
};
use crate::repository::usage::ProviderApiKeyUsageDelta;
use serde_json::{json, Value};
@@ -1347,6 +1373,12 @@ mod tests {
let update = ProviderCatalogKeyOAuthRuntimeStateCasUpdate {
key_id: "key-1".to_string(),
expected_encrypted_auth_config: Some("ciphertext-auth-1".to_string()),
expected_credential: Some(ProviderCatalogKeyOAuthCredentialFence {
encrypted_api_key: Some("ciphertext-placeholder".to_string()),
auth_type: "api_key".to_string(),
provider_id: "provider-1".to_string(),
provider_type: "custom".to_string(),
}),
encrypted_auth_config: "ciphertext-auth-2".to_string(),
encrypted_api_key_update: Some("ciphertext-api-2".to_string()),
expires_at_unix_secs_update: Some(Some(456)),
@@ -1391,6 +1423,99 @@ mod tests {
);
}
#[tokio::test]
async fn oauth_runtime_cas_rejects_changed_credential_context() {
let repository = || {
InMemoryProviderCatalogReadRepository::seed(
vec![sample_provider("provider-1")],
vec![],
vec![sample_key("key-1", "provider-1")
.with_transport_fields(
None,
"ciphertext-api-1".to_string(),
Some("ciphertext-auth-1".to_string()),
None,
None,
None,
None,
None,
None,
)
.expect("key transport should build")],
)
};
let update = || ProviderCatalogKeyOAuthRuntimeStateCasUpdate {
key_id: "key-1".to_string(),
expected_encrypted_auth_config: Some("ciphertext-auth-1".to_string()),
expected_credential: Some(ProviderCatalogKeyOAuthCredentialFence {
encrypted_api_key: Some("ciphertext-api-1".to_string()),
auth_type: "api_key".to_string(),
provider_id: "provider-1".to_string(),
provider_type: "custom".to_string(),
}),
encrypted_auth_config: "ciphertext-auth-2".to_string(),
encrypted_api_key_update: Some("ciphertext-api-2".to_string()),
expires_at_unix_secs_update: None,
oauth_invalid_at_unix_secs: None,
oauth_invalid_reason: None,
upstream_metadata_patch: None,
status_snapshot_patch: json!({}),
reset_error_count: false,
updated_at_unix_secs: Some(123),
};
let api_key_repository = repository();
let mut key = api_key_repository
.list_keys_by_ids(&["key-1".to_string()])
.await
.expect("key should load")
.pop()
.expect("key should exist");
key.encrypted_api_key = Some("ciphertext-admin".to_string());
api_key_repository
.update_key(&key)
.await
.expect("api key replacement should persist");
assert!(!api_key_repository
.compare_and_update_key_oauth_runtime_state(&update())
.await
.expect("API key mismatch should be a CAS miss"));
let auth_type_repository = repository();
let mut key = auth_type_repository
.list_keys_by_ids(&["key-1".to_string()])
.await
.expect("key should load")
.pop()
.expect("key should exist");
key.auth_type = "oauth".to_string();
auth_type_repository
.update_key(&key)
.await
.expect("auth type replacement should persist");
assert!(!auth_type_repository
.compare_and_update_key_oauth_runtime_state(&update())
.await
.expect("auth type mismatch should be a CAS miss"));
let provider_repository = repository();
let mut provider = provider_repository
.list_providers_by_ids(&["provider-1".to_string()])
.await
.expect("provider should load")
.pop()
.expect("provider should exist");
provider.provider_type = "codex".to_string();
provider_repository
.update_provider(&provider)
.await
.expect("provider type replacement should persist");
assert!(!provider_repository
.compare_and_update_key_oauth_runtime_state(&update())
.await
.expect("provider type mismatch should be a CAS miss"));
}
#[tokio::test]
async fn materializes_codex_window_usage_stats_delta_in_memory() {
let mut key = sample_key("key-1", "provider-1");
@@ -4,8 +4,9 @@ mod memory;
pub(crate) use aether_data_contracts::repository::provider_catalog::{
ProviderCatalogKeyAdaptiveState, ProviderCatalogKeyAdaptiveStateUpdate,
ProviderCatalogKeyHealthStateUpdate, ProviderCatalogKeyListOrder, ProviderCatalogKeyListQuery,
ProviderCatalogKeyOAuthRuntimeStateCasUpdate, ProviderCatalogKeyRuntimeMetadataUpdate,
ProviderCatalogKeyStatusSnapshotUpdate, ProviderCatalogReadRepository, ProviderCatalogSnapshot,
ProviderCatalogKeyOAuthCredentialFence, ProviderCatalogKeyOAuthRuntimeStateCasUpdate,
ProviderCatalogKeyRuntimeMetadataUpdate, ProviderCatalogKeyStatusSnapshotUpdate,
ProviderCatalogReadRepository, ProviderCatalogSnapshot,
ProviderCatalogUpstreamMetadataNamespaceUpdate, ProviderCatalogWriteRepository,
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
StoredProviderCatalogKeyMaintenanceSummary, StoredProviderCatalogKeyPage,