mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
支持 Gemini Interactions 与 Antigravity 配额精细化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
pub(crate) fn normalized_signature(api_format: &str) -> Option<&'static str> {
|
||||
match crate::ai_serving::normalize_api_format_alias(api_format).as_str() {
|
||||
"gemini:generate_content" => Some("gemini:generate_content"),
|
||||
"gemini:interactions" => Some("gemini:interactions"),
|
||||
"gemini:embedding" => Some("gemini:embedding"),
|
||||
"gemini:video" => Some("gemini:video"),
|
||||
"gemini:files" => Some("gemini:files"),
|
||||
@@ -11,6 +12,7 @@ pub(crate) fn normalized_signature(api_format: &str) -> Option<&'static str> {
|
||||
pub(crate) fn local_path(api_format: &str) -> Option<&'static str> {
|
||||
match crate::ai_serving::normalize_api_format_alias(api_format).as_str() {
|
||||
"gemini" | "gemini:generate_content" => Some("/v1beta/models/{model}:{action}"),
|
||||
"gemini:interactions" => Some("/v1/interactions"),
|
||||
"gemini:embedding" => Some("/v1beta/models/{model}:{action}"),
|
||||
"gemini:video" => Some("/v1beta/models/{model}:predictLongRunning"),
|
||||
"gemini:files" => Some("/v1beta/files"),
|
||||
|
||||
@@ -17,6 +17,8 @@ const AI_POST_ROUTE_PATTERNS: &[&str] = &[
|
||||
"/v1/responses/compact",
|
||||
"/v1/images/generations",
|
||||
"/v1/images/edits",
|
||||
"/v1/interactions",
|
||||
"/v1beta/interactions",
|
||||
"/v1internal:loadCodeAssist",
|
||||
"/v1internal:fetchAvailableModels",
|
||||
"/v1internal:fetchUserInfo",
|
||||
@@ -95,6 +97,12 @@ mod tests {
|
||||
fn supports_data_api_endpoint_signatures_and_public_paths() {
|
||||
for (api_format, family, kind, path) in [
|
||||
("openai:embedding", "openai", "embedding", "/v1/embeddings"),
|
||||
(
|
||||
"gemini:interactions",
|
||||
"gemini",
|
||||
"interactions",
|
||||
"/v1/interactions",
|
||||
),
|
||||
(
|
||||
"gemini:embedding",
|
||||
"gemini",
|
||||
|
||||
@@ -97,6 +97,17 @@ pub(super) fn classify_ai_public_route(
|
||||
"openai:video",
|
||||
true,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(normalized_path, "/v1/interactions" | "/v1beta/interactions")
|
||||
{
|
||||
Some(classified_with_request_auth_channel(
|
||||
"ai_public",
|
||||
"gemini",
|
||||
"interactions",
|
||||
"api_key",
|
||||
"gemini:interactions",
|
||||
true,
|
||||
))
|
||||
} else if method == http::Method::POST && is_gemini_models_route(normalized_path) {
|
||||
if normalized_path.ends_with(":predictLongRunning") {
|
||||
Some(classified(
|
||||
|
||||
@@ -274,6 +274,23 @@ fn classifies_gemini_batch_embed_contents_as_embedding_route() {
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_gemini_interactions_as_interactions_route() {
|
||||
let headers = headers(&[("x-goog-api-key", "gemini-key")]);
|
||||
let uri: Uri = "/v1/interactions".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("gemini"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("interactions"));
|
||||
assert_eq!(decision.request_auth_channel.as_deref(), Some("api_key"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("gemini:interactions")
|
||||
);
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_gemini_predict_long_running_as_video_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
@@ -179,6 +179,7 @@ pub(crate) fn resolve_core_sync_error_finalize_report_kind(
|
||||
"openai_responses_compact_sync" => "openai_responses_compact_sync_finalize",
|
||||
"claude_chat_sync" => "claude_chat_sync_finalize",
|
||||
"gemini_chat_sync" => "gemini_chat_sync_finalize",
|
||||
"gemini_interactions_sync" => "gemini_interactions_sync_finalize",
|
||||
"claude_cli_sync" => "claude_cli_sync_finalize",
|
||||
"gemini_cli_sync" => "gemini_cli_sync_finalize",
|
||||
_ => return None,
|
||||
@@ -324,6 +325,7 @@ pub(crate) fn resolve_core_stream_error_finalize_report_kind(
|
||||
"openai_chat_stream" => "openai_chat_sync_finalize",
|
||||
"claude_chat_stream" => "claude_chat_sync_finalize",
|
||||
"gemini_chat_stream" => "gemini_chat_sync_finalize",
|
||||
"gemini_interactions_stream" => "gemini_interactions_sync_finalize",
|
||||
"openai_responses_stream" => "openai_responses_sync_finalize",
|
||||
"openai_responses_compact_stream" => "openai_responses_compact_sync_finalize",
|
||||
"claude_cli_stream" => "claude_cli_sync_finalize",
|
||||
@@ -340,6 +342,7 @@ pub(crate) fn resolve_core_stream_direct_finalize_report_kind(plan_kind: &str) -
|
||||
"openai_image_stream" => "openai_image_sync_finalize",
|
||||
"claude_chat_stream" => "claude_chat_sync_finalize",
|
||||
"gemini_chat_stream" => "gemini_chat_sync_finalize",
|
||||
"gemini_interactions_stream" => "gemini_interactions_sync_finalize",
|
||||
"openai_responses_stream" => "openai_responses_sync_finalize",
|
||||
"openai_responses_compact_stream" => "openai_responses_compact_sync_finalize",
|
||||
"claude_cli_stream" => "claude_cli_sync_finalize",
|
||||
|
||||
@@ -104,6 +104,10 @@ fn resolve_core_error_background_report_kind_maps_all_core_finalize_kinds() {
|
||||
("openai_chat_sync_finalize", Some("openai_chat_sync_error")),
|
||||
("claude_chat_sync_finalize", Some("claude_chat_sync_error")),
|
||||
("gemini_chat_sync_finalize", Some("gemini_chat_sync_error")),
|
||||
(
|
||||
"gemini_interactions_sync_finalize",
|
||||
Some("gemini_interactions_sync_error"),
|
||||
),
|
||||
(
|
||||
"openai_responses_sync_finalize",
|
||||
Some("openai_responses_sync_error"),
|
||||
@@ -151,6 +155,10 @@ fn resolve_core_success_background_report_kind_maps_all_core_finalize_kinds() {
|
||||
"gemini_chat_sync_finalize",
|
||||
Some("gemini_chat_sync_success"),
|
||||
),
|
||||
(
|
||||
"gemini_interactions_sync_finalize",
|
||||
Some("gemini_interactions_sync_success"),
|
||||
),
|
||||
(
|
||||
"openai_responses_sync_finalize",
|
||||
Some("openai_responses_sync_success"),
|
||||
|
||||
@@ -82,14 +82,14 @@ pub(super) fn provider_query_standard_test_unsupported_reason(
|
||||
api_format,
|
||||
)
|
||||
}
|
||||
"gemini:generate_content" | "gemini:embedding"
|
||||
"gemini:generate_content" | "gemini:embedding" | "gemini:interactions"
|
||||
if crate::provider_transport::is_vertex_transport_context(transport) =>
|
||||
{
|
||||
aether_provider_transport::vertex::local_vertex_gemini_transport_unsupported_reason_with_network(
|
||||
transport,
|
||||
)
|
||||
}
|
||||
"gemini:generate_content" | "gemini:embedding" => {
|
||||
"gemini:generate_content" | "gemini:embedding" | "gemini:interactions" => {
|
||||
crate::provider_transport::policy::local_gemini_transport_unsupported_reason_with_network(
|
||||
transport,
|
||||
api_format,
|
||||
@@ -250,6 +250,7 @@ pub(super) fn provider_query_test_adapter_for_provider_api_format(
|
||||
| "openai:responses:compact"
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content"
|
||||
| "gemini:interactions"
|
||||
| "openai:embedding"
|
||||
| "gemini:embedding"
|
||||
| "jina:embedding"
|
||||
@@ -366,7 +367,7 @@ pub(super) fn provider_query_transport_supports_model_test_execution(
|
||||
transport, api_format,
|
||||
)
|
||||
}
|
||||
"gemini:generate_content" | "gemini:embedding" => {
|
||||
"gemini:generate_content" | "gemini:embedding" | "gemini:interactions" => {
|
||||
if crate::provider_transport::is_vertex_transport_context(transport) {
|
||||
aether_provider_transport::vertex::supports_local_vertex_gemini_transport_with_network(transport)
|
||||
} else {
|
||||
|
||||
@@ -120,6 +120,10 @@ fn provider_query_endpoint_route_payload(
|
||||
("Gemini API", "gemini_native", "generateContent", "")
|
||||
}
|
||||
"gemini:generate_content" => ("Gemini native", "gemini_native", "generateContent", ""),
|
||||
"gemini:interactions" if is_gemini_api => {
|
||||
("Gemini API", "gemini_native", "interactions", "")
|
||||
}
|
||||
"gemini:interactions" => ("Gemini native", "gemini_native", "interactions", ""),
|
||||
"openai:embedding" if is_vertex && is_openai_compat => (
|
||||
"Vertex AI OpenAI-compatible",
|
||||
"openai_compatible",
|
||||
|
||||
@@ -678,6 +678,10 @@ fn provider_query_test_adapter_routes_fixed_provider_endpoint_types() {
|
||||
provider_query_test_adapter_for_provider_api_format("custom", "gemini:embedding"),
|
||||
Some(ProviderQueryTestAdapter::Standard)
|
||||
);
|
||||
assert_eq!(
|
||||
provider_query_test_adapter_for_provider_api_format("custom", "gemini:interactions"),
|
||||
Some(ProviderQueryTestAdapter::Standard)
|
||||
);
|
||||
assert_eq!(
|
||||
provider_query_test_adapter_for_provider_api_format(
|
||||
"aliyun",
|
||||
|
||||
@@ -102,7 +102,7 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
let format_value = crate::ai_serving::normalize_api_format_alias(&format_value);
|
||||
if !matches!(
|
||||
format_value.as_str(),
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content"
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content" | "gemini:interactions"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
@@ -186,6 +186,10 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
"parts": [{"text": "Health check"}],
|
||||
}],
|
||||
}),
|
||||
"gemini:interactions" => json!({
|
||||
"model": model,
|
||||
"input": "Health check",
|
||||
}),
|
||||
_ => return None,
|
||||
};
|
||||
if !crate::provider_transport::apply_local_body_rules(
|
||||
@@ -197,7 +201,7 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
}
|
||||
|
||||
let oauth_auth = match format_value.as_str() {
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content" => {
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content" | "gemini:interactions" => {
|
||||
match state.resolve_local_oauth_request_auth(&transport).await {
|
||||
Ok(Some(crate::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
name,
|
||||
@@ -218,7 +222,7 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
crate::provider_transport::auth::resolve_local_standard_auth(&transport)
|
||||
.or(oauth_auth.clone())
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
"gemini:generate_content" | "gemini:interactions" => {
|
||||
crate::provider_transport::auth::resolve_local_gemini_auth(&transport)
|
||||
.or(oauth_auth.clone())
|
||||
}
|
||||
|
||||
@@ -1452,6 +1452,11 @@ async fn gateway_handles_admin_system_api_formats_locally_with_trusted_admin_pri
|
||||
assert!(formats.iter().any(|item| item["value"] == "jina:embedding"));
|
||||
assert!(formats.iter().any(|item| item["value"] == "jina:rerank"));
|
||||
assert!(formats.iter().any(|item| item["value"] == "gemini:video"));
|
||||
let gemini_interactions = formats
|
||||
.iter()
|
||||
.find(|item| item["value"] == "gemini:interactions")
|
||||
.expect("gemini interactions format should exist");
|
||||
assert_eq!(gemini_interactions["default_path"], "/v1/interactions");
|
||||
let aliyun_embedding = formats
|
||||
.iter()
|
||||
.find(|item| item["value"] == "aliyun:multimodal_embedding")
|
||||
|
||||
Reference in New Issue
Block a user