fix: Antigravity 刷新 token 时检测 project_id 缺失

- 刷新 token 后调用 LoadCodeAssist 获取 project_id
- 如果获取失败,保留原有 project_id,标记账户为 error
- token 仍会正常更新,不影响凭证刷新
- 错误信息:账户缺少project id,可能无法使用Antigravity
This commit is contained in:
song
2026-01-16 12:13:54 +08:00
parent a61042bca0
commit 95fe1e818f
5 changed files with 55 additions and 18 deletions

View File

@@ -450,6 +450,28 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
newCredentials[k] = v newCredentials[k] = v
} }
} }
// 如果 project_id 获取失败,先更新凭证,再标记账户为 error
if tokenInfo.ProjectIDMissing {
// 先更新凭证
_, updateErr := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{
Credentials: newCredentials,
})
if updateErr != nil {
response.InternalError(c, "Failed to update credentials: "+updateErr.Error())
return
}
// 标记账户为 error
if setErr := h.adminService.SetAccountError(c.Request.Context(), accountID, "账户缺少project id可能无法使用Antigravity"); setErr != nil {
response.InternalError(c, "Failed to set account error: "+setErr.Error())
return
}
response.Success(c, gin.H{
"message": "Token refreshed but project_id is missing, account marked as error",
"warning": "missing_project_id",
})
return
}
} else { } else {
// Use Anthropic/Claude OAuth service to refresh token // Use Anthropic/Claude OAuth service to refresh token
tokenInfo, err := h.oauthService.RefreshAccountToken(c.Request.Context(), account) tokenInfo, err := h.oauthService.RefreshAccountToken(c.Request.Context(), account)

View File

@@ -42,6 +42,7 @@ type AdminService interface {
DeleteAccount(ctx context.Context, id int64) error DeleteAccount(ctx context.Context, id int64) error
RefreshAccountCredentials(ctx context.Context, id int64) (*Account, error) RefreshAccountCredentials(ctx context.Context, id int64) (*Account, error)
ClearAccountError(ctx context.Context, id int64) (*Account, error) ClearAccountError(ctx context.Context, id int64) (*Account, error)
SetAccountError(ctx context.Context, id int64, errorMsg string) error
SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error)
BulkUpdateAccounts(ctx context.Context, input *BulkUpdateAccountsInput) (*BulkUpdateAccountsResult, error) BulkUpdateAccounts(ctx context.Context, input *BulkUpdateAccountsInput) (*BulkUpdateAccountsResult, error)
@@ -991,6 +992,10 @@ func (s *adminServiceImpl) ClearAccountError(ctx context.Context, id int64) (*Ac
return account, nil return account, nil
} }
func (s *adminServiceImpl) SetAccountError(ctx context.Context, id int64, errorMsg string) error {
return s.accountRepo.SetError(ctx, id, errorMsg)
}
func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error) { func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error) {
if err := s.accountRepo.SetSchedulable(ctx, id, schedulable); err != nil { if err := s.accountRepo.SetSchedulable(ctx, id, schedulable); err != nil {
return nil, err return nil, err

View File

@@ -82,13 +82,14 @@ type AntigravityExchangeCodeInput struct {
// AntigravityTokenInfo token 信息 // AntigravityTokenInfo token 信息
type AntigravityTokenInfo struct { type AntigravityTokenInfo struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"` ExpiresIn int64 `json:"expires_in"`
ExpiresAt int64 `json:"expires_at"` ExpiresAt int64 `json:"expires_at"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
ProjectID string `json:"project_id,omitempty"` ProjectID string `json:"project_id,omitempty"`
ProjectIDMissing bool `json:"-"` // LoadCodeAssist 未返回 project_id
} }
// ExchangeCode 用 authorization code 交换 token // ExchangeCode 用 authorization code 交换 token
@@ -236,16 +237,15 @@ func (s *AntigravityOAuthService) RefreshAccountToken(ctx context.Context, accou
tokenInfo.Email = existingEmail tokenInfo.Email = existingEmail
} }
// 每次刷新都调用 LoadCodeAssist 更新 project_id // 每次刷新都调用 LoadCodeAssist 获取 project_id
client := antigravity.NewClient(proxyURL) client := antigravity.NewClient(proxyURL)
loadResp, _, err := client.LoadCodeAssist(ctx, tokenInfo.AccessToken) loadResp, _, err := client.LoadCodeAssist(ctx, tokenInfo.AccessToken)
if err != nil { if err != nil || loadResp == nil || loadResp.CloudAICompanionProject == "" {
// 失败时保留原有 project_id // LoadCodeAssist 失败或返回空,保留原有 project_id,标记缺失
existingProjectID := strings.TrimSpace(account.GetCredential("project_id")) existingProjectID := strings.TrimSpace(account.GetCredential("project_id"))
if existingProjectID != "" { tokenInfo.ProjectID = existingProjectID
tokenInfo.ProjectID = existingProjectID tokenInfo.ProjectIDMissing = true
} } else {
} else if loadResp != nil && loadResp.CloudAICompanionProject != "" {
tokenInfo.ProjectID = loadResp.CloudAICompanionProject tokenInfo.ProjectID = loadResp.CloudAICompanionProject
} }

View File

@@ -61,5 +61,10 @@ func (r *AntigravityTokenRefresher) Refresh(ctx context.Context, account *Accoun
} }
} }
// 如果 project_id 获取失败,返回 credentials 但同时返回错误让账户被标记
if tokenInfo.ProjectIDMissing {
return newCredentials, fmt.Errorf("missing_project_id: 账户缺少project id可能无法使用Antigravity")
}
return newCredentials, nil return newCredentials, nil
} }

View File

@@ -163,12 +163,16 @@ func (s *TokenRefreshService) refreshWithRetry(ctx context.Context, account *Acc
for attempt := 1; attempt <= s.cfg.MaxRetries; attempt++ { for attempt := 1; attempt <= s.cfg.MaxRetries; attempt++ {
newCredentials, err := refresher.Refresh(ctx, account) newCredentials, err := refresher.Refresh(ctx, account)
if err == nil {
// 刷新成功更新账号credentials // 如果有新凭证,先更新(即使有错误也要保存 token
if newCredentials != nil {
account.Credentials = newCredentials account.Credentials = newCredentials
if err := s.accountRepo.Update(ctx, account); err != nil { if saveErr := s.accountRepo.Update(ctx, account); saveErr != nil {
return fmt.Errorf("failed to save credentials: %w", err) return fmt.Errorf("failed to save credentials: %w", saveErr)
} }
}
if err == nil {
return nil return nil
} }
@@ -219,6 +223,7 @@ func isNonRetryableRefreshError(err error) bool {
"invalid_client", // 客户端配置错误 "invalid_client", // 客户端配置错误
"unauthorized_client", // 客户端未授权 "unauthorized_client", // 客户端未授权
"access_denied", // 访问被拒绝 "access_denied", // 访问被拒绝
"missing_project_id", // 缺少 project_id
} }
for _, needle := range nonRetryable { for _, needle := range nonRetryable {
if strings.Contains(msg, needle) { if strings.Contains(msg, needle) {