Commit 9484354
committed
fix(ssestream): skip events with empty data to prevent JSON unmarshal errors
Fix crash when parsing SSE streams that contain empty events from retry:
directives or comment lines.
## Problem
The eventStreamDecoder creates events with empty Data when it encounters
empty lines after non-data SSE fields (like "retry: 3000"). Stream.Next()
then attempts json.Unmarshal on empty bytes, causing "unexpected end of
JSON input" error. This breaks streaming with any SSE server using the
retry directive.
## Root Cause
Per the SSE specification [1], events are dispatched when empty lines are
encountered, regardless of whether data was present.
The spec states for empty line handling:
> "If the line is empty (a blank line) [Dispatch the event], as defined below."
And for the retry field:
> "If the field value consists of only ASCII digits, then interpret the field
> value as an integer in base ten, and set the event stream's reconnection time
> to that integer. Otherwise, ignore the field."
For empty data handling:
> "If the data buffer is an empty string, set the data buffer and the event
> type buffer to the empty string and return."
This means that a sequence like:
```
retry: 3000
```
Creates a valid empty event according to the spec. Servers commonly send this
for reconnection configuration, but the SDK assumed all events contain JSON data.
[1] https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
## Solution
Check if event.Data is empty before attempting to unmarshal. Skip empty
events and continue processing the stream. This maintains compatibility
with OpenAI API while supporting standard SSE practices per spec.
## Tests Added
- TestStream_EmptyEvents: Verifies handling of retry directive with empty event
- TestStream_OnlyRetryDirective: Tests stream with only retry (no data)
- TestStream_MultipleEmptyEvents: Tests multiple empty events interspersed with data
All tests pass:
```
=== RUN TestStream_EmptyEvents
--- PASS: TestStream_EmptyEvents (0.00s)
=== RUN TestStream_OnlyRetryDirective
--- PASS: TestStream_OnlyRetryDirective (0.00s)
=== RUN TestStream_MultipleEmptyEvents
--- PASS: TestStream_MultipleEmptyEvents (0.00s)
PASS
```
## Impact
- Enables compatibility with SSE servers using retry: directive (common practice)
- No breaking changes - only adds resilience to spec-compliant edge case
- Verified with streaming function calling through Anthropic API gateway
## Real-World Testing
Tested with Anthropic Claude 3.5 streaming API via AI Gateway:
- Before: "Stream error: unexpected end of JSON input"
- After: Successfully receives and processes all streaming chunks
Fixes stream crashes with "unexpected end of JSON input" when encountering
SSE streams with retry directives or comment lines.1 parent dae47f3 commit 9484354
2 files changed
+161
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
166 | 171 | | |
167 | 172 | | |
168 | 173 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
0 commit comments