mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-07 00:24:17 +08:00
121 lines
4.0 KiB
Go
121 lines
4.0 KiB
Go
package executor
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
|
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
type claudeFastFallbackOptions struct {
|
|
auth *cliproxyauth.Auth
|
|
apiKey string
|
|
stream bool
|
|
extraBetas []string
|
|
body []byte
|
|
fallbackBilling string
|
|
cchSigning bool
|
|
incomingHeaders http.Header
|
|
confirmedNative bool
|
|
sessionID string
|
|
allowEntitlementFallback bool
|
|
}
|
|
|
|
func (e *ClaudeExecutor) retryClaudeFastModeRefusal(
|
|
ctxReq *http.Request,
|
|
client *http.Client,
|
|
initialResp *http.Response,
|
|
options claudeFastFallbackOptions,
|
|
) (*http.Response, []byte, bool, error) {
|
|
if initialResp == nil || ctxReq == nil || client == nil || !options.allowEntitlementFallback || initialResp.StatusCode != http.StatusTooManyRequests {
|
|
return initialResp, options.body, false, nil
|
|
}
|
|
|
|
errorBody, errDecode := decodeResponseBody(initialResp.Body, claudeResponseContentEncoding(initialResp.Header))
|
|
if errDecode != nil {
|
|
return nil, options.body, false, fmt.Errorf("decode Claude Fast refusal: %w", errDecode)
|
|
}
|
|
body, errRead := io.ReadAll(errorBody)
|
|
if errClose := errorBody.Close(); errClose != nil {
|
|
log.Errorf("response body close error: %v", errClose)
|
|
}
|
|
if errRead != nil {
|
|
return nil, options.body, false, fmt.Errorf("read Claude Fast refusal: %w", errRead)
|
|
}
|
|
if !claudeBodyIndicatesFastModeCredits(body) {
|
|
initialResp.Body = io.NopCloser(bytes.NewReader(body))
|
|
initialResp.ContentLength = int64(len(body))
|
|
initialResp.Header.Del("Content-Encoding")
|
|
initialResp.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
|
return initialResp, options.body, false, nil
|
|
}
|
|
|
|
helps.AppendAPIResponseChunk(ctxReq.Context(), e.cfg, body)
|
|
fallbackBody, errDelete := sjson.DeleteBytes(options.body, "speed")
|
|
if errDelete != nil {
|
|
return nil, options.body, false, fmt.Errorf("remove Claude Fast speed: %w", errDelete)
|
|
}
|
|
if options.cchSigning {
|
|
var errCCH error
|
|
fallbackBody, errCCH = finalizeAnthropicMessagesBodyCCH(fallbackBody, options.fallbackBilling)
|
|
if errCCH != nil {
|
|
return nil, options.body, false, fmt.Errorf("re-finalize Claude CCH for Fast fallback: %w", errCCH)
|
|
}
|
|
}
|
|
|
|
fallbackReq, errRequest := http.NewRequestWithContext(ctxReq.Context(), http.MethodPost, ctxReq.URL.String(), bytes.NewReader(fallbackBody))
|
|
if errRequest != nil {
|
|
return nil, options.body, false, fmt.Errorf("create Claude Fast fallback request: %w", errRequest)
|
|
}
|
|
fallbackBetas := append([]string(nil), options.extraBetas...)
|
|
fallbackBetas = append(fallbackBetas, claudeFastModeBeta)
|
|
if errHeaders := applyClaudeHeaders(
|
|
fallbackReq,
|
|
options.auth,
|
|
options.apiKey,
|
|
options.stream,
|
|
fallbackBetas,
|
|
fallbackBody,
|
|
e.cfg,
|
|
options.incomingHeaders,
|
|
options.confirmedNative,
|
|
options.sessionID,
|
|
); errHeaders != nil {
|
|
return nil, options.body, false, errHeaders
|
|
}
|
|
|
|
authID, authLabel, authType, authValue := claudeAuthLogIdentity(options.auth)
|
|
helps.RecordAPIRequest(ctxReq.Context(), e.cfg, helps.UpstreamRequestLog{
|
|
URL: fallbackReq.URL.String(),
|
|
Method: http.MethodPost,
|
|
Headers: fallbackReq.Header.Clone(),
|
|
Body: fallbackBody,
|
|
Provider: e.upstreamRequestLogProvider(),
|
|
AuthID: authID,
|
|
AuthLabel: authLabel,
|
|
AuthType: authType,
|
|
AuthValue: authValue,
|
|
})
|
|
|
|
fallbackResp, errDo := doClaudeUpstreamRequest(client, fallbackReq)
|
|
if errDo != nil {
|
|
helps.RecordAPIResponseError(ctxReq.Context(), e.cfg, errDo)
|
|
return nil, fallbackBody, true, errDo
|
|
}
|
|
helps.RecordAPIResponseMetadata(ctxReq.Context(), e.cfg, fallbackResp.StatusCode, fallbackResp.Header.Clone())
|
|
return fallbackResp, fallbackBody, true, 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
|
|
}
|