mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 19:29:50 +08:00
支持 Gemini Interactions 与 Antigravity 配额精细化
This commit is contained in:
@@ -150,6 +150,9 @@ fn build_transport_request_url_inner(
|
||||
params.request_query,
|
||||
gemini_embedding_batch,
|
||||
),
|
||||
"gemini:interactions" => {
|
||||
build_gemini_interactions_url(&transport.endpoint.base_url, params.request_query)
|
||||
}
|
||||
"doubao:embedding" => build_passthrough_path_url(
|
||||
&transport.endpoint.base_url,
|
||||
"/embeddings",
|
||||
@@ -345,7 +348,9 @@ fn build_transport_hook_url(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if is_antigravity_provider_transport(transport) {
|
||||
if is_antigravity_provider_transport(transport)
|
||||
&& normalized_provider_api_format == "gemini:generate_content"
|
||||
{
|
||||
let query = params.request_query.map(|raw| {
|
||||
form_urlencoded::parse(raw.as_bytes())
|
||||
.into_owned()
|
||||
@@ -398,7 +403,8 @@ fn build_path_params(
|
||||
}
|
||||
let provider_api_format =
|
||||
aether_ai_formats::normalize_api_format_alias(params.provider_api_format);
|
||||
if provider_api_format.starts_with("gemini:") {
|
||||
if provider_api_format == "gemini:generate_content" || provider_api_format == "gemini:embedding"
|
||||
{
|
||||
path_params.insert(
|
||||
"action",
|
||||
if provider_api_format == "gemini:embedding" {
|
||||
@@ -445,6 +451,10 @@ fn build_provider_rerank_v1_url(upstream_base_url: &str, query: Option<&str>) ->
|
||||
build_provider_api_root_url(upstream_base_url, "/rerank", query)
|
||||
}
|
||||
|
||||
fn build_gemini_interactions_url(upstream_base_url: &str, query: Option<&str>) -> Option<String> {
|
||||
build_passthrough_path_url(upstream_base_url, "/v1/interactions", query, &["key"])
|
||||
}
|
||||
|
||||
fn build_provider_api_root_url(
|
||||
upstream_base_url: &str,
|
||||
path: &str,
|
||||
@@ -1116,6 +1126,76 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_interactions_request_url_uses_stable_v1_endpoint() {
|
||||
let gemini = sample_transport(
|
||||
"gemini",
|
||||
"gemini:interactions",
|
||||
"https://generativelanguage.googleapis.com",
|
||||
None,
|
||||
);
|
||||
let versioned = sample_transport(
|
||||
"gemini",
|
||||
"gemini:interactions",
|
||||
"https://generativelanguage.googleapis.com/v1",
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
build_transport_request_url(
|
||||
&gemini,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "gemini:interactions",
|
||||
mapped_model: Some("gemini-3.5-flash"),
|
||||
upstream_is_stream: false,
|
||||
request_query: Some("key=client-key&trace=1"),
|
||||
kiro_api_region: None,
|
||||
},
|
||||
)
|
||||
.as_deref(),
|
||||
Some("https://generativelanguage.googleapis.com/v1/interactions?trace=1")
|
||||
);
|
||||
assert_eq!(
|
||||
build_transport_request_url(
|
||||
&versioned,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "gemini:interactions",
|
||||
mapped_model: Some("gemini-3.5-flash"),
|
||||
upstream_is_stream: true,
|
||||
request_query: Some("key=client-key&trace=2"),
|
||||
kiro_api_region: None,
|
||||
},
|
||||
)
|
||||
.as_deref(),
|
||||
Some("https://generativelanguage.googleapis.com/v1/interactions?trace=2")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn antigravity_hook_is_limited_to_generate_content() {
|
||||
let transport = sample_transport(
|
||||
"antigravity",
|
||||
"gemini:interactions",
|
||||
"https://daily-cloudcode-pa.googleapis.com",
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
build_transport_request_url(
|
||||
&transport,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "gemini:interactions",
|
||||
mapped_model: Some("gemini-3.5-flash"),
|
||||
upstream_is_stream: false,
|
||||
request_query: Some("key=client-key"),
|
||||
kiro_api_region: None,
|
||||
},
|
||||
)
|
||||
.as_deref(),
|
||||
Some("https://daily-cloudcode-pa.googleapis.com/v1/interactions")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedding_request_url_preserves_google_openai_compat_roots() {
|
||||
let developer_api_openai = sample_transport(
|
||||
|
||||
@@ -265,13 +265,39 @@ fn build_same_format_provider_request_body_inner(
|
||||
}
|
||||
}
|
||||
SameFormatProviderFamily::Gemini => {
|
||||
if provider_request_body.remove("model").is_some() {
|
||||
record_compatibility_edit(
|
||||
&mut compatibility_edits,
|
||||
"model",
|
||||
SameFormatProviderCompatibilityEditAction::RuntimeRewrite,
|
||||
"removed top-level model because Gemini model is carried by the upstream URL",
|
||||
if aether_ai_formats::api_format_alias_matches(
|
||||
input.provider_api_format,
|
||||
"gemini:interactions",
|
||||
) {
|
||||
let rewrite_field = if provider_request_body.contains_key("model") {
|
||||
"model"
|
||||
} else if provider_request_body.contains_key("agent") {
|
||||
"agent"
|
||||
} else {
|
||||
"model"
|
||||
};
|
||||
let previous_value = provider_request_body.get(rewrite_field).cloned();
|
||||
provider_request_body.insert(
|
||||
rewrite_field.to_string(),
|
||||
Value::String(input.mapped_model.to_string()),
|
||||
);
|
||||
if previous_value != Some(Value::String(input.mapped_model.to_string())) {
|
||||
record_compatibility_edit(
|
||||
&mut compatibility_edits,
|
||||
rewrite_field,
|
||||
SameFormatProviderCompatibilityEditAction::RuntimeRewrite,
|
||||
"rewrote Gemini Interactions routing field to mapped upstream target",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if provider_request_body.remove("model").is_some() {
|
||||
record_compatibility_edit(
|
||||
&mut compatibility_edits,
|
||||
"model",
|
||||
SameFormatProviderCompatibilityEditAction::RuntimeRewrite,
|
||||
"removed top-level model because Gemini model is carried by the upstream URL",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,6 +603,7 @@ pub fn same_format_provider_transport_unsupported_reason_for_trace(
|
||||
"openai:responses:compact" => "openai:responses:compact",
|
||||
"claude:messages" => "claude:messages",
|
||||
"gemini:generate_content" => "gemini:generate_content",
|
||||
"gemini:interactions" => "gemini:interactions",
|
||||
_ => return Some("transport_api_format_unsupported"),
|
||||
};
|
||||
let behavior = classify_same_format_provider_request_behavior(
|
||||
@@ -1210,6 +1237,62 @@ mod tests {
|
||||
assert!(body.get("stream").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_format_gemini_interactions_body_keeps_body_routing_target() {
|
||||
let body = build_same_format_provider_request_body(SameFormatProviderRequestBodyInput {
|
||||
body_json: &json!({
|
||||
"model": "client-model",
|
||||
"input": [{"role": "user", "content": "hello"}],
|
||||
"stream": true
|
||||
}),
|
||||
mapped_model: "gemini-3.5-flash",
|
||||
client_api_format: "gemini:interactions",
|
||||
provider_api_format: "gemini:interactions",
|
||||
source_model: Some("client-model"),
|
||||
family: SameFormatProviderFamily::Gemini,
|
||||
body_rules: None,
|
||||
request_headers: None,
|
||||
upstream_is_stream: true,
|
||||
force_body_stream_field: false,
|
||||
kiro_auth_config: None,
|
||||
is_claude_code: false,
|
||||
enable_model_directives: false,
|
||||
})
|
||||
.expect("body should build");
|
||||
|
||||
assert_eq!(body.get("model"), Some(&json!("gemini-3.5-flash")));
|
||||
assert_eq!(body.get("stream"), Some(&json!(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_format_gemini_interactions_body_rewrites_agent_target() {
|
||||
let body = build_same_format_provider_request_body(SameFormatProviderRequestBodyInput {
|
||||
body_json: &json!({
|
||||
"agent": "antigravity-preview-05-2026",
|
||||
"input": "hello"
|
||||
}),
|
||||
mapped_model: "antigravity-preview-05-2026",
|
||||
client_api_format: "gemini:interactions",
|
||||
provider_api_format: "gemini:interactions",
|
||||
source_model: None,
|
||||
family: SameFormatProviderFamily::Gemini,
|
||||
body_rules: None,
|
||||
request_headers: None,
|
||||
upstream_is_stream: false,
|
||||
force_body_stream_field: false,
|
||||
kiro_auth_config: None,
|
||||
is_claude_code: false,
|
||||
enable_model_directives: false,
|
||||
})
|
||||
.expect("body should build");
|
||||
|
||||
assert_eq!(
|
||||
body.get("agent"),
|
||||
Some(&json!("antigravity-preview-05-2026"))
|
||||
);
|
||||
assert!(body.get("model").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_format_gemini_body_strips_function_response_id_for_upstream() {
|
||||
let body = build_same_format_provider_request_body(SameFormatProviderRequestBodyInput {
|
||||
|
||||
Reference in New Issue
Block a user