mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-19 14:09:05 +08:00
fix(claude): map request timeout status to timeout_error
- Map `http.StatusRequestTimeout` (408) to Claude `timeout_error` response type Closes: #6298
This commit is contained in:
@@ -459,7 +459,7 @@ func claudeErrorTypeFromStatus(status int) string {
|
||||
return "request_too_large"
|
||||
case http.StatusTooManyRequests:
|
||||
return "rate_limit_error"
|
||||
case http.StatusGatewayTimeout:
|
||||
case http.StatusRequestTimeout, http.StatusGatewayTimeout:
|
||||
return "timeout_error"
|
||||
case 529:
|
||||
return "overloaded_error"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,84 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestClaudeErrorTypeFromStatus(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
status int
|
||||
want string
|
||||
}{
|
||||
{http.StatusBadRequest, "invalid_request_error"},
|
||||
{http.StatusRequestTimeout, "timeout_error"},
|
||||
{http.StatusTooManyRequests, "rate_limit_error"},
|
||||
{http.StatusInternalServerError, "api_error"},
|
||||
{http.StatusGatewayTimeout, "timeout_error"},
|
||||
} {
|
||||
t.Run(http.StatusText(tt.status), func(t *testing.T) {
|
||||
if got := claudeErrorTypeFromStatus(tt.status); got != tt.want {
|
||||
t.Fatalf("error.type = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeIncompleteStreamError(t *testing.T) {
|
||||
const message = "stream error: stream disconnected before completion: stream closed before response.completed"
|
||||
for _, streaming := range []bool{false, true} {
|
||||
name := "JSON"
|
||||
if streaming {
|
||||
name = "committed SSE"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil)
|
||||
handler := NewClaudeCodeAPIHandler(&handlers.BaseAPIHandler{})
|
||||
msg := &interfaces.ErrorMessage{
|
||||
StatusCode: http.StatusRequestTimeout,
|
||||
Error: errors.New(message),
|
||||
}
|
||||
wantStatus := http.StatusRequestTimeout
|
||||
if streaming {
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
_, _ = c.Writer.Write([]byte("event: message_start\ndata: {}\n\n"))
|
||||
c.Writer.Flush()
|
||||
errs := make(chan *interfaces.ErrorMessage, 1)
|
||||
errs <- msg
|
||||
close(errs)
|
||||
handler.forwardClaudeStream(c, c.Writer, func(err error) {
|
||||
if err != msg.Error {
|
||||
t.Errorf("cancel error = %v, want %v", err, msg.Error)
|
||||
}
|
||||
}, nil, errs)
|
||||
wantStatus = http.StatusOK
|
||||
} else {
|
||||
handler.WriteErrorResponse(c, msg)
|
||||
}
|
||||
if recorder.Code != wantStatus {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, wantStatus)
|
||||
}
|
||||
body := recorder.Body.String()
|
||||
if streaming {
|
||||
const prefix = "event: error\ndata: "
|
||||
if strings.Count(body, prefix) != 1 {
|
||||
t.Fatalf("expected one terminal error event; body=%s", body)
|
||||
}
|
||||
_, body, _ = strings.Cut(body, prefix)
|
||||
body = strings.TrimSpace(body)
|
||||
}
|
||||
if got := gjson.Get(body, "type").String(); got != "error" {
|
||||
t.Errorf("type = %q, want error", got)
|
||||
}
|
||||
if got := gjson.Get(body, "error.type").String(); got != "timeout_error" {
|
||||
t.Errorf("error.type = %q, want timeout_error", got)
|
||||
}
|
||||
if got := gjson.Get(body, "error.message").String(); got != message {
|
||||
t.Errorf("error.message = %q, want %q", got, message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeErrorExtractsOpenAIStyleUpstreamJSON(t *testing.T) {
|
||||
handler := &ClaudeCodeAPIHandler{}
|
||||
msg := &interfaces.ErrorMessage{
|
||||
|
||||
Reference in New Issue
Block a user