Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
170
backend/sdk/api/handlers/handlers_errors.go
Normal file
170
backend/sdk/api/handlers/handlers_errors.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/clienterror"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func statusFromError(err error) int {
|
||||
return clienterror.HTTPStatusFromError(err)
|
||||
}
|
||||
|
||||
func isAuthSelectionUnavailable(err error) bool {
|
||||
var authErr *coreauth.Error
|
||||
if !errors.As(err, &authErr) || authErr == nil {
|
||||
return false
|
||||
}
|
||||
code := strings.TrimSpace(authErr.Code)
|
||||
return code == "auth_not_found" || code == "auth_unavailable"
|
||||
}
|
||||
|
||||
func enrichAuthSelectionError(err error, providers []string, model string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var authErr *coreauth.Error
|
||||
if !errors.As(err, &authErr) || authErr == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code := strings.TrimSpace(authErr.Code)
|
||||
if code != "auth_not_found" && code != "auth_unavailable" {
|
||||
return err
|
||||
}
|
||||
|
||||
providerText := strings.Join(providers, ",")
|
||||
if providerText == "" {
|
||||
providerText = "unknown"
|
||||
}
|
||||
modelText := strings.TrimSpace(model)
|
||||
if modelText == "" {
|
||||
modelText = "unknown"
|
||||
}
|
||||
|
||||
baseMessage := strings.TrimSpace(authErr.Message)
|
||||
if baseMessage == "" {
|
||||
baseMessage = "no auth available"
|
||||
}
|
||||
detail := fmt.Sprintf("%s (providers=%s, model=%s)", baseMessage, providerText, modelText)
|
||||
|
||||
// Clarify the most common alias confusion between Anthropic route names and internal provider keys.
|
||||
if strings.Contains(","+providerText+",", ",claude,") {
|
||||
detail += "; check Claude auth/key session and cooldown state via /v0/management/auth-files"
|
||||
}
|
||||
|
||||
status := authErr.HTTPStatus
|
||||
if status <= 0 {
|
||||
status = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
return &coreauth.Error{
|
||||
Code: authErr.Code,
|
||||
Message: detail,
|
||||
Retryable: authErr.Retryable,
|
||||
HTTPStatus: status,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteErrorResponse writes an error message to the response writer using the HTTP status embedded in the message.
|
||||
func (h *BaseAPIHandler) WriteErrorResponse(c *gin.Context, msg *interfaces.ErrorMessage) {
|
||||
status := http.StatusInternalServerError
|
||||
if msg != nil && msg.StatusCode > 0 {
|
||||
status = msg.StatusCode
|
||||
}
|
||||
if msg != nil && msg.DirectResponse {
|
||||
writeDirectErrorResponse(c, status, msg)
|
||||
return
|
||||
}
|
||||
if msg != nil && msg.Error != nil {
|
||||
for _, value := range coreauth.SafeResponseHeaders(msg.Error).Values("Retry-After") {
|
||||
c.Writer.Header().Add("Retry-After", value)
|
||||
}
|
||||
}
|
||||
if msg != nil && msg.Addon != nil && PassthroughHeadersEnabled(h.Cfg) {
|
||||
for key, values := range msg.Addon {
|
||||
if len(values) == 0 || IsCPAReservedResponseHeader(key) {
|
||||
continue
|
||||
}
|
||||
c.Writer.Header().Del(key)
|
||||
for _, value := range values {
|
||||
c.Writer.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errText := http.StatusText(status)
|
||||
if msg != nil && msg.Error != nil {
|
||||
if v := strings.TrimSpace(msg.Error.Error()); v != "" {
|
||||
errText = v
|
||||
}
|
||||
}
|
||||
|
||||
body := BuildErrorResponseBody(status, errText)
|
||||
// Append first to preserve upstream response logs, then drop duplicate payloads if already recorded.
|
||||
var previous []byte
|
||||
if existing, exists := c.Get("API_RESPONSE"); exists {
|
||||
if existingBytes, ok := existing.([]byte); ok && len(existingBytes) > 0 {
|
||||
previous = existingBytes
|
||||
}
|
||||
}
|
||||
appendAPIResponse(c, body)
|
||||
trimmedErrText := strings.TrimSpace(errText)
|
||||
trimmedBody := bytes.TrimSpace(body)
|
||||
if len(previous) > 0 {
|
||||
if (trimmedErrText != "" && bytes.Contains(previous, []byte(trimmedErrText))) ||
|
||||
(len(trimmedBody) > 0 && bytes.Contains(previous, trimmedBody)) {
|
||||
c.Set("API_RESPONSE", previous)
|
||||
}
|
||||
}
|
||||
|
||||
if !c.Writer.Written() {
|
||||
c.Writer.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
c.Status(status)
|
||||
_, _ = c.Writer.Write(body)
|
||||
}
|
||||
|
||||
func writeDirectErrorResponse(c *gin.Context, status int, msg *interfaces.ErrorMessage) {
|
||||
for key, values := range FilterUpstreamHeaders(msg.Headers) {
|
||||
if len(values) == 0 || IsCPAReservedResponseHeader(key) {
|
||||
continue
|
||||
}
|
||||
c.Writer.Header().Del(key)
|
||||
for _, value := range values {
|
||||
c.Writer.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
body := bytes.Clone(msg.Body)
|
||||
appendAPIResponse(c, body)
|
||||
if !c.Writer.Written() && c.Writer.Header().Get("Content-Type") == "" {
|
||||
c.Writer.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
c.Status(status)
|
||||
_, _ = c.Writer.Write(body)
|
||||
}
|
||||
|
||||
func (h *BaseAPIHandler) LoggingAPIResponseError(ctx context.Context, err *interfaces.ErrorMessage) {
|
||||
if h.Cfg.RequestLog {
|
||||
if ginContext, ok := ctx.Value("gin").(*gin.Context); ok {
|
||||
if apiResponseErrors, isExist := ginContext.Get("API_RESPONSE_ERROR"); isExist {
|
||||
if slicesAPIResponseError, isOk := apiResponseErrors.([]*interfaces.ErrorMessage); isOk {
|
||||
slicesAPIResponseError = append(slicesAPIResponseError, err)
|
||||
ginContext.Set("API_RESPONSE_ERROR", slicesAPIResponseError)
|
||||
}
|
||||
} else {
|
||||
// Create new response data entry
|
||||
ginContext.Set("API_RESPONSE_ERROR", []*interfaces.ErrorMessage{err})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue