Skip to content

Add line and Server-Sent Events readers for streaming responses#18

Merged
Mako88 merged 3 commits into
mainfrom
sse-helper
May 31, 2026
Merged

Add line and Server-Sent Events readers for streaming responses#18
Mako88 merged 3 commits into
mainfrom
sse-helper

Conversation

@Mako88

@Mako88 Mako88 commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds two streaming-consumption methods on ISimpleStreamResponse, so consumers don't have to hand-roll SSE framing on top of the raw Body stream:

  • ReadLinesAsync(ct) — reads the body as text lines (CR/LF/CRLF), encoding from the Content-Type charset (UTF-8 default), cancellation observed mid-read.
  • ReadServerSentEventsAsync(ct) — parses the text/event-stream wire format per the WHATWG SSE spec: blank-line dispatch, multiple data: lines joined with newlines, comment/keep-alive (:) lines skipped, event/id/retry fields, and id carrying forward. Returns ServerSentEvent frames (Data, EventType, Id, Retry).

Design

  • Interface methods, not extension methods — declared on ISimpleStreamResponse, implemented in SimpleStreamResponse, so callers can mock them (a test asserts this).
  • SSE framing is a web standard (the same format browser EventSource consumes), so it belongs in the library. Application-specific conventions are deliberately left to the caller:
    • No [DONE] special-casing — it passes through as ordinary Data (OpenAI's convention; Anthropic uses event: message_stop). The caller's if decides.
    • No payload deserializationData is a string; the caller deserializes with whatever serializer/type it wants.
using var response = await client.MakeStreamRequest(request, ct);
await foreach (var sse in response.ReadServerSentEventsAsync(ct))
{
    if (sse.Data == "[DONE]") break;            // caller's convention
    var chunk = client.Serializer.Deserialize<MyChunk>(sse.Data);
}

Notes

  • IAsyncEnumerable works on netstandard2.0 via Microsoft.Bcl.AsyncInterfaces, already pulled in transitively by System.Text.Json — no new dependency. Added <LangVersion>latest</LangVersion> so the netstandard2.0 target (C# 7.3 by default) can compile async streams.
  • Composes with the existing CancellationAwareStream, so cancellation is honored mid-read.
  • Backward-compatible, additive only → minor bump.

Tests

14 new tests (parser units via in-memory streams, a WireMock end-to-end through MakeStreamRequest, and a mockability test): line splitting, CRLF, multi-data join, no-space-after-colon, comment skipping, event/id/retry, id-carries-forward, reset-between-events, [DONE] pass-through, event-without-data not dispatched, incomplete trailing event not dispatched, pre-cancelled token throws, mockable interface. net10.0 and net48: 110 each.

🤖 Generated with Claude Code

Add two IAsyncEnumerable members on ISimpleStreamResponse (implemented in
SimpleStreamResponse), so callers can mock them:
- ReadLinesAsync: reads the body as text lines (CR/LF/CRLF), encoding from the
  Content-Type charset (UTF-8 default), cancellation observed mid-read.
- ReadServerSentEventsAsync: parses the text/event-stream wire format per the WHATWG
  SSE spec (blank-line dispatch, multi-data join, comment/keep-alive skipping,
  event/id/retry fields, id carries forward), returning ServerSentEvent frames.

Both are generic to the SSE standard - application conventions (OpenAI's [DONE]
sentinel, deserializing the data payload) are deliberately left to the caller, keeping
SimpleHttpClient general-purpose. Made these interface methods (not extension methods)
so they can be mocked; a test asserts mockability. IAsyncEnumerable works on
netstandard2.0 via the Microsoft.Bcl.AsyncInterfaces dependency System.Text.Json
already brings in; added <LangVersion>latest</LangVersion> so the netstandard2.0 target
(C# 7.3 by default) can compile async streams.

Adds ServerSentEvent model, 14 tests (parser units + WireMock end-to-end + mockability),
and README docs. net10.0 and net48: 110 tests each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mako88 and others added 2 commits May 30, 2026 23:25
The intro sentence and NuGet <Description> predated the streaming
support, so they described only the buffered request/response model.
Mention the line/SSE streaming helpers, add the SSE subsection to the
README table of contents, and fix grammar in the package description.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the three benefits parallel noun phrases joined by a single 'and' in both the README intro and the NuGet description, fixing the awkward 'No extension methods, included interfaces allow for...' reading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Mako88 Mako88 merged commit cfff4ac into main May 31, 2026
2 checks passed
@Mako88 Mako88 deleted the sse-helper branch May 31, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant