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

@@ -524,7 +524,7 @@ async fn gateway_handles_admin_api_keys_update_locally_with_trusted_admin_princi
"rate_limit": null,
"concurrent_limit": 12,
"allowed_providers": ["gemini"],
"allowed_api_formats": ["gemini:chat"],
"allowed_api_formats": ["gemini:generate_content"],
"allowed_models": ["gemini-2.5-pro"],
"expires_at": "2030-03-04",
"auto_delete_on_expiry": true,
@@ -541,7 +541,10 @@ async fn gateway_handles_admin_api_keys_update_locally_with_trusted_admin_princi
assert_eq!(payload["rate_limit"], serde_json::Value::Null);
assert_eq!(payload["concurrent_limit"], json!(12));
assert_eq!(payload["allowed_providers"], json!(["gemini"]));
assert_eq!(payload["allowed_api_formats"], json!(["gemini:chat"]));
assert_eq!(
payload["allowed_api_formats"],
json!(["gemini:generate_content"])
);
assert_eq!(payload["allowed_models"], json!(["gemini-2.5-pro"]));
assert_eq!(payload["auto_delete_on_expiry"], json!(true));
assert_eq!(payload["wallet"]["limit_mode"], json!("unlimited"));

View File

@@ -469,7 +469,7 @@ async fn gateway_exports_admin_provider_key_locally_with_trusted_admin_principal
let mut key = sample_key(
"key-kiro-a",
"provider-kiro",
"claude:cli",
"claude:messages",
"oauth-access-token",
);
key.auth_type = "oauth".to_string();
@@ -1569,7 +1569,12 @@ async fn gateway_handles_admin_keys_grouped_by_format_locally_with_trusted_admin
key_a.health_by_format = Some(json!({"openai:chat": {"health_score": 0.8}}));
key_a.circuit_breaker_by_format = Some(json!({"openai:chat": {"open": false}}));
let mut key_b = sample_key("key-claude-a", "provider-claude", "claude:chat", "sk-ant-a");
let mut key_b = sample_key(
"key-claude-a",
"provider-claude",
"claude:messages",
"sk-ant-a",
);
key_b.internal_priority = 20;
key_b.request_count = Some(2);
key_b.success_count = Some(1);
@@ -1592,7 +1597,7 @@ async fn gateway_handles_admin_keys_grouped_by_format_locally_with_trusted_admin
sample_endpoint(
"endpoint-claude-chat",
"provider-claude",
"claude:chat",
"claude:messages",
"https://api.claude.example",
),
],
@@ -1633,7 +1638,7 @@ async fn gateway_handles_admin_keys_grouped_by_format_locally_with_trusted_admin
"https://api.openai.example"
);
assert_eq!(payload["openai:chat"][0]["capabilities"], json!(["1h缓存"]));
assert_eq!(payload["claude:chat"][0]["provider_active"], false);
assert_eq!(payload["claude:messages"][0]["provider_active"], false);
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();

View File

@@ -472,7 +472,7 @@ async fn gateway_refreshes_admin_provider_quota_locally_for_kiro_with_trusted_ad
)
.expect("key should build")
.with_transport_fields(
Some(json!(["claude:cli"])),
Some(json!(["claude:messages"])),
encrypted_api_key,
Some(encrypted_auth_config),
None,
@@ -495,7 +495,7 @@ async fn gateway_refreshes_admin_provider_quota_locally_for_kiro_with_trusted_ad
vec![sample_endpoint(
"endpoint-kiro-cli",
"provider-kiro",
"claude:cli",
"claude:messages",
"https://q.us-west-2.amazonaws.com",
)],
vec![key],
@@ -830,7 +830,7 @@ async fn gateway_refreshes_admin_provider_quota_locally_for_antigravity_with_tru
)
.expect("key should build")
.with_transport_fields(
Some(json!(["gemini:chat"])),
Some(json!(["gemini:generate_content"])),
encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, "ya29.ant-token")
.expect("api key ciphertext should build"),
Some(encrypted_auth_config),
@@ -854,7 +854,7 @@ async fn gateway_refreshes_admin_provider_quota_locally_for_antigravity_with_tru
vec![sample_endpoint(
"endpoint-antigravity-chat",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"https://daily-cloudcode-pa.googleapis.com",
)],
vec![key],

View File

@@ -53,7 +53,7 @@ fn sample_admin_gemini_endpoint() -> StoredProviderCatalogEndpoint {
StoredProviderCatalogEndpoint::new(
"endpoint-gemini-admin-1".to_string(),
"provider-gemini-admin-1".to_string(),
"gemini:chat".to_string(),
"gemini:generate_content".to_string(),
Some("gemini".to_string()),
Some("chat".to_string()),
true,
@@ -83,12 +83,12 @@ fn sample_admin_gemini_key(id: &str, name: &str, secret: &str) -> StoredProvider
)
.expect("key should build")
.with_transport_fields(
Some(json!(["gemini:chat"])),
Some(json!(["gemini:generate_content"])),
encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, secret)
.expect("api key should encrypt"),
None,
None,
Some(json!({"gemini:chat": 1})),
Some(json!({"gemini:generate_content": 1})),
None,
None,
None,

View File

@@ -885,7 +885,7 @@ async fn gateway_handles_admin_pool_list_keys_with_quota_compatibility_fields()
let mut key = sample_key(
"key-antigravity-a",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"sk-antigravity",
);
key.name = "quota-key".to_string();
@@ -1008,7 +1008,7 @@ async fn gateway_includes_pool_quota_and_compat_fields_in_list_keys_response() {
let mut key = sample_key(
"key-antigravity-oauth",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"oauth-placeholder",
);
key.name = "quota key".to_string();
@@ -1133,7 +1133,7 @@ async fn gateway_prefers_status_snapshot_antigravity_quota_over_stale_metadata()
let mut key = sample_key(
"key-antigravity-snapshot-fresh",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"oauth-placeholder",
);
key.name = "antigravity snapshot fresh".to_string();
@@ -1248,7 +1248,7 @@ async fn gateway_renders_gemini_cli_account_quota_from_status_snapshot() {
let mut key = sample_key(
"key-gemini-cli-snapshot",
"provider-gemini-cli",
"gemini:chat",
"gemini:generate_content",
"oauth-placeholder",
);
key.name = "gemini cli snapshot".to_string();
@@ -1700,7 +1700,7 @@ async fn gateway_marks_exhausted_kiro_pool_key_as_blocked_when_flag_enabled() {
let mut key = sample_key(
"key-kiro-exhausted",
"provider-kiro",
"claude:cli",
"claude:messages",
"oauth-placeholder",
);
key.name = "kiro exhausted".to_string();
@@ -1773,7 +1773,7 @@ async fn gateway_prefers_status_snapshot_kiro_quota_over_stale_metadata() {
let mut key = sample_key(
"key-kiro-snapshot-fresh",
"provider-kiro",
"claude:cli",
"claude:messages",
"oauth-placeholder",
);
key.name = "kiro snapshot fresh".to_string();

View File

@@ -980,7 +980,7 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally() {
assert_eq!(plan.provider_id, "provider-kiro");
assert_eq!(plan.endpoint_id, "endpoint-kiro-cli");
assert_eq!(plan.key_id, "key-kiro-primary");
assert_eq!(plan.provider_api_format, "claude:cli");
assert_eq!(plan.provider_api_format, "claude:messages");
assert_eq!(plan.model_name.as_deref(), Some("claude-sonnet-4-upstream"));
Json(json!({
"request_id": plan.request_id,
@@ -1011,7 +1011,7 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally() {
let mut key = sample_key(
"key-kiro-primary",
"provider-kiro",
"claude:cli",
"claude:messages",
"__placeholder__",
);
key.auth_type = "oauth".to_string();
@@ -1038,9 +1038,9 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally() {
vec![StoredProviderCatalogEndpoint::new(
"endpoint-kiro-cli".to_string(),
"provider-kiro".to_string(),
"claude:cli".to_string(),
"claude:messages".to_string(),
Some("claude".to_string()),
Some("cli".to_string()),
Some("messages".to_string()),
true,
)
.expect("endpoint should build")
@@ -1076,7 +1076,7 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally() {
.json(&json!({
"provider_id": "provider-kiro",
"model_name": "claude-sonnet-4-upstream",
"api_format": "claude:cli"
"api_format": "claude:messages"
}))
.send()
.await
@@ -1148,7 +1148,7 @@ async fn gateway_handles_admin_provider_query_test_model_failover_for_kiro_local
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let build_key = |id: &str| {
let mut key = sample_key(id, "provider-kiro", "claude:cli", "__placeholder__");
let mut key = sample_key(id, "provider-kiro", "claude:messages", "__placeholder__");
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
@@ -1175,9 +1175,9 @@ async fn gateway_handles_admin_provider_query_test_model_failover_for_kiro_local
vec![StoredProviderCatalogEndpoint::new(
"endpoint-kiro-cli".to_string(),
"provider-kiro".to_string(),
"claude:cli".to_string(),
"claude:messages".to_string(),
Some("claude".to_string()),
Some("cli".to_string()),
Some("messages".to_string()),
true,
)
.expect("endpoint should build")
@@ -1217,7 +1217,7 @@ async fn gateway_handles_admin_provider_query_test_model_failover_for_kiro_local
"mode": "direct",
"model_name": "claude-sonnet-4-upstream",
"failover_models": ["claude-sonnet-4-upstream"],
"api_format": "claude:cli",
"api_format": "claude:messages",
"request_id": "provider-test-kiro"
}))
.send()
@@ -1295,7 +1295,7 @@ async fn gateway_retries_kiro_failover_after_http_error_without_message() {
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let build_key = |id: &str| {
let mut key = sample_key(id, "provider-kiro", "claude:cli", "__placeholder__");
let mut key = sample_key(id, "provider-kiro", "claude:messages", "__placeholder__");
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
@@ -1322,9 +1322,9 @@ async fn gateway_retries_kiro_failover_after_http_error_without_message() {
vec![StoredProviderCatalogEndpoint::new(
"endpoint-kiro-cli".to_string(),
"provider-kiro".to_string(),
"claude:cli".to_string(),
"claude:messages".to_string(),
Some("claude".to_string()),
Some("cli".to_string()),
Some("messages".to_string()),
true,
)
.expect("endpoint should build")
@@ -1364,7 +1364,7 @@ async fn gateway_retries_kiro_failover_after_http_error_without_message() {
"mode": "direct",
"model_name": "claude-sonnet-4-upstream",
"failover_models": ["claude-sonnet-4-upstream"],
"api_format": "claude:cli",
"api_format": "claude:messages",
"request_id": "provider-test-kiro-empty-error"
}))
.send()
@@ -1677,13 +1677,13 @@ async fn gateway_returns_stub_for_transport_unsupported_non_kiro_provider() {
vec![sample_endpoint(
"endpoint-antigravity-gemini",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"https://cloudcode-pa.googleapis.com",
)],
vec![sample_key(
"key-antigravity-gemini",
"provider-antigravity",
"gemini:chat",
"gemini:generate_content",
"sk-test-antigravity",
)],
));
@@ -1707,7 +1707,7 @@ async fn gateway_returns_stub_for_transport_unsupported_non_kiro_provider() {
.json(&json!({
"provider_id": "provider-antigravity",
"model": "gemini-2.5-pro",
"api_format": "gemini:chat"
"api_format": "gemini:generate_content"
}))
.send()
.await
@@ -1969,7 +1969,7 @@ async fn gateway_prefers_supported_non_kiro_endpoint_with_compatible_key_when_ap
sample_endpoint(
"endpoint-gemini-chat",
"provider-openai",
"gemini:chat",
"gemini:generate_content",
"https://api.gemini.example",
),
sample_endpoint(
@@ -2315,7 +2315,7 @@ async fn gateway_handles_claude_cli_test_model_locally() {
assert_eq!(plan.provider_id, "provider-claude");
assert_eq!(plan.endpoint_id, "endpoint-claude-cli");
assert_eq!(plan.key_id, "key-claude-cli");
assert_eq!(plan.provider_api_format, "claude:cli");
assert_eq!(plan.provider_api_format, "claude:messages");
assert_eq!(plan.url, "https://api.anthropic.example/v1/messages");
assert_eq!(plan.model_name.as_deref(), Some("claude-sonnet-4-5"));
Json(json!({
@@ -2351,13 +2351,13 @@ async fn gateway_handles_claude_cli_test_model_locally() {
vec![sample_endpoint(
"endpoint-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"https://api.anthropic.example",
)],
vec![sample_key(
"key-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"sk-test-claude-cli",
)],
));
@@ -2380,7 +2380,7 @@ async fn gateway_handles_claude_cli_test_model_locally() {
.json(&json!({
"provider_id": "provider-claude",
"model": "claude-sonnet-4-5",
"api_format": "claude:cli"
"api_format": "claude:messages"
}))
.send()
.await
@@ -2404,7 +2404,7 @@ async fn gateway_uses_compatible_claude_cli_endpoint_when_api_format_is_omitted(
"/v1/execute/sync",
any(move |Json(plan): Json<ExecutionPlan>| async move {
assert_eq!(plan.endpoint_id, "endpoint-claude-cli");
assert_eq!(plan.provider_api_format, "claude:cli");
assert_eq!(plan.provider_api_format, "claude:messages");
assert_eq!(plan.key_id, "key-claude-cli");
Json(json!({
"request_id": plan.request_id,
@@ -2439,13 +2439,13 @@ async fn gateway_uses_compatible_claude_cli_endpoint_when_api_format_is_omitted(
vec![sample_endpoint(
"endpoint-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"https://api.anthropic.example",
)],
vec![sample_key(
"key-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"sk-test-claude-cli",
)],
));
@@ -2493,7 +2493,7 @@ async fn gateway_handles_claude_cli_test_model_failover_locally() {
assert_eq!(plan.provider_id, "provider-claude");
assert_eq!(plan.endpoint_id, "endpoint-claude-cli");
assert_eq!(plan.key_id, "key-claude-cli");
assert_eq!(plan.provider_api_format, "claude:cli");
assert_eq!(plan.provider_api_format, "claude:messages");
assert_eq!(plan.model_name.as_deref(), Some("claude-sonnet-4-5"));
Json(json!({
"request_id": plan.request_id,
@@ -2528,13 +2528,13 @@ async fn gateway_handles_claude_cli_test_model_failover_locally() {
vec![sample_endpoint(
"endpoint-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"https://api.anthropic.example",
)],
vec![sample_key(
"key-claude-cli",
"provider-claude",
"claude:cli",
"claude:messages",
"sk-test-claude-cli",
)],
));
@@ -2559,7 +2559,7 @@ async fn gateway_handles_claude_cli_test_model_failover_locally() {
.json(&json!({
"provider_id": "provider-claude",
"failover_models": ["claude-sonnet-4-5"],
"api_format": "claude:cli"
"api_format": "claude:messages"
}))
.send()
.await
@@ -2586,7 +2586,7 @@ async fn gateway_handles_gemini_cli_test_model_locally() {
assert_eq!(plan.provider_id, "provider-gemini");
assert_eq!(plan.endpoint_id, "endpoint-gemini-cli");
assert_eq!(plan.key_id, "key-gemini-cli");
assert_eq!(plan.provider_api_format, "gemini:cli");
assert_eq!(plan.provider_api_format, "gemini:generate_content");
assert_eq!(
plan.url,
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent"
@@ -2625,13 +2625,13 @@ async fn gateway_handles_gemini_cli_test_model_locally() {
vec![sample_endpoint(
"endpoint-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
"key-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"sk-test-gemini-cli",
)],
));
@@ -2654,7 +2654,7 @@ async fn gateway_handles_gemini_cli_test_model_locally() {
.json(&json!({
"provider_id": "provider-gemini",
"model": "gemini-2.5-pro",
"api_format": "gemini:cli"
"api_format": "gemini:generate_content"
}))
.send()
.await
@@ -2680,7 +2680,7 @@ async fn gateway_handles_gemini_cli_test_model_with_oauth_header_fallback() {
assert_eq!(plan.provider_id, "provider-gemini");
assert_eq!(plan.endpoint_id, "endpoint-gemini-cli");
assert_eq!(plan.key_id, "key-gemini-cli");
assert_eq!(plan.provider_api_format, "gemini:cli");
assert_eq!(plan.provider_api_format, "gemini:generate_content");
assert_eq!(
plan.url,
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent"
@@ -2721,7 +2721,7 @@ async fn gateway_handles_gemini_cli_test_model_with_oauth_header_fallback() {
let mut key = sample_key(
"key-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"cached-gemini-cli-token",
);
key.auth_type = "oauth".to_string();
@@ -2737,7 +2737,7 @@ async fn gateway_handles_gemini_cli_test_model_with_oauth_header_fallback() {
vec![sample_endpoint(
"endpoint-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![key],
@@ -2761,7 +2761,7 @@ async fn gateway_handles_gemini_cli_test_model_with_oauth_header_fallback() {
.json(&json!({
"provider_id": "provider-gemini",
"model": "gemini-2.5-pro",
"api_format": "gemini:cli"
"api_format": "gemini:generate_content"
}))
.send()
.await
@@ -2785,7 +2785,7 @@ async fn gateway_uses_compatible_gemini_cli_endpoint_when_api_format_is_omitted(
"/v1/execute/sync",
any(move |Json(plan): Json<ExecutionPlan>| async move {
assert_eq!(plan.endpoint_id, "endpoint-gemini-cli");
assert_eq!(plan.provider_api_format, "gemini:cli");
assert_eq!(plan.provider_api_format, "gemini:generate_content");
assert_eq!(plan.key_id, "key-gemini-cli");
Json(json!({
"request_id": plan.request_id,
@@ -2820,13 +2820,13 @@ async fn gateway_uses_compatible_gemini_cli_endpoint_when_api_format_is_omitted(
vec![sample_endpoint(
"endpoint-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
"key-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"sk-test-gemini-cli",
)],
));
@@ -2874,7 +2874,7 @@ async fn gateway_handles_gemini_cli_test_model_failover_locally() {
assert_eq!(plan.provider_id, "provider-gemini");
assert_eq!(plan.endpoint_id, "endpoint-gemini-cli");
assert_eq!(plan.key_id, "key-gemini-cli");
assert_eq!(plan.provider_api_format, "gemini:cli");
assert_eq!(plan.provider_api_format, "gemini:generate_content");
assert_eq!(plan.model_name.as_deref(), Some("gemini-2.5-pro"));
Json(json!({
"request_id": plan.request_id,
@@ -2909,13 +2909,13 @@ async fn gateway_handles_gemini_cli_test_model_failover_locally() {
vec![sample_endpoint(
"endpoint-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
"key-gemini-cli",
"provider-gemini",
"gemini:cli",
"gemini:generate_content",
"sk-test-gemini-cli",
)],
));
@@ -2940,7 +2940,7 @@ async fn gateway_handles_gemini_cli_test_model_failover_locally() {
.json(&json!({
"provider_id": "provider-gemini",
"failover_models": ["gemini-2.5-pro"],
"api_format": "gemini:cli"
"api_format": "gemini:generate_content"
}))
.send()
.await

View File

@@ -585,7 +585,7 @@ async fn gateway_handles_admin_providers_summary_list_locally_with_trusted_admin
sample_endpoint(
"endpoint-anthropic-chat",
"provider-anthropic",
"claude:chat",
"claude:messages",
"https://api.anthropic.example",
),
],

View File

@@ -482,9 +482,9 @@ async fn gateway_handles_admin_usage_aggregation_stats_locally_with_trusted_admi
);
usage_3.provider_id = Some("provider-anthropic".to_string());
usage_3.total_tokens = usage_3.input_tokens;
usage_3.api_format = Some("claude:cli".to_string());
usage_3.api_format = Some("claude:messages".to_string());
usage_3.api_family = Some("claude".to_string());
usage_3.endpoint_api_format = Some("claude:cli".to_string());
usage_3.endpoint_api_format = Some("claude:messages".to_string());
usage_3.provider_api_family = Some("claude".to_string());
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
@@ -558,7 +558,7 @@ async fn gateway_handles_admin_usage_aggregation_stats_locally_with_trusted_admi
assert_eq!(api_format_items.len(), 2);
assert_eq!(api_format_items[0]["api_format"], "openai:chat");
assert_eq!(api_format_items[0]["output_tokens"], 40);
assert_eq!(api_format_items[1]["api_format"], "claude:cli");
assert_eq!(api_format_items[1]["api_format"], "claude:messages");
assert_eq!(api_format_items[1]["output_tokens"], 20);
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);

View File

@@ -462,7 +462,7 @@ async fn gateway_handles_admin_user_detail_routes_locally_with_trusted_admin_pri
"username": "alice_updated",
"role": "admin",
"allowed_providers": ["openai", "anthropic"],
"allowed_api_formats": ["openai:chat", "gemini:chat"],
"allowed_api_formats": ["openai:chat", "gemini:generate_content"],
"allowed_models": ["gpt-4.1", "gemini-2.5-pro"],
"rate_limit": 120,
"is_active": false,

View File

@@ -501,7 +501,7 @@ async fn gateway_locally_denies_disallowed_claude_api_format_without_hitting_con
assert_eq!(payload["error"]["type"], "http_error");
assert_eq!(
payload["error"]["message"],
"当前密钥不允许访问 claude:chat 格式"
"当前密钥不允许访问 claude:messages 格式"
);
assert_eq!(*auth_context_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -625,7 +625,7 @@ async fn gateway_locally_denies_disallowed_gemini_model_without_hitting_control_
let mut snapshot = sample_currently_usable_auth_snapshot("key-gemini-123", "user-gemini-123");
snapshot.api_key_allowed_providers = Some(vec!["gemini".to_string()]);
snapshot.user_allowed_providers = Some(vec!["gemini".to_string()]);
snapshot.api_key_allowed_api_formats = Some(vec!["gemini:chat".to_string()]);
snapshot.api_key_allowed_api_formats = Some(vec!["gemini:generate_content".to_string()]);
snapshot.api_key_allowed_models = Some(vec!["gemini-1.5-pro".to_string()]);
let repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
Some(hash_api_key("gemini-client-key-123")),