Add projects

This commit is contained in:
Alois 2026-08-24 00:10:41 +02:00
commit 8b607dd700
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
1802 changed files with 503346 additions and 2 deletions

View file

@ -0,0 +1,189 @@
// Package clienterror classifies upstream failures caused by the client request.
package clienterror
import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/tidwall/gjson"
)
// StatusClientClosedRequest is the nginx-style status used when the client
// aborts the request before the proxy finishes (context.Canceled).
const StatusClientClosedRequest = 499
var requestFaultCodes = map[string]struct{}{
"cyber_policy": {},
"context_length_exceeded": {},
"message_too_big": {},
"string_above_max_length": {},
"invalid_prompt": {},
"invalid_value": {},
"unsupported_value": {},
"invalid_request_error": {},
"previous_response_not_found": {},
}
var requestFaultTypes = map[string]struct{}{
"invalid_request": {},
"invalid_request_error": {},
"bad_request_error": {},
"invalid_prompt": {},
}
// HTTPStatusFromError extracts an HTTP status from err.
// Explicit StatusCode() values win. Otherwise context.Canceled maps to 499
// and context.DeadlineExceeded maps to 504. Returns 0 when unknown.
func HTTPStatusFromError(err error) int {
if err == nil {
return 0
}
type statusCoder interface {
StatusCode() int
}
var sc statusCoder
if errors.As(err, &sc) && sc != nil {
if code := sc.StatusCode(); code > 0 {
return code
}
}
if errors.Is(err, context.Canceled) {
return StatusClientClosedRequest
}
if errors.Is(err, context.DeadlineExceeded) {
return http.StatusGatewayTimeout
}
return 0
}
// HTTPStatusFromErrorOr is like HTTPStatusFromError but returns fallback when
// the error does not carry a known status.
func HTTPStatusFromErrorOr(err error, fallback int) int {
if code := HTTPStatusFromError(err); code > 0 {
return code
}
return fallback
}
// IsRequestFault reports whether an upstream failure is caused by the request
// and therefore must not rotate or penalize credentials.
func IsRequestFault(status int, err error) bool {
if status <= 0 && err != nil {
type statusCoder interface {
StatusCode() int
}
var statusErr statusCoder
if errors.As(err, &statusErr) && statusErr != nil {
status = statusErr.StatusCode()
}
}
// Payment and rate-limit statuses are authoritative even when an upstream
// pairs them with a generic invalid_request_error body. The credential must
// remain eligible for cooldown and rotation.
if status == http.StatusPaymentRequired || status == http.StatusTooManyRequests {
return false
}
// DeepSeek reports an invalid API key as 401 with the authentication_error
// type alongside the same generic code. Preserve that credential failure
// classification without weakening generic request-fault handling.
if status == http.StatusUnauthorized && hasAuthenticationErrorBody(err) {
return false
}
if hasRequestFaultBody(err) {
return true
}
if err != nil && IsItemNotPersisted(err.Error()) {
return true
}
switch status {
case http.StatusBadRequest,
http.StatusConflict,
http.StatusRequestEntityTooLarge,
http.StatusUnprocessableEntity:
return true
default:
return false
}
}
// IsItemNotPersisted matches the upstream 404 raised when a request references a
// response item the upstream never stored because `store` was false. The upstream
// sends this as a plain-text message rather than a JSON body, so it cannot be
// recognized through the structured identifiers above.
//
// The request can only succeed once the client rebuilds it without the stale
// reference, so it is a request fault: rotating credentials cannot help, and the
// client must be told rather than left to retry the same broken input.
func IsItemNotPersisted(message string) bool {
lower := strings.ToLower(message)
return strings.Contains(lower, "item with id") &&
strings.Contains(lower, "not found") &&
strings.Contains(lower, "items are not persisted when `store` is set to false")
}
func hasAuthenticationErrorBody(err error) bool {
if err == nil {
return false
}
body := strings.TrimSpace(err.Error())
if body == "" || !json.Valid([]byte(body)) {
return false
}
for _, path := range []string{"error.type", "type", "response.error.type", "body.error.type"} {
if errType := strings.ToLower(strings.TrimSpace(gjson.Get(body, path).String())); errType == "authentication_error" {
return true
}
}
return false
}
func hasRequestFaultBody(err error) bool {
if err == nil {
return false
}
body := strings.TrimSpace(err.Error())
if body == "" || !json.Valid([]byte(body)) {
return false
}
for _, path := range []string{"error.code", "code", "response.error.code", "body.error.code"} {
code := strings.ToLower(strings.TrimSpace(gjson.Get(body, path).String()))
if _, ok := requestFaultCodes[code]; ok {
return true
}
}
for _, path := range []string{"error.type", "type", "response.error.type", "body.error.type"} {
errType := strings.ToLower(strings.TrimSpace(gjson.Get(body, path).String()))
if _, ok := requestFaultTypes[errType]; ok {
return true
}
}
return false
}
// IsClientCancellation reports whether an HTTP status code or error represents
// a client-initiated cancellation (HTTP 499 StatusClientClosedRequest or context.Canceled).
func IsClientCancellation(status int, err error) bool {
if status == StatusClientClosedRequest {
return true
}
if err != nil {
if errors.Is(err, context.Canceled) {
return true
}
type statusCoder interface {
StatusCode() int
}
var sc statusCoder
if errors.As(err, &sc) && sc != nil && sc.StatusCode() == StatusClientClosedRequest {
return true
}
lower := strings.ToLower(err.Error())
if strings.Contains(lower, "context canceled") || strings.Contains(lower, "client closed request") {
return true
}
}
return false
}

View file

@ -0,0 +1,255 @@
package clienterror
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"testing"
)
type statusError struct {
status int
body string
}
func (e statusError) Error() string { return e.body }
func (e statusError) StatusCode() int { return e.status }
func TestHTTPStatusFromError(t *testing.T) {
tests := []struct {
name string
err error
want int
}{
{name: "nil", err: nil, want: 0},
{name: "plain error", err: errors.New("boom"), want: 0},
{name: "context canceled", err: context.Canceled, want: StatusClientClosedRequest},
{name: "context deadline exceeded", err: context.DeadlineExceeded, want: http.StatusGatewayTimeout},
{
name: "url error wraps canceled",
err: &url.Error{Op: "Post", URL: "https://example.com", Err: context.Canceled},
want: StatusClientClosedRequest,
},
{
name: "url error wraps deadline",
err: &url.Error{Op: "Post", URL: "https://example.com", Err: context.DeadlineExceeded},
want: http.StatusGatewayTimeout,
},
{
name: "fmt wrap canceled",
err: fmt.Errorf("upstream: %w", context.Canceled),
want: StatusClientClosedRequest,
},
{
name: "explicit status code wins",
err: statusError{status: http.StatusTooManyRequests, body: "rate limited"},
want: http.StatusTooManyRequests,
},
{
name: "explicit status wins over canceled unwrap",
err: statusAndUnwrapError{
status: http.StatusTooManyRequests,
body: "rate limited",
cause: context.Canceled,
},
want: http.StatusTooManyRequests,
},
{
name: "zero status code falls through to canceled unwrap",
err: statusAndUnwrapError{
status: 0,
body: "canceled",
cause: context.Canceled,
},
want: StatusClientClosedRequest,
},
{
name: "zero status code without unwrap stays unknown",
err: statusError{status: 0, body: context.Canceled.Error()},
want: 0,
},
{
name: "wrapped status code via errors.As",
err: fmt.Errorf("execute failed: %w", statusError{status: http.StatusUnauthorized, body: "unauthorized"}),
want: http.StatusUnauthorized,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := HTTPStatusFromError(tc.err); got != tc.want {
t.Fatalf("HTTPStatusFromError() = %d, want %d", got, tc.want)
}
})
}
if got := HTTPStatusFromErrorOr(errors.New("boom"), http.StatusBadGateway); got != http.StatusBadGateway {
t.Fatalf("HTTPStatusFromErrorOr(plain) = %d, want %d", got, http.StatusBadGateway)
}
if got := HTTPStatusFromErrorOr(context.Canceled, http.StatusBadGateway); got != StatusClientClosedRequest {
t.Fatalf("HTTPStatusFromErrorOr(canceled) = %d, want %d", got, StatusClientClosedRequest)
}
}
type statusAndUnwrapError struct {
status int
body string
cause error
}
func (e statusAndUnwrapError) Error() string { return e.body }
func (e statusAndUnwrapError) StatusCode() int {
return e.status
}
func (e statusAndUnwrapError) Unwrap() error { return e.cause }
func TestIsRequestFaultStructuredIdentifiers(t *testing.T) {
for _, code := range []string{
"cyber_policy",
"context_length_exceeded",
"message_too_big",
"string_above_max_length",
"invalid_prompt",
"invalid_value",
"unsupported_value",
"invalid_request_error",
"previous_response_not_found",
} {
t.Run("code/"+code, func(t *testing.T) {
err := errors.New(`{"error":{"code":"` + code + `"}}`)
if !IsRequestFault(http.StatusBadGateway, err) {
t.Fatalf("code %q was not classified as a request fault", code)
}
})
}
for _, errType := range []string{
"invalid_request",
"invalid_request_error",
"bad_request_error",
"invalid_prompt",
} {
t.Run("type/"+errType, func(t *testing.T) {
err := errors.New(`{"error":{"type":"` + errType + `"}}`)
if !IsRequestFault(http.StatusBadGateway, err) {
t.Fatalf("type %q was not classified as a request fault", errType)
}
})
}
}
func TestIsRequestFault(t *testing.T) {
tests := []struct {
name string
status int
err error
want bool
}{
{name: "bad request status", status: http.StatusBadRequest, err: errors.New("bad request"), want: true},
{name: "conflict status", status: http.StatusConflict, err: errors.New("conflict"), want: true},
{name: "entity too large status", status: http.StatusRequestEntityTooLarge, err: errors.New("too large"), want: true},
{name: "unprocessable status", status: http.StatusUnprocessableEntity, err: errors.New("unprocessable"), want: true},
{
name: "cyber policy behind bad gateway",
status: http.StatusBadGateway,
err: errors.New(`{"error":{"type":"invalid_request","code":"cyber_policy","message":"blocked"}}`),
want: true,
},
{
name: "context length behind internal error",
status: http.StatusInternalServerError,
err: errors.New(`{"response":{"error":{"type":"server_error","code":"context_length_exceeded"}}}`),
want: true,
},
{
name: "invalid request type behind bad gateway",
status: http.StatusBadGateway,
err: errors.New(`{"body":{"error":{"type":"invalid_request","message":"invalid"}}}`),
want: true,
},
{
name: "status from error",
err: statusError{status: http.StatusConflict, body: "conflict"},
want: true,
},
{
// Verbatim upstream text: plain text, not JSON, so it can only be matched
// by message.
name: "item not persisted with store=false",
status: http.StatusNotFound,
err: errors.New("Item with id 'rs_0b5f3eb6f51f175c0169ca74e4a85881998539920821603a74' not found. Items are not persisted when `store` is set to false. Try again with `store` set to true, or remove this item from your input."),
want: true,
},
{
// An upstream internal error is not a request fault: it must stay eligible
// for credential rotation and (credential, model) cooldown.
name: "upstream unknown internal error",
status: http.StatusInternalServerError,
err: errors.New(`{"error":{"code":500,"message":"Internal error encountered.","status":"UNKNOWN"}}`),
},
{name: "plain not found", status: http.StatusNotFound, err: errors.New("model not found")},
{name: "unauthorized", status: http.StatusUnauthorized, err: errors.New("invalid token")},
{
name: "deepseek authentication failure is credential failure",
status: http.StatusUnauthorized,
err: errors.New(`{"error":{"code":"invalid_request_error","message":"Authentication Fails, Your api key: ****heck is invalid","param":null,"type":"authentication_error"}}`),
want: false,
},
{
name: "deepseek insufficient balance is payment failure",
status: http.StatusPaymentRequired,
err: errors.New(`{"error":{"message":"Insufficient Balance","type":"unknown_error","param":null,"code":"invalid_request_error"}}`),
want: false,
},
{
name: "rate limit status overrides generic request error code",
status: http.StatusTooManyRequests,
err: errors.New(`{"error":{"message":"Rate Limit Reached","type":"unknown_error","param":null,"code":"invalid_request_error"}}`),
want: false,
},
{name: "quota", status: http.StatusTooManyRequests, err: errors.New("quota")},
{name: "transport", status: http.StatusBadGateway, err: errors.New("unexpected EOF")},
{name: "invalid JSON body", status: http.StatusBadGateway, err: errors.New(`{"error":`)},
{name: "nil", status: 0},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := IsRequestFault(tc.status, tc.err); got != tc.want {
t.Fatalf("IsRequestFault(%d, %v) = %t, want %t", tc.status, tc.err, got, tc.want)
}
})
}
}
func TestIsClientCancellation(t *testing.T) {
tests := []struct {
name string
status int
err error
want bool
}{
{name: "status 499", status: StatusClientClosedRequest, want: true},
{name: "context canceled error", status: 0, err: context.Canceled, want: true},
{name: "fmt wrapped context canceled", status: 0, err: fmt.Errorf("read: %w", context.Canceled), want: true},
{name: "context canceled string in error", status: 0, err: errors.New("upstream failed: context canceled"), want: true},
{name: "client closed request string in error", status: 0, err: errors.New("client closed request"), want: true},
{name: "statusCoder with 499", status: 0, err: statusError{status: StatusClientClosedRequest, body: "aborted"}, want: true},
{name: "status 200 without error", status: http.StatusOK, err: nil, want: false},
{name: "status 400 bad request", status: http.StatusBadRequest, err: errors.New("bad request"), want: false},
{name: "status 429 rate limit", status: http.StatusTooManyRequests, err: errors.New("rate limited"), want: false},
{name: "status 500 internal error", status: http.StatusInternalServerError, err: errors.New("internal error"), want: false},
{name: "plain unrelated error", status: 0, err: errors.New("connection reset by peer"), want: false},
{name: "nil error and 0 status", status: 0, err: nil, want: false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := IsClientCancellation(tc.status, tc.err); got != tc.want {
t.Fatalf("IsClientCancellation(%d, %v) = %t, want %t", tc.status, tc.err, got, tc.want)
}
})
}
}