180 lines · 4.4 KB · Go
RawDownload
package executor import (	"bytes"	"errors"	"fmt"	"net/http"	"strings"	"time" 	"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"	cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"	cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor") // claudeFastRequestError marks a Fast request failure as request-scoped. Fast// errors must stop at the caller: they do not justify retrying another// credential or changing the selected credential's availability, unless the failure// is a genuine credential-level rate limit.type claudeFastRequestError struct {	cause      error	status     int	retryAfter *time.Duration} func (e *claudeFastRequestError) Error() string {	if e == nil || e.cause == nil {		return ""	}	return e.cause.Error()} func (e *claudeFastRequestError) Unwrap() error {	if e == nil {		return nil	}	return e.cause} func (e *claudeFastRequestError) StatusCode() int {	if e == nil || (e.status >= http.StatusOK && e.status < http.StatusMultipleChoices) {		return 0	}	return e.status} func (e *claudeFastRequestError) IsRequestScoped() bool {	if e == nil {		return false	}	if e.IsCredentialScoped() {		return false	}	return true} func (e *claudeFastRequestError) IsCredentialScoped() bool {	if e == nil {		return false	}	type credentialScopedProvider interface {		IsCredentialScoped() bool	}	var csp credentialScopedProvider	if errors.As(e.cause, &csp) && csp != nil {		return csp.IsCredentialScoped()	}	return false} func (e *claudeFastRequestError) RetryAfter() *time.Duration {	if e == nil {		return nil	}	return e.retryAfter} // claudeFastDirectResponseError carries an upstream HTTP error response through// the auth manager and protocol handlers without retrying or rebuilding its// status and JSON body.type claudeFastDirectResponseError struct {	response         *cliproxyexecutor.RequestTerminatedError	retryAfter       *time.Duration	credentialScoped bool} func (e *claudeFastDirectResponseError) Error() string {	if e == nil || e.response == nil {		return ""	}	return fmt.Sprintf("claude Fast upstream request failed with status %d", e.response.HTTPStatus)} func (e *claudeFastDirectResponseError) Unwrap() error {	if e == nil {		return nil	}	return e.response} func (e *claudeFastDirectResponseError) IsRequestScoped() bool {	if e == nil {		return false	}	if e.credentialScoped {		return false	}	return true} func (e *claudeFastDirectResponseError) IsCredentialScoped() bool {	if e == nil {		return false	}	return e.credentialScoped} func (e *claudeFastDirectResponseError) RetryAfter() *time.Duration {	if e == nil {		return nil	}	return e.retryAfter} func wrapClaudeFastRequestError(fastRequest bool, status int, err error) error {	if err == nil || !fastRequest {		return err	}	var retryAfter *time.Duration	if rap, ok := err.(interface{ RetryAfter() *time.Duration }); ok && rap != nil {		retryAfter = rap.RetryAfter()	}	return &claudeFastRequestError{cause: err, status: status, retryAfter: retryAfter}} func newClaudeFastDirectResponseError(resp *http.Response, body []byte) error {	if resp == nil {		return nil	}	headers := resp.Header.Clone()	// body has already been decoded. Do not forward stale representation or	// length headers that describe the compressed upstream bytes.	headers.Del("Content-Encoding")	headers.Del("Content-Length") 	var retryAfter *time.Duration	credentialScoped := false	if resp.StatusCode == http.StatusTooManyRequests {		retryAfter = helps.ParseClaudeRateLimitReset(resp.Header, time.Now())		if helps.ClaudeHeadersIndicateUnifiedRateLimitRejection(resp.Header) {			credentialScoped = true		}	} 	return &claudeFastDirectResponseError{		response: &cliproxyexecutor.RequestTerminatedError{			HTTPStatus: resp.StatusCode,			Header:     headers,			Body:       bytes.Clone(body),		},		retryAfter:       retryAfter,		credentialScoped: credentialScoped,	}} func claudeRequestIsFast(req *http.Request, body []byte) bool {	if req == nil {		return false	}	betas := strings.Join(req.Header.Values("Anthropic-Beta"), ",")	return claudeRequestUsesFastMode(body, claudeRequestedBetas(betas, nil))} func claudeAuthLogIdentity(auth *cliproxyauth.Auth) (id, label, authType, authValue string) {	if auth == nil {		return "", "", "", ""	}	authType, authValue = auth.AccountInfo()	return auth.ID, auth.Label, authType, authValue}