125 lines · 2.9 KB · Go
RawDownload
package openai import (	"encoding/json"	"errors"	"fmt"	"net/http"	"strconv"	"strings"	"time" 	crawlembed "github.com/openclaw/crawlkit/embed") type APIError struct {	Status     int	Type       string	Code       string	Message    string	RetryAfter time.Duration} func (e *APIError) Error() string {	parts := []string{fmt.Sprintf("openai request status=%d", e.Status)}	if e.Type != "" {		parts = append(parts, "type="+e.Type)	}	if e.Code != "" {		parts = append(parts, "code="+e.Code)	}	if e.Message != "" {		parts = append(parts, "message="+e.Message)	}	return strings.Join(parts, " ")} func apiErrorFromHTTP(status int, header http.Header, body []byte, now time.Time) *APIError {	apiErr := &APIError{		Status:     status,		Message:    strings.TrimSpace(string(body)),		RetryAfter: parseRetryAfter(header.Get("Retry-After"), now),	}	var parsed summaryResponse	if err := json.Unmarshal(body, &parsed); err == nil && parsed.Error != nil {		apiErr.Message = parsed.Error.Message		apiErr.Type = parsed.Error.Type		apiErr.Code = parsed.Error.Code	}	return apiErr} func (e *APIError) Retryable() bool {	if e == nil {		return false	}	switch e.Status {	case http.StatusRequestTimeout, http.StatusTooManyRequests:		return e.Type != "insufficient_quota" && e.Code != "insufficient_quota"	case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:		return true	default:		return false	}} func (e *APIError) IsOverloaded() bool {	return e != nil && (e.Type == "overloaded_error" || (e.Status == http.StatusServiceUnavailable && e.Code == "overloaded"))} func AsAPIError(err error) *APIError {	if err == nil {		return nil	}	var apiErr *APIError	if errors.As(err, &apiErr) {		return apiErr	}	return nil} func apiErrorFromEmbed(err error, now time.Time) *APIError {	var httpErr *crawlembed.HTTPError	if !errors.As(err, &httpErr) {		return nil	}	apiErr := &APIError{		Status:     httpErr.StatusCode,		Message:    strings.TrimSpace(httpErr.Body),		RetryAfter: parseRetryAfter(httpErr.Header.Get("Retry-After"), now),	}	var parsed embeddingResponse	if jerr := json.Unmarshal([]byte(httpErr.Body), &parsed); jerr == nil && parsed.Error != nil {		apiErr.Message = parsed.Error.Message		apiErr.Type = parsed.Error.Type		apiErr.Code = parsed.Error.Code	}	return apiErr} func parseRetryAfter(header string, now time.Time) time.Duration {	header = strings.TrimSpace(header)	if header == "" {		return 0	}	if seconds, err := strconv.Atoi(header); err == nil {		if seconds < 0 {			return 0		}		return time.Duration(seconds) * time.Second	}	if seconds, err := strconv.ParseFloat(header, 64); err == nil {		if seconds < 0 {			return 0		}		return time.Duration(seconds * float64(time.Second))	}	if when, err := http.ParseTime(header); err == nil {		delta := when.Sub(now)		if delta < 0 {			return 0		}		return delta	}	return 0}