Files
gemini-banlancer/internal/errors/upstream_errors.go
2025-11-20 12:24:05 +08:00

112 lines
3.9 KiB
Go

// Filename: internal/errors/upstream_errors.go
package errors
import (
"strings"
)
// TODO: [Future Evolution] This file establishes the new, granular error classification framework.
// The next step is to refactor the handleKeyUsageEvent method in APIKeyService to utilize these new
// classifiers and implement the corresponding actions:
//
// 1. On IsPermanentUpstreamError:
// - Set mapping status to models.StatusBanned.
// - Set the master APIKey's status to models.MasterStatusRevoked.
// - This is a "one-strike, you're out" policy for definitively invalid keys.
//
// 2. On IsTemporaryUpstreamError:
// - Increment mapping.ConsecutiveErrorCount.
// - Check against the blacklist threshold to potentially set status to models.StatusDisabled.
// - This is for recoverable errors that are the key's fault (e.g., quota limits).
//
// 3. On ALL other upstream errors (that are not Permanent or Temporary):
// - These are treated as "Truly Ignorable" from the key's perspective (e.g., 503 Service Unavailable).
// - Do NOT increment the error count. Only update LastUsedAt.
// - This prevents good keys from being punished for upstream service instability.
// --- 1. Permanent Errors ---
// Errors that indicate the API Key itself is permanently invalid.
// Action: Ban mapping, Revoke Master Key.
var permanentErrorSubstrings = []string{
"invalid api key",
"api key not valid",
"api key suspended",
"API Key not found",
"api key expired",
"permission denied", // Often indicates the key lacks permissions for the target model/service.
"permission_denied", // Catches the 'status' field in Google's JSON error, e.g., "status": "PERMISSION_DENIED".
"service_disabled", // Catches the 'reason' field for disabled APIs, e.g., "reason": "SERVICE_DISABLED".
"api has not been used",
}
// --- 2. Temporary Errors ---
// Errors that are attributable to the key's state but are recoverable over time.
// Action: Increment consecutive error count, potentially disable the key.
var temporaryErrorSubstrings = []string{
"quota",
"limit reached",
"insufficient",
"billing",
"exceeded",
"too many requests",
}
// --- 3. Unretryable Request Errors ---
// Errors indicating a problem with the user's request, not the key. Retrying with a new key is pointless.
// Action: Abort the retry loop immediately in ProxyHandler.
var unretryableRequestErrorSubstrings = []string{
"invalid content",
"invalid argument",
"malformed",
"unsupported",
"invalid model",
}
// --- 4. Ignorable Client/Network Errors ---
// Network-level errors, typically caused by the client disconnecting.
// Action: Ignore for logging and metrics purposes.
var clientNetworkErrorSubstrings = []string{
"context canceled",
"connection reset by peer",
"broken pipe",
"use of closed network connection",
"request canceled",
}
// IsPermanentUpstreamError checks if an upstream error indicates the key is permanently invalid.
func IsPermanentUpstreamError(msg string) bool {
return containsSubstring(msg, permanentErrorSubstrings)
}
// IsTemporaryUpstreamError checks if an upstream error is due to temporary, key-specific limits.
func IsTemporaryUpstreamError(msg string) bool {
return containsSubstring(msg, temporaryErrorSubstrings)
}
// IsUnretryableRequestError checks if an upstream error is due to a malformed user request.
func IsUnretryableRequestError(msg string) bool {
return containsSubstring(msg, unretryableRequestErrorSubstrings)
}
// IsClientNetworkError checks if an error is a common, ignorable client-side network issue.
func IsClientNetworkError(err error) bool {
if err == nil {
return false
}
return containsSubstring(err.Error(), clientNetworkErrorSubstrings)
}
// containsSubstring is a helper function to avoid code repetition.
func containsSubstring(s string, substrings []string) bool {
if s == "" {
return false
}
lowerS := strings.ToLower(s)
for _, sub := range substrings {
if strings.Contains(lowerS, sub) {
return true
}
}
return false
}