Files
sub2api/backend/internal/service/openai_gateway_usage_integrity.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

82 lines
2.4 KiB
Go

package service
import (
"encoding/json"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
const (
grokMissingUsageErrorCode = "grok_missing_usage"
grokMissingUsageMessage = "xAI upstream returned a successful chat completion without billable usage"
)
// hasBillableGrokChatUsage stays aligned with the aggregate token buckets used
// to account for chat completions. Detail fields alone do not prove that the
// successful response can be settled safely.
func hasBillableGrokChatUsage(usage OpenAIUsage) bool {
return usage.InputTokens > 0 ||
usage.OutputTokens > 0 ||
usage.CacheCreationInputTokens > 0 ||
usage.CacheReadInputTokens > 0
}
// requiresBillableGrokChatUsage identifies Grok traffic by both account
// platform and model identity. Grok models may be served through generic
// OpenAI-compatible accounts, so account.Platform alone is not a safe billing
// boundary.
func requiresBillableGrokChatUsage(account *Account, models ...string) bool {
if account != nil && account.Platform == PlatformGrok {
return true
}
for _, model := range models {
normalized := strings.ToLower(strings.TrimSpace(model))
if separator := strings.LastIndex(normalized, "/"); separator >= 0 {
normalized = strings.TrimSpace(normalized[separator+1:])
}
if normalized == "grok" || strings.HasPrefix(normalized, "grok-") {
return true
}
}
return false
}
func newGrokMissingUsageFailoverError(c *gin.Context, account *Account, upstreamRequestID string) *UpstreamFailoverError {
accountID := int64(0)
accountName := ""
if account != nil {
accountID = account.ID
accountName = account.Name
}
setOpsUpstreamError(c, http.StatusBadGateway, grokMissingUsageMessage, "")
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
Platform: PlatformGrok,
AccountID: accountID,
AccountName: accountName,
UpstreamStatusCode: http.StatusBadGateway,
UpstreamRequestID: strings.TrimSpace(upstreamRequestID),
Kind: "failover",
Message: grokMissingUsageMessage,
})
body, _ := json.Marshal(gin.H{
"error": gin.H{
"type": "upstream_error",
"code": grokMissingUsageErrorCode,
"message": grokMissingUsageMessage,
},
})
headers := http.Header{}
if requestID := strings.TrimSpace(upstreamRequestID); requestID != "" {
headers.Set("x-request-id", requestID)
}
return &UpstreamFailoverError{
StatusCode: http.StatusBadGateway,
ResponseBody: body,
ResponseHeaders: headers,
}
}