@@ -3,13 +3,16 @@ package executor
33import (
44 "bufio"
55 "bytes"
6+ "compress/flate"
7+ "compress/gzip"
68 "context"
79 "fmt"
810 "io"
911 "net/http"
1012 "strings"
1113 "time"
1214
15+ "github.com/andybalholm/brotli"
1316 "github.com/klauspost/compress/zstd"
1417 claudeauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude"
1518 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
@@ -89,31 +92,31 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
8992 recordAPIResponseError (ctx , e .cfg , err )
9093 return resp , err
9194 }
92- defer func () {
93- if errClose := httpResp .Body .Close (); errClose != nil {
94- log .Errorf ("response body close error: %v" , errClose )
95- }
96- }()
9795 recordAPIResponseMetadata (ctx , e .cfg , httpResp .StatusCode , httpResp .Header .Clone ())
9896 if httpResp .StatusCode < 200 || httpResp .StatusCode >= 300 {
9997 b , _ := io .ReadAll (httpResp .Body )
10098 appendAPIResponseChunk (ctx , e .cfg , b )
10199 log .Debugf ("request error, error status: %d, error body: %s" , httpResp .StatusCode , string (b ))
102100 err = statusErr {code : httpResp .StatusCode , msg : string (b )}
101+ if errClose := httpResp .Body .Close (); errClose != nil {
102+ log .Errorf ("response body close error: %v" , errClose )
103+ }
103104 return resp , err
104105 }
105- reader := io .Reader (httpResp .Body )
106- var decoder * zstd.Decoder
107- if hasZSTDEcoding (httpResp .Header .Get ("Content-Encoding" )) {
108- decoder , err = zstd .NewReader (httpResp .Body )
109- if err != nil {
110- recordAPIResponseError (ctx , e .cfg , err )
111- return resp , fmt .Errorf ("failed to initialize zstd decoder: %w" , err )
106+ decodedBody , err := decodeResponseBody (httpResp .Body , httpResp .Header .Get ("Content-Encoding" ))
107+ if err != nil {
108+ recordAPIResponseError (ctx , e .cfg , err )
109+ if errClose := httpResp .Body .Close (); errClose != nil {
110+ log .Errorf ("response body close error: %v" , errClose )
112111 }
113- reader = decoder
114- defer decoder .Close ()
112+ return resp , err
115113 }
116- data , err := io .ReadAll (reader )
114+ defer func () {
115+ if errClose := decodedBody .Close (); errClose != nil {
116+ log .Errorf ("response body close error: %v" , errClose )
117+ }
118+ }()
119+ data , err := io .ReadAll (decodedBody )
117120 if err != nil {
118121 recordAPIResponseError (ctx , e .cfg , err )
119122 return resp , err
@@ -192,19 +195,27 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
192195 err = statusErr {code : httpResp .StatusCode , msg : string (b )}
193196 return nil , err
194197 }
198+ decodedBody , err := decodeResponseBody (httpResp .Body , httpResp .Header .Get ("Content-Encoding" ))
199+ if err != nil {
200+ recordAPIResponseError (ctx , e .cfg , err )
201+ if errClose := httpResp .Body .Close (); errClose != nil {
202+ log .Errorf ("response body close error: %v" , errClose )
203+ }
204+ return nil , err
205+ }
195206 out := make (chan cliproxyexecutor.StreamChunk )
196207 stream = out
197208 go func () {
198209 defer close (out )
199210 defer func () {
200- if errClose := httpResp . Body .Close (); errClose != nil {
211+ if errClose := decodedBody .Close (); errClose != nil {
201212 log .Errorf ("response body close error: %v" , errClose )
202213 }
203214 }()
204215
205216 // If from == to (Claude → Claude), directly forward the SSE stream without translation
206217 if from == to {
207- scanner := bufio .NewScanner (httpResp . Body )
218+ scanner := bufio .NewScanner (decodedBody )
208219 buf := make ([]byte , 20_971_520 )
209220 scanner .Buffer (buf , 20_971_520 )
210221 for scanner .Scan () {
@@ -228,7 +239,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
228239 }
229240
230241 // For other formats, use translation
231- scanner := bufio .NewScanner (httpResp . Body )
242+ scanner := bufio .NewScanner (decodedBody )
232243 buf := make ([]byte , 20_971_520 )
233244 scanner .Buffer (buf , 20_971_520 )
234245 var param any
@@ -304,29 +315,29 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
304315 recordAPIResponseError (ctx , e .cfg , err )
305316 return cliproxyexecutor.Response {}, err
306317 }
307- defer func () {
308- if errClose := resp .Body .Close (); errClose != nil {
309- log .Errorf ("response body close error: %v" , errClose )
310- }
311- }()
312318 recordAPIResponseMetadata (ctx , e .cfg , resp .StatusCode , resp .Header .Clone ())
313319 if resp .StatusCode < 200 || resp .StatusCode >= 300 {
314320 b , _ := io .ReadAll (resp .Body )
315321 appendAPIResponseChunk (ctx , e .cfg , b )
322+ if errClose := resp .Body .Close (); errClose != nil {
323+ log .Errorf ("response body close error: %v" , errClose )
324+ }
316325 return cliproxyexecutor.Response {}, statusErr {code : resp .StatusCode , msg : string (b )}
317326 }
318- reader := io .Reader (resp .Body )
319- var decoder * zstd.Decoder
320- if hasZSTDEcoding (resp .Header .Get ("Content-Encoding" )) {
321- decoder , err = zstd .NewReader (resp .Body )
322- if err != nil {
323- recordAPIResponseError (ctx , e .cfg , err )
324- return cliproxyexecutor.Response {}, fmt .Errorf ("failed to initialize zstd decoder: %w" , err )
327+ decodedBody , err := decodeResponseBody (resp .Body , resp .Header .Get ("Content-Encoding" ))
328+ if err != nil {
329+ recordAPIResponseError (ctx , e .cfg , err )
330+ if errClose := resp .Body .Close (); errClose != nil {
331+ log .Errorf ("response body close error: %v" , errClose )
325332 }
326- reader = decoder
327- defer decoder .Close ()
333+ return cliproxyexecutor.Response {}, err
328334 }
329- data , err := io .ReadAll (reader )
335+ defer func () {
336+ if errClose := decodedBody .Close (); errClose != nil {
337+ log .Errorf ("response body close error: %v" , errClose )
338+ }
339+ }()
340+ data , err := io .ReadAll (decodedBody )
330341 if err != nil {
331342 recordAPIResponseError (ctx , e .cfg , err )
332343 return cliproxyexecutor.Response {}, err
@@ -419,7 +430,7 @@ func (e *ClaudeExecutor) resolveClaudeConfig(auth *cliproxyauth.Auth) *config.Cl
419430 continue
420431 }
421432 if attrKey != "" && strings .EqualFold (cfgKey , attrKey ) {
422- if attrBase == "" || cfgBase == "" || strings .EqualFold (cfgBase , attrBase ) {
433+ if cfgBase == "" || strings .EqualFold (cfgBase , attrBase ) {
423434 return entry
424435 }
425436 }
@@ -438,17 +449,84 @@ func (e *ClaudeExecutor) resolveClaudeConfig(auth *cliproxyauth.Auth) *config.Cl
438449 return nil
439450}
440451
441- func hasZSTDEcoding (contentEncoding string ) bool {
452+ type compositeReadCloser struct {
453+ io.Reader
454+ closers []func () error
455+ }
456+
457+ func (c * compositeReadCloser ) Close () error {
458+ var firstErr error
459+ for i := range c .closers {
460+ if c .closers [i ] == nil {
461+ continue
462+ }
463+ if err := c .closers [i ](); err != nil && firstErr == nil {
464+ firstErr = err
465+ }
466+ }
467+ return firstErr
468+ }
469+
470+ func decodeResponseBody (body io.ReadCloser , contentEncoding string ) (io.ReadCloser , error ) {
471+ if body == nil {
472+ return nil , fmt .Errorf ("response body is nil" )
473+ }
442474 if contentEncoding == "" {
443- return false
475+ return body , nil
444476 }
445- parts := strings .Split (contentEncoding , "," )
446- for i := range parts {
447- if strings .EqualFold (strings .TrimSpace (parts [i ]), "zstd" ) {
448- return true
477+ encodings := strings .Split (contentEncoding , "," )
478+ for _ , raw := range encodings {
479+ encoding := strings .TrimSpace (strings .ToLower (raw ))
480+ switch encoding {
481+ case "" , "identity" :
482+ continue
483+ case "gzip" :
484+ gzipReader , err := gzip .NewReader (body )
485+ if err != nil {
486+ _ = body .Close ()
487+ return nil , fmt .Errorf ("failed to create gzip reader: %w" , err )
488+ }
489+ return & compositeReadCloser {
490+ Reader : gzipReader ,
491+ closers : []func () error {
492+ gzipReader .Close ,
493+ func () error { return body .Close () },
494+ },
495+ }, nil
496+ case "deflate" :
497+ deflateReader := flate .NewReader (body )
498+ return & compositeReadCloser {
499+ Reader : deflateReader ,
500+ closers : []func () error {
501+ deflateReader .Close ,
502+ func () error { return body .Close () },
503+ },
504+ }, nil
505+ case "br" :
506+ return & compositeReadCloser {
507+ Reader : brotli .NewReader (body ),
508+ closers : []func () error {
509+ func () error { return body .Close () },
510+ },
511+ }, nil
512+ case "zstd" :
513+ decoder , err := zstd .NewReader (body )
514+ if err != nil {
515+ _ = body .Close ()
516+ return nil , fmt .Errorf ("failed to create zstd reader: %w" , err )
517+ }
518+ return & compositeReadCloser {
519+ Reader : decoder ,
520+ closers : []func () error {
521+ func () error { decoder .Close (); return nil },
522+ func () error { return body .Close () },
523+ },
524+ }, nil
525+ default :
526+ continue
449527 }
450528 }
451- return false
529+ return body , nil
452530}
453531
454532func applyClaudeHeaders (r * http.Request , apiKey string , stream bool ) {
0 commit comments