修复 Antigravity OAuth 配额复检缺 project

This commit is contained in:
MMEXA
2026-06-19 22:21:22 +08:00
parent 16584067d7
commit 6c4e730e60
12 changed files with 977 additions and 130 deletions
@@ -32,6 +32,8 @@ pub(super) struct AdminProviderOAuthBatchImportEntry {
pub email: Option<String>,
pub account_name: Option<String>,
pub project_id: Option<String>,
pub client_version: Option<String>,
pub session_id: Option<String>,
pub sso_rw_token: Option<String>,
pub cf_cookies: Option<String>,
pub cf_clearance: Option<String>,
@@ -191,6 +193,8 @@ fn extract_admin_provider_oauth_batch_import_entry(
email: None,
account_name: None,
project_id: None,
client_version: None,
session_id: None,
sso_rw_token: grok_cookie_value(raw_token, "sso-rw"),
cf_cookies: grok_cookie_profile(raw_token),
cf_clearance: grok_cookie_value(raw_token, "cf_clearance"),
@@ -331,6 +335,19 @@ fn extract_admin_provider_oauth_batch_import_entry(
.or_else(|| object.get("cloudaicompanionProject"))
.or_else(|| object.get("cloudAiCompanionProject")),
);
let client_version = coerce_admin_provider_oauth_import_str(
object
.get("client_version")
.or_else(|| object.get("clientVersion"))
.or_else(|| object.get("antigravityClientVersion")),
);
let session_id = coerce_admin_provider_oauth_import_str(
object
.get("session_id")
.or_else(|| object.get("sessionId"))
.or_else(|| object.get("vscode_session_id"))
.or_else(|| object.get("vscodeSessionId")),
);
let sso_rw_token = coerce_admin_provider_oauth_import_str(
object
.get("sso_rw_token")
@@ -379,6 +396,8 @@ fn extract_admin_provider_oauth_batch_import_entry(
email,
account_name,
project_id,
client_version,
session_id,
sso_rw_token,
cf_cookies,
cf_clearance,
@@ -470,6 +489,8 @@ fn parse_error_entry(error: String) -> AdminProviderOAuthBatchImportEntry {
email: None,
account_name: None,
project_id: None,
client_version: None,
session_id: None,
sso_rw_token: None,
cf_cookies: None,
cf_clearance: None,
@@ -502,6 +523,29 @@ pub(super) fn apply_admin_provider_oauth_batch_import_hints(
}
return;
}
if provider_type == "antigravity" {
if let Some(project_id) = entry.project_id.as_ref() {
auth_config
.entry("project_id".to_string())
.or_insert_with(|| json!(project_id));
}
if let Some(client_version) = entry.client_version.as_ref() {
auth_config
.entry("client_version".to_string())
.or_insert_with(|| json!(client_version));
}
if let Some(session_id) = entry.session_id.as_ref() {
auth_config
.entry("session_id".to_string())
.or_insert_with(|| json!(session_id));
}
if let Some(user_agent) = entry.user_agent.as_ref() {
auth_config
.entry("user_agent".to_string())
.or_insert_with(|| json!(user_agent));
}
return;
}
if !matches!(provider_type.as_str(), "codex" | "chatgpt_web" | "grok") {
return;
}
@@ -823,6 +867,23 @@ mod tests {
);
}
#[test]
fn applies_antigravity_project_and_user_agent_hints_to_auth_config() {
let entries = parse_admin_provider_oauth_batch_import_entries(
"antigravity",
r#"{"refreshToken":"rt-1","cloudaicompanionProject":{"id":"project-antigravity-2"},"userAgent":"antigravity"}"#,
);
let mut auth_config = serde_json::Map::new();
apply_admin_provider_oauth_batch_import_hints("antigravity", &entries[0], &mut auth_config);
assert_eq!(
auth_config.get("project_id"),
Some(&json!("project-antigravity-2"))
);
assert_eq!(auth_config.get("user_agent"), Some(&json!("antigravity")));
}
#[test]
fn parses_windsurf_json_credentials_for_native_import() {
let entries = parse_admin_provider_oauth_batch_import_entries(
@@ -106,6 +106,34 @@ fn import_payload_string_any(
.map(ToOwned::to_owned)
}
fn import_payload_project_id_any(
payload: &serde_json::Map<String, serde_json::Value>,
keys: &[&str],
) -> Option<String> {
keys.iter().find_map(|key| {
let value = payload.get(*key)?;
if let Some(string) = value
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
{
return Some(string.to_string());
}
value
.as_object()
.and_then(|object| {
object
.get("id")
.or_else(|| object.get("project_id"))
.or_else(|| object.get("projectId"))
})
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
})
}
fn import_payload_u64_any(
payload: &serde_json::Map<String, serde_json::Value>,
keys: &[&str],
@@ -129,6 +157,49 @@ fn apply_single_import_hints(
auth_config: &mut serde_json::Map<String, serde_json::Value>,
) {
let provider_type = provider_type.trim().to_ascii_lowercase();
if provider_type == "antigravity" {
if let Some(project_id) = import_payload_project_id_any(
payload,
&[
"project_id",
"projectId",
"cloudaicompanionProject",
"cloudAiCompanionProject",
],
) {
auth_config
.entry("project_id".to_string())
.or_insert_with(|| json!(project_id));
}
for (target, keys) in [
(
"client_version",
&[
"client_version",
"clientVersion",
"antigravityClientVersion",
][..],
),
(
"session_id",
&[
"session_id",
"sessionId",
"vscode_session_id",
"vscodeSessionId",
][..],
),
("user_agent", &["user_agent", "userAgent"][..]),
] {
let Some(value) = import_payload_string_any(payload, keys) else {
continue;
};
auth_config
.entry(target.to_string())
.or_insert_with(|| json!(value));
}
return;
}
if !matches!(provider_type.as_str(), "codex" | "chatgpt_web" | "grok") {
return;
}
@@ -640,7 +711,8 @@ pub(super) async fn handle_admin_provider_oauth_import_refresh_token(
#[cfg(test)]
mod tests {
use super::{
import_payload_string_any, import_payload_u64_any, sanitize_windsurf_import_error,
apply_single_import_hints, import_payload_string_any, import_payload_u64_any,
sanitize_windsurf_import_error,
};
use aether_oauth::core::OAuthError;
use serde_json::json;
@@ -675,6 +747,35 @@ mod tests {
);
}
#[test]
fn single_import_applies_antigravity_identity_hints() {
let payload = json!({
"cloudaicompanionProject": {
"id": "project-antigravity-1"
},
"clientVersion": "1.99.0",
"sessionId": "session-antigravity-1",
"userAgent": "antigravity"
})
.as_object()
.cloned()
.expect("payload should be an object");
let mut auth_config = serde_json::Map::new();
apply_single_import_hints("antigravity", &payload, &mut auth_config);
assert_eq!(
auth_config.get("project_id"),
Some(&json!("project-antigravity-1"))
);
assert_eq!(auth_config.get("client_version"), Some(&json!("1.99.0")));
assert_eq!(
auth_config.get("session_id"),
Some(&json!("session-antigravity-1"))
);
assert_eq!(auth_config.get("user_agent"), Some(&json!("antigravity")));
}
#[test]
fn windsurf_import_error_redacts_http_body() {
let error = OAuthError::HttpStatus {
@@ -68,7 +68,7 @@ pub(crate) async fn refresh_antigravity_provider_quota_locally(
let mut auto_removed_count = 0usize;
for key in keys {
let transport = match state
let mut transport = match state
.read_provider_transport_snapshot(&provider.id, &endpoint.id, &key.id)
.await?
{
@@ -104,15 +104,25 @@ pub(crate) async fn refresh_antigravity_provider_quota_locally(
}
};
let Some((project_id, identity_headers)) =
state.resolve_local_antigravity_identity_headers(&transport)
else {
let identity = match state.resolve_local_antigravity_identity_headers(&transport) {
Some(identity) => Some(identity),
None => state
.app()
.hydrate_antigravity_project_metadata_for_transport(&transport)
.await
.and_then(|hydrated| {
let identity = state.resolve_local_antigravity_identity_headers(&hydrated);
transport = hydrated;
identity
}),
};
let Some((project_id, identity_headers)) = identity else {
failed_count += 1;
results.push(json!({
"key_id": key.id,
"key_name": key.name,
"status": "error",
"message": "缺少 OAuth 认证信息,请先授权/刷新 Token",
"message": "缺少 Antigravity project_id,loadCodeAssist 未返回可用项目信息",
}));
continue;
};