当 Gemini for Google Cloud API 未启用时(SERVICE_DISABLED 错误), 系统现在会: - 自动检测 403 PERMISSION_DENIED 错误 - 从错误响应中提取 API 激活 URL - 向用户显示清晰的错误消息和可点击的激活链接 - 提供操作指引(启用后等待几分钟) 新增文件: - internal/pkg/googleapi/error.go: Google API 错误解析器 - internal/pkg/googleapi/error_test.go: 完整的测试覆盖 - GEMINI_API_ERROR_HANDLING.md: 实现文档 修改文件: - internal/repository/geminicli_codeassist_client.go: 在 LoadCodeAssist 和 OnboardUser 中增强错误处理 这大大改善了用户体验,用户不再需要手动从错误日志中查找激活 URL。
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
// Package googleapi provides helpers for Google-style API responses.
|
|
package googleapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ErrorResponse represents a Google API error response
|
|
type ErrorResponse struct {
|
|
Error ErrorDetail `json:"error"`
|
|
}
|
|
|
|
// ErrorDetail contains the error details from Google API
|
|
type ErrorDetail struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Status string `json:"status"`
|
|
Details []json.RawMessage `json:"details,omitempty"`
|
|
}
|
|
|
|
// ErrorDetailInfo contains additional error information
|
|
type ErrorDetailInfo struct {
|
|
Type string `json:"@type"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Domain string `json:"domain,omitempty"`
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// ErrorHelp contains help links
|
|
type ErrorHelp struct {
|
|
Type string `json:"@type"`
|
|
Links []HelpLink `json:"links,omitempty"`
|
|
}
|
|
|
|
// HelpLink represents a help link
|
|
type HelpLink struct {
|
|
Description string `json:"description"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// ParseError parses a Google API error response and extracts key information
|
|
func ParseError(body string) (*ErrorResponse, error) {
|
|
var errResp ErrorResponse
|
|
if err := json.Unmarshal([]byte(body), &errResp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse error response: %w", err)
|
|
}
|
|
return &errResp, nil
|
|
}
|
|
|
|
// ExtractActivationURL extracts the API activation URL from error details
|
|
func ExtractActivationURL(body string) string {
|
|
var errResp ErrorResponse
|
|
if err := json.Unmarshal([]byte(body), &errResp); err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Check error details for activation URL
|
|
for _, detailRaw := range errResp.Error.Details {
|
|
// Parse as ErrorDetailInfo
|
|
var info ErrorDetailInfo
|
|
if err := json.Unmarshal(detailRaw, &info); err == nil {
|
|
if info.Metadata != nil {
|
|
if activationURL, ok := info.Metadata["activationUrl"]; ok && activationURL != "" {
|
|
return activationURL
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse as ErrorHelp
|
|
var help ErrorHelp
|
|
if err := json.Unmarshal(detailRaw, &help); err == nil {
|
|
for _, link := range help.Links {
|
|
if strings.Contains(link.Description, "activation") ||
|
|
strings.Contains(link.Description, "API activation") ||
|
|
strings.Contains(link.URL, "/apis/api/") {
|
|
return link.URL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// IsServiceDisabledError checks if the error is a SERVICE_DISABLED error
|
|
func IsServiceDisabledError(body string) bool {
|
|
var errResp ErrorResponse
|
|
if err := json.Unmarshal([]byte(body), &errResp); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if it's a 403 PERMISSION_DENIED with SERVICE_DISABLED reason
|
|
if errResp.Error.Code != 403 || errResp.Error.Status != "PERMISSION_DENIED" {
|
|
return false
|
|
}
|
|
|
|
for _, detailRaw := range errResp.Error.Details {
|
|
var info ErrorDetailInfo
|
|
if err := json.Unmarshal(detailRaw, &info); err == nil {
|
|
if info.Reason == "SERVICE_DISABLED" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|