fix(cliproxy): centralize client error status mapping and apply context cancellation/deadline HTTP codes

Closes: #4601
This commit is contained in:
Luis Pater
2026-08-08 04:53:34 +08:00
parent 37609fa179
commit 4b3cc55cdc
15 changed files with 297 additions and 59 deletions

View File

@@ -17,6 +17,7 @@ import (
"sync"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v7/internal/clienterror"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
@@ -175,7 +176,7 @@ func (h *Handler) Handle(c *gin.Context) {
body, errRead := readBody(c.Request.Body)
if errRead != nil {
status := http.StatusBadRequest
status := clienterror.HTTPStatusFromErrorOr(errRead, http.StatusBadRequest)
if errors.Is(errRead, errBodyTooLarge) {
status = http.StatusRequestEntityTooLarge
}
@@ -246,7 +247,7 @@ func (h *Handler) Handle(c *gin.Context) {
authIndex: selectedIndex,
})
if errSDP != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": errSDP.Error()})
c.JSON(clienterror.HTTPStatusFromErrorOr(errSDP, http.StatusBadGateway), gin.H{"error": errSDP.Error()})
return
}
defer func() {
@@ -291,7 +292,7 @@ func (h *Handler) Handle(c *gin.Context) {
if selection != nil {
selection.End("attempt_canceled")
}
c.JSON(http.StatusRequestTimeout, gin.H{"error": errContext.Error()})
c.JSON(clienterror.HTTPStatusFromErrorOr(errContext, http.StatusRequestTimeout), gin.H{"error": errContext.Error()})
return
}
resp, errRequest := performRequest(selected)
@@ -300,7 +301,7 @@ func (h *Handler) Handle(c *gin.Context) {
selection.End("request_failed")
}
helps.RecordAPIResponseError(ctx, runtimeConfig, errRequest)
c.JSON(http.StatusBadGateway, gin.H{"error": errRequest.Error()})
c.JSON(clienterror.HTTPStatusFromErrorOr(errRequest, http.StatusBadGateway), gin.H{"error": errRequest.Error()})
return
}
if selection != nil && resp.StatusCode == http.StatusUnauthorized {
@@ -327,7 +328,7 @@ func (h *Handler) Handle(c *gin.Context) {
if errRequest != nil {
selection.End("retry_failed")
helps.RecordAPIResponseError(ctx, runtimeConfig, errRequest)
c.JSON(http.StatusBadGateway, gin.H{"error": errRequest.Error()})
c.JSON(clienterror.HTTPStatusFromErrorOr(errRequest, http.StatusBadGateway), gin.H{"error": errRequest.Error()})
return
}
if resp.StatusCode == http.StatusUnauthorized {
@@ -361,10 +362,12 @@ func (h *Handler) Handle(c *gin.Context) {
if errResponse != nil {
helps.RecordAPIResponseError(ctx, runtimeConfig, errResponse)
message := "Failed to read Codex live response"
status := clienterror.HTTPStatusFromErrorOr(errResponse, http.StatusBadGateway)
if errors.Is(errResponse, errBodyTooLarge) {
message = "Codex live response body too large"
status = http.StatusBadGateway
}
c.JSON(http.StatusBadGateway, gin.H{"error": message})
c.JSON(status, gin.H{"error": message})
return
}
helps.AppendAPIResponseChunk(ctx, runtimeConfig, responseBody)
@@ -389,7 +392,7 @@ func (h *Handler) Handle(c *gin.Context) {
}
downstreamAnswer, errAnswer := mediaSession.AcceptUpstreamAnswer(ctx, upstreamAnswer)
if errAnswer != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": errAnswer.Error()})
c.JSON(clienterror.HTTPStatusFromErrorOr(errAnswer, http.StatusBadGateway), gin.H{"error": errAnswer.Error()})
return
}
responseBodyToWrite = []byte(downstreamAnswer)
@@ -718,10 +721,7 @@ func writeResponseHeaders(destination, source http.Header) {
}
func writeSelectionError(c *gin.Context, err error) {
status := http.StatusServiceUnavailable
if statusError, ok := err.(interface{ StatusCode() int }); ok && statusError.StatusCode() > 0 {
status = statusError.StatusCode()
}
status := clienterror.HTTPStatusFromErrorOr(err, http.StatusServiceUnavailable)
for _, value := range auth.SafeResponseHeaders(err).Values("Retry-After") {
c.Writer.Header().Add("Retry-After", value)
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/router-for-me/CLIProxyAPI/v7/internal/clienterror"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
@@ -550,7 +551,7 @@ func callIDFromLocation(location string) string {
}
func handleSidebandDialError(c *gin.Context, ctx context.Context, cfg *config.Config, response *http.Response, errDial error) {
status := http.StatusBadGateway
status := clienterror.HTTPStatusFromErrorOr(errDial, http.StatusBadGateway)
if response != nil {
if response.StatusCode > 0 {
status = response.StatusCode