Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pkg/runtime/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ func (c *Client) runAgentWithAgentName(ctx context.Context, sessionID, agent, ag
defer resp.Body.Close()

scanner := bufio.NewScanner(resp.Body)
// A single SSE line can carry a large tool response; raise the cap
// above bufio's 64 KiB default so an oversized line does not silently
// truncate the stream (bufio.ErrTooLong).
scanner.Buffer(make([]byte, 0, bufio.MaxScanTokenSize), maxSSELineBytes)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 || line[0] == ':' {
Expand Down Expand Up @@ -438,7 +442,12 @@ func (c *Client) runAgentWithAgentName(ctx context.Context, sessionID, agent, ag
eventChan <- e
}

// Surface a read failure (e.g. an over-long line) instead of ending
// the stream silently — otherwise the run appears to stop with no
// error after the last event that fit.
if err := scanner.Err(); err != nil {
slog.DebugContext(ctx, "event", "scanner_error", err)
eventChan <- Error(fmt.Sprintf("reading event stream: %v", err))
return
}
}()
Expand Down Expand Up @@ -541,6 +550,10 @@ func (c *Client) StreamSessionEvents(ctx context.Context, sessionID string) (<-c
defer resp.Body.Close()

scanner := bufio.NewScanner(resp.Body)
// A single SSE line can carry a large tool response; raise the cap
// above bufio's 64 KiB default so an oversized line does not silently
// truncate the stream (bufio.ErrTooLong).
scanner.Buffer(make([]byte, 0, bufio.MaxScanTokenSize), maxSSELineBytes)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 || line[0] == ':' {
Expand Down Expand Up @@ -579,8 +592,12 @@ func (c *Client) StreamSessionEvents(ctx context.Context, sessionID string) (<-c
eventChan <- e
}

// Surface a read failure (e.g. an over-long line) instead of ending
// the stream silently — otherwise the run appears to stop with no
// error after the last event that fit.
if err := scanner.Err(); err != nil {
slog.DebugContext(ctx, "scanner error", "error", err)
eventChan <- Error(fmt.Sprintf("reading event stream: %v", err))
}
}()

Expand Down
11 changes: 11 additions & 0 deletions pkg/runtime/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import "time"
// so the buffer size is set in exactly one place.
const defaultEventChannelCapacity = 128

// maxSSELineBytes bounds a single Server-Sent-Events line the client SSE
// reader will accept. Each event is delivered as one `data: {json}` line, and
// a tool result can be large (e.g. a big tool-response payload), so the default
// bufio.Scanner cap of 64 KiB (bufio.MaxScanTokenSize) truncates the stream:
// the oversized line trips bufio.ErrTooLong, the scan ends, and the run appears
// to stop silently after the preceding event. The reader raises its buffer to
// this ceiling and surfaces any remaining scanner error as an Error event
// rather than closing the stream without a trace. Sized to comfortably hold a
// large tool response while still bounding per-line memory.
const maxSSELineBytes = 16 * 1024 * 1024

// defaultMaxOverflowCompactions caps the number of consecutive
// context-overflow auto-compactions that the run loop will attempt before
// giving up and surfacing the error to the caller. The runtime's
Expand Down
Loading