-
Notifications
You must be signed in to change notification settings - Fork 369
Add implementing-server-sent-events skill #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,207 @@ | ||||||||||||||
| --- | ||||||||||||||
| name: implementing-server-sent-events | ||||||||||||||
| description: Implement Server-Sent Events (SSE) endpoints in ASP.NET Core. Use when building real-time streaming from server to client without WebSockets. | ||||||||||||||
| --- | ||||||||||||||
|
|
||||||||||||||
| # Implementing Server-Sent Events (SSE) in ASP.NET Core | ||||||||||||||
|
|
||||||||||||||
| ## When to Use | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move when to use and when not to use entirely or largely into the description, which is the thing the ai reads when deciding whether or not to load. probably shouldn't have this section here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SSE endpoints hold long-lived HTTP connections. Without connection limits, an attacker (or misbehaving client) can exhaust server resources by opening many concurrent connections. Consider adding a note about:
This is especially relevant for a skill doc since agents will copy the pattern as-is without thinking about production hardening.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (says AI) |
||||||||||||||
| - Server-to-client real-time push (notifications, live updates, streaming progress) | ||||||||||||||
| - When you DON'T need bidirectional communication (use WebSockets for that) | ||||||||||||||
| - SSE is simpler than WebSockets and works over standard HTTP | ||||||||||||||
| - Automatic reconnection built into EventSource browser API | ||||||||||||||
|
|
||||||||||||||
| ## When Not to Use | ||||||||||||||
| - Bidirectional communication needed → use WebSockets | ||||||||||||||
| - Binary data streaming → use WebSockets or gRPC streaming | ||||||||||||||
| - Need more than 6 concurrent connections per domain in HTTP/1.1 → use HTTP/2 | ||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+14
to
+18
|
||||||||||||||
| ## Inputs | ||||||||||||||
|
|
||||||||||||||
| | Input | Required | Description | | ||||||||||||||
| |-------|----------|-------------| | ||||||||||||||
| | Event data source | Yes | The data to stream (IAsyncEnumerable, Channel, timer, etc.) | | ||||||||||||||
| | Event types | No | Named event types for `event:` field | | ||||||||||||||
| | Client reconnection | No | Whether to support Last-Event-ID reconnection | | ||||||||||||||
|
|
||||||||||||||
| ## Workflow | ||||||||||||||
|
|
||||||||||||||
| ### Step 1: CRITICAL — There Is No Built-In MapSSE() or MapServerSentEvents() | ||||||||||||||
|
|
||||||||||||||
| ASP.NET Core has NO built-in SSE endpoint helper. You must manually write the SSE protocol. | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong. We have Most of this Skill should probably be rewritten to use |
||||||||||||||
|
|
||||||||||||||
| ```csharp | ||||||||||||||
| // COMMON MISTAKE: Trying to use a non-existent API | ||||||||||||||
| // app.MapSSE("/events", ...); // DOES NOT EXIST | ||||||||||||||
| // app.MapServerSentEvents("/events", ...); // DOES NOT EXIST | ||||||||||||||
| // app.UseServerSentEvents(); // DOES NOT EXIST | ||||||||||||||
|
|
||||||||||||||
| // CORRECT: Use a standard minimal API endpoint with manual SSE protocol | ||||||||||||||
| app.MapGet("/events", async (HttpContext context, CancellationToken ct) => | ||||||||||||||
| { | ||||||||||||||
| // CRITICAL: Set these three headers for SSE | ||||||||||||||
| context.Response.Headers.ContentType = "text/event-stream"; | ||||||||||||||
| context.Response.Headers.CacheControl = "no-cache"; | ||||||||||||||
|
Comment on lines
+43
to
+44
|
||||||||||||||
| context.Response.Headers.ContentType = "text/event-stream"; | |
| context.Response.Headers.CacheControl = "no-cache"; | |
| context.Response.ContentType = "text/event-stream"; | |
| context.Response.Headers["Cache-Control"] = "no-cache"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should {message} be sanitized for newlines here and below. AI says - risk of injection attack
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StreamWriter.WriteLineAsync uses the platform newline by default (often \r\n), and this example also appends an extra \n, which can produce confusing line endings for SSE. Prefer writing explicit \n (or set writer.NewLine = "\n" and use WriteAsync) to keep the SSE framing unambiguous cross-platform.
| await writer.WriteLineAsync($"data: {msg}\n"); // Note: WriteLine adds one \n, we add one more | |
| await writer.WriteAsync($"data: {msg}\n\n"); // Explicit \n\n for SSE event framing |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same compile issue here: context.Response.Headers.ContentType / .CacheControl are not valid APIs on IHeaderDictionary. Update this snippet to use context.Response.ContentType / Headers["Cache-Control"] (or typed headers) so the example builds.
| context.Response.Headers.ContentType = "text/event-stream"; | |
| context.Response.Headers.CacheControl = "no-cache"; | |
| context.Response.ContentType = "text/event-stream"; | |
| context.Response.Headers["Cache-Control"] = "no-cache"; |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int.Parse(lastEventId) will throw if the client sends a non-integer Last-Event-ID header (or an out-of-range value), turning a reconnect into a 500. Use int.TryParse and fall back to a safe default (or return 400) to make the reconnection example robust.
| var startFrom = lastEventId != null ? int.Parse(lastEventId) + 1 : 0; | |
| var startFrom = 0; | |
| if (!string.IsNullOrEmpty(lastEventId) && int.TryParse(lastEventId, out var parsedId) && parsedId >= 0) | |
| { | |
| startFrom = parsedId + 1; | |
| } |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same header API issue in this snippet: context.Response.Headers.ContentType / .CacheControl won’t compile. Please update consistently across all examples (Step 5/6) to avoid copy/paste failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adding a note about CORS configuration. SSE endpoints are frequently consumed cross-origin via the browser EventSource API, which does not support custom headers. Without app.UseCors(...) or a CORS policy on the endpoint, cross-origin clients will silently fail. This is a common deployment surprise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(says AI)
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| scenarios: | ||
| - name: "Implement SSE notification endpoint in ASP.NET Core 8 minimal API" | ||
|
Comment on lines
+1
to
+2
|
||
| prompt: | | ||
| I need to implement a Server-Sent Events (SSE) endpoint in an ASP.NET Core 8 minimal API project. | ||
|
|
||
| The endpoint should be at GET /api/events/notifications and should: | ||
| 1. Stream real-time notifications to connected clients | ||
| 2. Support named event types (e.g., "message", "alert", "update") | ||
| 3. Include event IDs so clients can reconnect and resume from where they left off | ||
| 4. Handle client disconnection gracefully | ||
| 5. Set a 5-second retry interval for client reconnection | ||
| 6. Work correctly behind reverse proxies like nginx | ||
|
|
||
| The notification data should be JSON objects with properties: Id, Type, Message, Timestamp. | ||
|
|
||
| Use a simple background service that generates a notification every 2 seconds to simulate a real event source. Use Channel<T> to communicate between the background service and the SSE endpoint. | ||
|
|
||
| Create a complete working project with Program.cs and any needed service files. | ||
| rubric: | ||
| - "Uses manual SSE protocol (Response.WriteAsync with text/event-stream content type), NOT a non-existent MapSSE(), MapServerSentEvents(), or UseServerSentEvents() helper method" | ||
| - "Sets all required SSE headers: Content-Type text/event-stream, Cache-Control no-cache, and Connection keep-alive" | ||
| - "Uses correct SSE format with double newline (\\n\\n) to terminate each event, not single newline" | ||
| - "Calls Response.Body.FlushAsync() after each event write (or uses equivalent auto-flush mechanism) so client receives events immediately" | ||
| - "Uses HttpContext.RequestAborted or CancellationToken to detect client disconnection and handles OperationCanceledException gracefully" | ||
| - "Includes id: field in SSE events and reads Last-Event-ID header from request for reconnection support" | ||
| - "Sets retry: field to control client reconnection interval" | ||
| - "Sets X-Accel-Buffering: no header for reverse proxy compatibility" | ||
| - "Does NOT return Results.Ok() or any IResult after writing SSE events to the response body" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This SKILL.md is placed under
src/dotnet/skills/..., but the repository layout expects skills underplugins/<plugin>/skills/<skill-name>/SKILL.mdso they are packaged and discovered (e.g.plugins/dotnet/skills/implementing-server-sent-events/SKILL.md). As-is, the evaluation workflow that validates./plugins/dotnet/skillswill not discover or test this skill.