Skip to content

Commit 61e1e90

Browse files
authored
Merge pull request #1967 from dgageot/board/open-router-4c39baf4
Disable automatic gzip compression to fix SSE streaming
2 parents 7caf905 + 292ec9d commit 61e1e90

2 files changed

Lines changed: 57 additions & 51 deletions

File tree

pkg/httpclient/client.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@ func NewHTTPClient(opts ...Opt) *http.Client {
2929
// Enforce a consistent User-Agent header
3030
httpOptions.Header.Set("User-Agent", fmt.Sprintf("Cagent/%s (%s; %s)", version.Version, runtime.GOOS, runtime.GOARCH))
3131

32+
// Disable automatic gzip: Go's default transport transparently compresses
33+
// and decompresses responses, which is incompatible with SSE streaming.
34+
// See https://github.com/docker/docker-agent/issues/1956
35+
rt := newTransport()
36+
3237
return &http.Client{
3338
Transport: &userAgentTransport{
3439
httpOptions: httpOptions,
35-
rt: http.DefaultTransport,
40+
rt: rt,
3641
},
3742
}
3843
}
@@ -90,6 +95,17 @@ func WithQuery(query url.Values) Opt {
9095
}
9196
}
9297

98+
// newTransport returns an HTTP transport with automatic gzip compression disabled.
99+
func newTransport() http.RoundTripper {
100+
t, ok := http.DefaultTransport.(*http.Transport)
101+
if !ok {
102+
return http.DefaultTransport
103+
}
104+
transport := t.Clone()
105+
transport.DisableCompression = true
106+
return transport
107+
}
108+
93109
type userAgentTransport struct {
94110
httpOptions HTTPOptions
95111
rt http.RoundTripper

pkg/httpclient/client_test.go

Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,89 +9,79 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
func TestWithModelName(t *testing.T) {
12+
func TestHeaders(t *testing.T) {
1313
t.Parallel()
1414

1515
tests := []struct {
16-
name string
17-
modelName string
18-
wantSet bool
16+
name string
17+
opts []Opt
18+
wantHeader string
19+
wantValue string
1920
}{
2021
{
21-
name: "sets header when name is provided",
22-
modelName: "my-fast-model",
23-
wantSet: true,
22+
name: "WithModel sets X-Cagent-Model",
23+
opts: []Opt{WithModel("gpt-4o")},
24+
wantHeader: "X-Cagent-Model",
25+
wantValue: "gpt-4o",
2426
},
2527
{
26-
name: "skips header when name is empty",
27-
modelName: "",
28-
wantSet: false,
28+
name: "WithModelName sets X-Cagent-Model-Name",
29+
opts: []Opt{WithModelName("my-fast-model")},
30+
wantHeader: "X-Cagent-Model-Name",
31+
wantValue: "my-fast-model",
32+
},
33+
{
34+
name: "WithModelName skips header when empty",
35+
opts: []Opt{WithModelName("")},
36+
wantHeader: "X-Cagent-Model-Name",
37+
wantValue: "",
38+
},
39+
{
40+
name: "WithProvider sets X-Cagent-Provider",
41+
opts: []Opt{WithProvider("openai")},
42+
wantHeader: "X-Cagent-Provider",
43+
wantValue: "openai",
44+
},
45+
{
46+
name: "compression is disabled to support SSE streaming",
47+
wantHeader: "Accept-Encoding",
48+
wantValue: "",
2949
},
3050
}
3151

3252
for _, tt := range tests {
3353
t.Run(tt.name, func(t *testing.T) {
3454
t.Parallel()
3555

36-
var capturedHeaders http.Header
37-
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
38-
capturedHeaders = r.Header
39-
}))
40-
defer srv.Close()
41-
42-
client := NewHTTPClient(WithModelName(tt.modelName))
43-
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
44-
require.NoError(t, err)
56+
headers := doRequest(t, tt.opts...)
4557

46-
resp, err := client.Do(req)
47-
require.NoError(t, err)
48-
defer func() { _ = resp.Body.Close() }()
49-
50-
if tt.wantSet {
51-
assert.Equal(t, tt.modelName, capturedHeaders.Get("X-Cagent-Model-Name"))
58+
if tt.wantValue != "" {
59+
assert.Equal(t, tt.wantValue, headers.Get(tt.wantHeader))
5260
} else {
53-
assert.Empty(t, capturedHeaders.Get("X-Cagent-Model-Name"))
61+
assert.Empty(t, headers.Get(tt.wantHeader))
5462
}
5563
})
5664
}
5765
}
5866

59-
func TestWithModel(t *testing.T) {
60-
t.Parallel()
61-
62-
var capturedHeaders http.Header
63-
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
64-
capturedHeaders = r.Header
65-
}))
66-
defer srv.Close()
67-
68-
client := NewHTTPClient(WithModel("gpt-4o"))
69-
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
70-
require.NoError(t, err)
71-
72-
resp, err := client.Do(req)
73-
require.NoError(t, err)
74-
defer func() { _ = resp.Body.Close() }()
75-
76-
assert.Equal(t, "gpt-4o", capturedHeaders.Get("X-Cagent-Model"))
77-
}
78-
79-
func TestWithProvider(t *testing.T) {
80-
t.Parallel()
67+
// doRequest creates an HTTP client with the given options, sends a GET request
68+
// to a test server, and returns the headers the server received.
69+
func doRequest(t *testing.T, opts ...Opt) http.Header {
70+
t.Helper()
8171

8272
var capturedHeaders http.Header
8373
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
8474
capturedHeaders = r.Header
8575
}))
8676
defer srv.Close()
8777

88-
client := NewHTTPClient(WithProvider("openai"))
78+
client := NewHTTPClient(opts...)
8979
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
9080
require.NoError(t, err)
9181

9282
resp, err := client.Do(req)
9383
require.NoError(t, err)
9484
defer func() { _ = resp.Body.Close() }()
9585

96-
assert.Equal(t, "openai", capturedHeaders.Get("X-Cagent-Provider"))
86+
return capturedHeaders
9787
}

0 commit comments

Comments
 (0)