package executor import ( "bytes" "fmt" "net/http" "strings" 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. type claudeFastRequestError struct { cause error status int } 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 { return e != nil } // 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 } 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 { return e != nil } func wrapClaudeFastRequestError(fastRequest bool, status int, err error) error { if err == nil || !fastRequest { return err } return &claudeFastRequestError{cause: err, status: status} } 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") return &claudeFastDirectResponseError{response: &cliproxyexecutor.RequestTerminatedError{ HTTPStatus: resp.StatusCode, Header: headers, Body: bytes.Clone(body), }} } 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 }