mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
Google 格式错误体没有 type 字段,ops 一律记为 api_error,客户端参数错误、 引用不存在的缓存等都被归为平台内部错误。 - googleError 写出时按状态码向 ops 登记错误类型:400/413 invalid_request_error、 401 authentication_error、403 permission_error、404 not_found_error, 429 与 5xx 不登记 - 计费、并发与风控拦截沿用 Claude / OpenAI 格式错误的类型与错误码,归类口径一致 - 存储价未配置、显式缓存未启用改用 googlePlatformError,仍按平台错误统计; 错误透传规则改写的上游错误不登记本地类型 - 上游返回的缓存不存在标记为请求级失败:按客户端请求错误分类, 上游原始错误保留为明细但不参与上游归因
193 lines
6.4 KiB
Go
193 lines
6.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/googleapi"
|
|
"github.com/Wei-Shaw/sub2api/internal/securityaudit"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
coderws "github.com/coder/websocket"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func (h *OpenAIGatewayHandler) openAISecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
h.errorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
|
|
return
|
|
}
|
|
errType := "api_error"
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
errType = "permission_error"
|
|
}
|
|
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
|
|
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
|
|
}})
|
|
}
|
|
|
|
func (h *GatewayHandler) openAISecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
h.chatCompletionsErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
|
|
return
|
|
}
|
|
errType := "api_error"
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
errType = "permission_error"
|
|
}
|
|
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
|
|
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
|
|
}})
|
|
}
|
|
|
|
func (h *GatewayHandler) responsesSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
h.responsesErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
|
|
return
|
|
}
|
|
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
|
|
"type": "api_error", "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
|
|
}})
|
|
}
|
|
|
|
func (h *GatewayHandler) anthropicSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
h.errorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
|
|
return
|
|
}
|
|
errType := "api_error"
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
errType = "permission_error"
|
|
}
|
|
c.JSON(securityAuditStatus(decision), gin.H{"type": "error", "error": gin.H{
|
|
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
|
|
}})
|
|
}
|
|
|
|
func (h *OpenAIGatewayHandler) anthropicSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
h.anthropicErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
|
|
return
|
|
}
|
|
errType := "api_error"
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
errType = "permission_error"
|
|
}
|
|
c.JSON(securityAuditStatus(decision), gin.H{"type": "error", "error": gin.H{
|
|
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
|
|
}})
|
|
}
|
|
|
|
func googleSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
|
|
if decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
googleErrorWithType(c, securityAuditStatus(decision), securityAuditErrorCode(decision), "", securityAuditMessage(decision))
|
|
return
|
|
}
|
|
errType := "api_error"
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
errType = "permission_error"
|
|
}
|
|
service.SetOpsLocalErrorType(c, errType, securityAuditErrorCode(decision))
|
|
status := securityAuditStatus(decision)
|
|
googleStatus := googleapi.HTTPStatusToGoogleStatus(status)
|
|
if status == http.StatusServiceUnavailable {
|
|
googleStatus = "UNAVAILABLE"
|
|
}
|
|
requestID := ""
|
|
if c != nil && c.Request != nil {
|
|
requestID = contentModerationRequestID(c.Request.Context())
|
|
}
|
|
c.JSON(status, gin.H{"error": gin.H{
|
|
"code": status, "message": securityAuditMessage(decision), "status": googleStatus,
|
|
"details": []gin.H{{
|
|
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
|
|
"reason": securityAuditErrorCode(decision), "domain": "sub2api.securityaudit",
|
|
"metadata": gin.H{"request_id": requestID},
|
|
}},
|
|
}})
|
|
}
|
|
|
|
func writeSecurityAuditWSError(ctx context.Context, conn *coderws.Conn, decision *securityaudit.Decision) {
|
|
if conn == nil || decision == nil {
|
|
return
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
legacy := decision.Legacy
|
|
writeContentModerationWSError(ctx, conn, (legacyContentModerationDecision{legacy}).toService())
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
payload, err := json.Marshal(gin.H{
|
|
"event_id": "evt_prompt_guard_rejected", "type": "error",
|
|
"error": gin.H{"type": "invalid_request_error", "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision)},
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
writeCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
|
defer cancel()
|
|
_ = conn.Write(writeCtx, coderws.MessageText, payload)
|
|
}
|
|
|
|
type legacyContentModerationDecision struct{ value *securityaudit.LegacyDecision }
|
|
|
|
func (d legacyContentModerationDecision) toService() *service.ContentModerationDecision {
|
|
if d.value == nil {
|
|
return nil
|
|
}
|
|
return &service.ContentModerationDecision{Allowed: d.value.Allowed, Blocked: d.value.Blocked, Flagged: d.value.Flagged, Message: d.value.Message, StatusCode: d.value.StatusCode, Action: d.value.Action}
|
|
}
|
|
|
|
func securityAuditWSCloseStatus(decision *securityaudit.Decision) coderws.StatusCode {
|
|
if decision == nil {
|
|
return coderws.StatusInternalError
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
return coderws.StatusPolicyViolation
|
|
}
|
|
if decision.Kind == securityaudit.DecisionBlock {
|
|
return coderws.StatusCode(4403)
|
|
}
|
|
return coderws.StatusTryAgainLater
|
|
}
|
|
|
|
func securityAuditWSCloseReason(decision *securityaudit.Decision) string {
|
|
if decision == nil {
|
|
return securityaudit.ErrorCodeUnavailable
|
|
}
|
|
if decision.Legacy != nil && decision.Legacy.Blocked {
|
|
message := strings.TrimSpace(decision.Legacy.Message)
|
|
if message != "" {
|
|
return message
|
|
}
|
|
return "content_policy_violation"
|
|
}
|
|
code := securityAuditErrorCode(decision)
|
|
if code == "" {
|
|
return securityaudit.ErrorCodeUnavailable
|
|
}
|
|
return code
|
|
}
|