Skip to content

Commit 85b4d33

Browse files
committed
Merge branch 'main' into feat-sdk-integration
# Conflicts: # lib/httpapi/server.go # lib/httpapi/server_test.go
2 parents 62caf2f + d0b5ad0 commit 85b4d33

File tree

6 files changed

+71
-3
lines changed

6 files changed

+71
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v0.7.1
4+
5+
### Fixes
6+
7+
- Adds headers to prevent proxies buffering SSE connections
8+
39
## v0.7.0
410

511
### Features

chat/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@
4444
"start": "next start",
4545
"storybook": "storybook dev -p 6006"
4646
},
47-
"version": "0.7.0"
47+
"version": "0.7.1"
4848
}

internal/version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
package version
44

5-
var Version = "0.7.0"
5+
var Version = "0.7.1"

lib/httpapi/server.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,19 @@ func hostAuthorizationMiddleware(allowedHosts []string, badHostHandler http.Hand
254254
}
255255
}
256256

257+
// sseMiddleware creates middleware that prevents proxy buffering for SSE endpoints
258+
func sseMiddleware(ctx huma.Context, next func(huma.Context)) {
259+
// Disable proxy buffering for SSE endpoints
260+
ctx.SetHeader("Cache-Control", "no-cache, no-store, must-revalidate")
261+
ctx.SetHeader("Pragma", "no-cache")
262+
ctx.SetHeader("Expires", "0")
263+
ctx.SetHeader("X-Accel-Buffering", "no") // nginx
264+
ctx.SetHeader("X-Proxy-Buffering", "no") // generic proxy
265+
ctx.SetHeader("Connection", "keep-alive")
266+
267+
next(ctx)
268+
}
269+
257270
// registerRoutes sets up all API endpoints
258271
func (s *Server) registerRoutes() {
259272
// GET /status endpoint
@@ -278,6 +291,7 @@ func (s *Server) registerRoutes() {
278291
Path: "/events",
279292
Summary: "Subscribe to events",
280293
Description: "The events are sent as Server-Sent Events (SSE). Initially, the endpoint returns a list of events needed to reconstruct the current state of the conversation and the agent's status. After that, it only returns events that have occurred since the last event was sent.\n\nNote: When an agent is running, the last message in the conversation history is updated frequently, and the endpoint sends a new message update event each time.",
294+
Middlewares: []func(huma.Context, func(huma.Context)){sseMiddleware},
281295
}, map[string]any{
282296
// Mapping of event type name to Go struct for that event.
283297
"message_update": types.MessageUpdateBody{},
@@ -290,6 +304,7 @@ func (s *Server) registerRoutes() {
290304
Path: "/internal/screen",
291305
Summary: "Subscribe to screen",
292306
Hidden: true,
307+
Middlewares: []func(huma.Context, func(huma.Context)){sseMiddleware},
293308
}, map[string]any{
294309
"screen": types.ScreenUpdateBody{},
295310
}, s.AgentHandler.SubscribeConversations)

lib/httpapi/server_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,3 +631,50 @@ func TestServer_CORSPreflightOrigins(t *testing.T) {
631631
})
632632
}
633633
}
634+
635+
func TestServer_SSEMiddleware_Events(t *testing.T) {
636+
t.Parallel()
637+
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
638+
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
639+
AgentType: msgfmt.AgentTypeClaude,
640+
Process: nil,
641+
Port: 0,
642+
ChatBasePath: "/chat",
643+
AllowedHosts: []string{"*"},
644+
AllowedOrigins: []string{"*"},
645+
})
646+
require.NoError(t, err)
647+
tsServer := httptest.NewServer(srv.Handler())
648+
t.Cleanup(tsServer.Close)
649+
650+
t.Run("events", func(t *testing.T) {
651+
t.Parallel()
652+
resp, err := tsServer.Client().Get(tsServer.URL + "/events")
653+
require.NoError(t, err)
654+
t.Cleanup(func() {
655+
_ = resp.Body.Close()
656+
})
657+
assertSSEHeaders(t, resp)
658+
})
659+
660+
t.Run("internal/screen", func(t *testing.T) {
661+
t.Parallel()
662+
663+
resp, err := tsServer.Client().Get(tsServer.URL + "/internal/screen")
664+
require.NoError(t, err)
665+
t.Cleanup(func() {
666+
_ = resp.Body.Close()
667+
})
668+
assertSSEHeaders(t, resp)
669+
})
670+
}
671+
672+
func assertSSEHeaders(t testing.TB, resp *http.Response) {
673+
t.Helper()
674+
assert.Equal(t, "no-cache, no-store, must-revalidate", resp.Header.Get("Cache-Control"))
675+
assert.Equal(t, "no-cache", resp.Header.Get("Pragma"))
676+
assert.Equal(t, "0", resp.Header.Get("Expires"))
677+
assert.Equal(t, "no", resp.Header.Get("X-Accel-Buffering"))
678+
assert.Equal(t, "no", resp.Header.Get("X-Proxy-Buffering"))
679+
assert.Equal(t, "keep-alive", resp.Header.Get("Connection"))
680+
}

openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@
307307
"info": {
308308
"description": "HTTP API for Claude Code, Goose, and Aider.\n\nhttps://github.com/coder/agentapi",
309309
"title": "AgentAPI",
310-
"version": "0.7.0"
310+
"version": "0.7.1"
311311
},
312312
"openapi": "3.1.0",
313313
"paths": {

0 commit comments

Comments
 (0)