Skip to content

Add implementing-server-sent-events skill#265

Open
mrsharm wants to merge 3 commits intodotnet:mainfrom
mrsharm:musharm/implementing-server-sent-events
Open

Add implementing-server-sent-events skill#265
mrsharm wants to merge 3 commits intodotnet:mainfrom
mrsharm:musharm/implementing-server-sent-events

Conversation

@mrsharm
Copy link
Member

@mrsharm mrsharm commented Mar 6, 2026

Summary

Adds a skill for implementing Server-Sent Events endpoints in ASP.NET Core 8 minimal APIs.

Note: Replaces #147 (migrated from skills-old repo to new plugins/ structure).

Eval Results (3-run)

  • Overall: +13.6% improvement (BL=4.0, SK=4.7)

Files

  • plugins/dotnet/skills/implementing-server-sent-events/SKILL.md
  • tests/dotnet/implementing-server-sent-events/eval.yaml

@mrsharm mrsharm requested a review from a team March 6, 2026 17:23
Copilot AI review requested due to automatic review settings March 6, 2026 17:23
@mrsharm
Copy link
Member Author

mrsharm commented Mar 6, 2026

Migration Note

This PR replaces #147 which was opened from mrsharm/skills-old. The skill and eval files have been migrated to the new plugins/ directory structure:

  • src/dotnet/skills/implementing-server-sent-events/plugins/dotnet/skills/implementing-server-sent-events/
  • src/dotnet/tests/implementing-server-sent-events/tests/dotnet/implementing-server-sent-events/

All prior review feedback from #147 still applies — please see that PR for the full discussion history.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new .NET skill documenting how to implement Server-Sent Events (SSE) endpoints in ASP.NET Core 8 minimal APIs, along with an evaluation scenario and CODEOWNERS entries to integrate it into the repo’s skill + eval workflow.

Changes:

  • Added the implementing-server-sent-events skill documentation under plugins/dotnet/skills/.
  • Added an eval scenario under tests/dotnet/implementing-server-sent-events/.
  • Registered ownership for the new skill and eval paths in .github/CODEOWNERS.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
plugins/dotnet/skills/implementing-server-sent-events/SKILL.md New skill doc explaining SSE requirements, formatting, flushing, disconnect handling, and reconnection.
tests/dotnet/implementing-server-sent-events/eval.yaml New eval scenario prompting an SSE endpoint implementation and scoring rubric expectations.
.github/CODEOWNERS Assigns owners for the new skill and its eval scenario directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +21 to +26
| 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 |

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown table under "## Inputs" is malformed (it uses double pipes like || Input | Required | Description |), so it won’t render correctly. Please switch to the standard table format used in other skills (single | delimiters with a separator row).

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +97
writer.AutoFlush = true; // Flushes after every Write
await writer.WriteLineAsync($"data: {msg}\n"); // Note: WriteLine adds one \n, we add one more
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StreamWriter example is misleading: WriteLineAsync appends TextWriter.NewLine (often \r\n on Windows), not a single \n as the comment states. For SSE it’s safer to explicitly write \n\n (or set writer.NewLine = "\n") so event framing is consistent across OSes.

Suggested change
writer.AutoFlush = true; // Flushes after every Write
await writer.WriteLineAsync($"data: {msg}\n"); // Note: WriteLine adds one \n, we add one more
writer.AutoFlush = true; // Flushes after every Write
writer.NewLine = "\n"; // Ensure consistent '\n' line endings across OSes
await writer.WriteLineAsync($"data: {msg}"); // Writes "data" line ending with '\n'
await writer.WriteLineAsync(); // Writes the blank line that terminates the SSE event

Copilot uses AI. Check for mistakes.
await context.Response.WriteAsync($"retry: 5000\n\n");
await context.Response.Body.FlushAsync();

var startFrom = lastEventId != null ? int.Parse(lastEventId) + 1 : 0;
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reconnection example assumes Last-Event-ID is an int (int.Parse(lastEventId)), but SSE event IDs are defined as opaque strings and the header can contain non-numeric values. Prefer treating it as a string (or using int.TryParse if you want to keep the numeric example) to avoid throwing and to better match the SSE spec.

Suggested change
var startFrom = lastEventId != null ? int.Parse(lastEventId) + 1 : 0;
var startFrom = 0;
if (!string.IsNullOrEmpty(lastEventId) && int.TryParse(lastEventId, out var parsedId))
{
startFrom = parsedId + 1;
}

Copilot uses AI. Check for mistakes.
@mrsharm
Copy link
Member Author

mrsharm commented Mar 6, 2026

Feedback carried over from #147

Code Review Comments

Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 110): 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. suggestion context.Response.ContentType = "text/event-stream"; context.Response.Headers["Cache-Control"] = "no-cache"; --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 176): 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. --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 97): 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. suggestion await writer.WriteAsync($"data: {msg}\n\n"); // Explicit \n\n for SSE event framing --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 147): 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. suggestion var startFrom = 0; if (!string.IsNullOrEmpty(lastEventId) && int.TryParse(lastEventId, out var parsedId) && parsedId >= 0) { startFrom = parsedId + 1; } --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 18): This skill doc doesnΓÇÖt include an explicit ΓÇ£ValidationΓÇ¥ section/checklist. CONTRIBUTING.md asks that changes include validation steps a reviewer can follow; consider adding a short section (e.g., how to run the sample, curl an SSE stream, verify Last-Event-ID behavior, and test via nginx) so the skill is verifiable. --- Copilot on src/dotnet/tests/implementing-server-sent-events/eval.yaml (line 2): This eval file is under src/dotnet/tests/..., but repo convention (and CI) expects evals at tests/<plugin>/<skill-name>/eval.yaml (e.g. tests/dotnet/implementing-server-sent-events/eval.yaml). With the current location, the evaluation workflow that runs --tests-dir ./tests/dotnet will not pick up these scenarios. --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 4): This SKILL.md is placed under src/dotnet/skills/..., but the repository layout expects skills under plugins/<plugin>/skills/<skill-name>/SKILL.md so 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/skills will not discover or test this skill. --- Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 44): These snippets use context.Response.Headers.ContentType / .CacheControl, but HttpResponse.Headers is an IHeaderDictionary and doesn't expose those properties. This will not compile in ASP.NET Core; set context.Response.ContentType (or Headers["Content-Type"]) and Headers["Cache-Control"] (or use context.Response.GetTypedHeaders()). suggestion context.Response.ContentType = "text/event-stream"; context.Response.Headers["Cache-Control"] = "no-cache"; --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 8): 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. --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 65): should {message} be sanitized for newlines here and below. AI says - risk of injection attack --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 8): 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: - Enforcing per-client or total connection limits (e.g., via middleware or a SemaphoreSlim) - Requiring authentication before establishing the SSE connection - Setting an idle timeout to drop stale connections This is especially relevant for a skill doc since agents will copy the pattern as-is without thinking about production hardening. --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 207): 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. --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 8): (says AI) --- danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 207): (says AI) --- BrennanConroy on src/dotnet/skills/implementing-server-sent-events/SKILL.md (line 31): Wrong. We have TypedResults.ServerSentEvents https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0#support-for-server-sent-events-sse Most of this Skill should probably be rewritten to use TypedResults.ServerSentEvents ---

Discussion Comments

mrsharm: ## Eval Results: implementing-server-sent-events ### 3-Run Validation: +13.6% PASS | Metric | Baseline | With Skill | Change | |--------|----------|------------|--------| | Overall Score | 4.0/5 | 4.7/5 | +0.7 | | Quality (overall) | 4.0/5 | 4.7/5 | +40% | | Pairwise | ΓÇö | skill wins | consistent | The skill consistently improves SSE implementations by teaching the manual protocol (no built-in helper exists), proper flush behavior, disconnection handling, and reconnection support. Model: claude-opus-4.6 (baseline + skill + judge) --- danmoseley: needs CODEOWNERS entry ---

@mrsharm
Copy link
Member Author

mrsharm commented Mar 6, 2026

Feedback carried over from #147

Code Review Comments

  • Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: This skill doc doesnΓÇÖt include an explicit ΓÇ£ValidationΓÇ¥ section/checklist. CONTRIBUTING.md asks that changes include validation steps a reviewer can follow; consider adding a short section (e.g., how to run the sample, curl an SSE stream, verify Last-Event-ID behavior, and test via nginx) so the skill is verifiable. - Copilot on src/dotnet/tests/implementing-server-sent-events/eval.yaml: This eval file is under src/dotnet/tests/..., but repo convention (and CI) expects evals at tests/<plugin>/<skill-name>/eval.yaml (e.g. tests/dotnet/implementing-server-sent-events/eval.yaml). With the current location, the evaluation workflow that runs --tests-dir ./tests/dotnet will not pick up these scenarios. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: This SKILL.md is placed under src/dotnet/skills/..., but the repository layout expects skills under plugins/<plugin>/skills/<skill-name>/SKILL.md so 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/skills will not discover or test this skill. - Copilot on src/dotnet/skills/implementing-server-sent-events/SKILL.md: These snippets use context.Response.Headers.ContentType / .CacheControl, but HttpResponse.Headers is an IHeaderDictionary and doesn't expose those properties. This will not compile in ASP.NET Core; set context.Response.ContentType (or Headers["Content-Type"]) and Headers["Cache-Control"] (or use context.Response.GetTypedHeaders()). - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: should {message} be sanitized for newlines here and below. AI says - risk of injection attack - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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: - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: 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. - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: (says AI) - danmoseley on src/dotnet/skills/implementing-server-sent-events/SKILL.md: (says AI) - BrennanConroy on src/dotnet/skills/implementing-server-sent-events/SKILL.md: Wrong. We have TypedResults.ServerSentEvents

Discussion

  • mrsharm: ## Eval Results: implementing-server-sent-events - danmoseley: needs CODEOWNERS entry

@ManishJayaswal
Copy link
Contributor

@mrsharm - - the repo has undergone some restructuring to make everything more organized. Hence, we are asking all open PRs to update the branch. Sorry about this.
This skill should be under ASP plugin. Please update the PR and submit again.
@adityamandaleeka @BrennanConroy - please review

Per repo restructuring feedback, ASP.NET Core specific skills should
be under the aspnetcore plugin rather than the dotnet plugin.
@mrsharm mrsharm force-pushed the musharm/implementing-server-sent-events branch from fd6c846 to 8654bb1 Compare March 10, 2026 20:17
mrsharm added 2 commits March 10, 2026 13:18
Per repo restructuring feedback, ASP.NET Core specific skills should
be under the aspnetcore plugin rather than the dotnet plugin.
Copilot AI review requested due to automatic review settings March 10, 2026 20:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +194 to +195
// COMMON MISTAKE: Using file.CopyToAsync for very large files
// IFormFile buffers everything in memory first — can cause OutOfMemoryException
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note says IFormFile buffers uploads "everything in memory first", but earlier in the same section it correctly notes buffering to a temp file. The in-memory claim is misleading and could cause readers to draw the wrong conclusions about resource usage; please adjust it to reflect the actual buffering behavior (memory up to a threshold, then temp file) and/or focus the warning on when to prefer MultipartReader.

Suggested change
// COMMON MISTAKE: Using file.CopyToAsync for very large files
// IFormFile buffers everything in memory first — can cause OutOfMemoryException
// COMMON MISTAKE: Using file.CopyToAsync with IFormFile for very large files
// IFormFile uses buffering (memory + temp files) during model binding; for true streaming
// of very large uploads, prefer MultipartReader as shown above to reduce memory/disk usage.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +23
## Files
- `plugins/aspnetcore/plugin.json` — ASP.NET Core plugin
- `plugins/aspnetcore/skills/implementing-server-sent-events/SKILL.md` — skill instructions
- `tests/aspnetcore/implementing-server-sent-events/eval.yaml` — 1 eval scenario (needs expansion)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR description file claims plugins/aspnetcore/plugin.json is part of the change and that the eval has "1 eval scenario (needs expansion)", but the PR as provided does not add plugins/aspnetcore/plugin.json, and the eval.yaml now contains multiple scenarios. Please update or remove this description file so it matches the actual changed files/structure.

Copilot uses AI. Check for mistakes.
## Files
- `plugins/aspnetcore/plugin.json` — new ASP.NET Core plugin
- `plugins/aspnetcore/skills/implementing-rate-limiting/SKILL.md` — skill instructions
- `tests/aspnetcore/implementing-rate-limiting/eval.yaml` — 5 eval scenarios
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR-267-description.md documents an "implementing-rate-limiting" skill and references files that are not part of this PR. Unless these PR-xxx description artifacts are intentionally being checked in for another purpose, they add noise and make it harder to review the actual changes; consider removing them from this PR or ensuring they correspond to included code.

Suggested change
- `tests/aspnetcore/implementing-rate-limiting/eval.yaml` — 5 eval scenarios

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +4
---
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.
---
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new aspnetcore plugin directory under plugins/aspnetcore/ does not include a plugin.json, unlike the other plugin roots (e.g., plugins/dotnet/plugin.json, plugins/dotnet-data/plugin.json). Adding plugins/aspnetcore/plugin.json is important for plugin manifest validation/discovery and to keep the repo’s plugin structure consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
---
name: minimal-api-file-upload
description: >
Implement file upload endpoints in ASP.NET Core minimal APIs (.NET 8+). USE FOR: handling IFormFile
and IFormFileCollection parameters, configuring dual size limits (Kestrel + FormOptions), disabling
anti-forgery on upload endpoints, validating file content via magic bytes, safe filename generation,
streaming large files with MultipartReader. DO NOT USE FOR: MVC controller file uploads ([FromForm]
works directly with attributes), simple JSON body endpoints, very large files over 1GB (use streaming
with MultipartReader instead of IFormFile).
---
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR metadata/description indicates this change is only adding the implementing-server-sent-events skill, but this PR also introduces a second new skill (minimal-api-file-upload) and its evals. Please either update the PR title/description to reflect both skills or split this into separate PRs to keep review/eval attribution clear.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +44
context.Response.Headers.ContentType = "text/event-stream";
context.Response.Headers.CacheControl = "no-cache";
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ASP.NET Core examples set SSE headers via context.Response.Headers.ContentType / .CacheControl, but IHeaderDictionary doesn't expose these properties (this won't compile). Use context.Response.ContentType = "text/event-stream" and set cache-control via context.Response.Headers["Cache-Control"] = "no-cache" (or GetTypedHeaders().CacheControl). This pattern appears multiple times in this document, so update all occurrences for consistency.

Suggested change
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 uses AI. Check for mistakes.
continue;

var fileName = disposition.FileName.Value;
var safeFile = $"{Guid.NewGuid()}{Path.GetExtension(fileName)}";
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the streaming example, the saved filename extension comes from Path.GetExtension(fileName) which is user-controlled input (and contradicts the earlier guidance to derive/validate extensions based on allowed types). Consider either fixing the extension based on validated content/allowlist, or omit the extension entirely to avoid trusting client-provided names.

Suggested change
var safeFile = $"{Guid.NewGuid()}{Path.GetExtension(fileName)}";
var safeFile = Guid.NewGuid().ToString();

Copilot uses AI. Check for mistakes.
Copy link
Member

@BrennanConroy BrennanConroy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said on the original PR, there is built-in SSE support in 10.0.
https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0#support-for-server-sent-events-sse

And if you don't want to use that, you can use the SsEFormatter type to write SSE frames from System.Net.ServerSentEvents nupkg.

@danmoseley
Copy link
Member

If #207 creates a dotnet-aspnet plugin, this skill should presumably move there.

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.

6 participants