Step 2: Stream a response
Time: 10 minutes
What you’ll see
Section titled “What you’ll see”Response text will arrive while the session is still working, and the application will know when the turn has finished.
How streaming changes the experience
Section titled “How streaming changes the experience”Streaming does not change the answer. It changes when your application receives it. Instead of waiting for one completed message, the session emits events throughout the turn:
AssistantMessageDeltaEventcontains each new piece of response text.AssistantMessageEventcontains the completed message.SessionIdleEventmeans the turn and any tool work have finished.SessionErrorEventreports a failed turn.
Why progressive output feels better
Section titled “Why progressive output feels better”Seeing text arrive makes the application feel more responsive. Later, the same event stream will show activity from local and MCP tools.
The session flow is now response deltas -> final message -> idle.
Let the response roll in
Section titled “Let the response roll in”1. Add the streaming helper
Section titled “1. Add the streaming helper”Create workshop-app/Helpers/ResponseStreamer.cs:
using GitHub.Copilot;
namespace HelloCopilotSDK.Helpers;
public static class ResponseStreamer{ public static async Task SendAndPrintAsync(CopilotSession session, string prompt) { var completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var receivedDelta = false;
using var subscription = session.On<SessionEvent>(sessionEvent => { switch (sessionEvent) { case AssistantMessageDeltaEvent delta when !string.IsNullOrEmpty(delta.Data.DeltaContent): receivedDelta = true; Console.Write(delta.Data.DeltaContent); break; case AssistantMessageEvent message when !receivedDelta: Console.Write(message.Data.Content); break; case SessionIdleEvent: Console.WriteLine(); completed.TrySetResult(); break; case SessionErrorEvent error: completed.TrySetException(new InvalidOperationException(error.Data.Message)); break; } });
await session.SendAsync(new MessageOptions { Prompt = prompt }); await completed.Task; }}The final-message case handles a runtime that completes without sending deltas. An error completes the task with an exception instead of looking like a successful turn.
2. Use the helper
Section titled “2. Use the helper”In Program.cs, keep var selectedModel = await ModelSelector.SelectAsync(client); from Step 1, then replace the session and response code with:
await using var session = await client.CreateSessionAsync(new SessionConfig{ Model = selectedModel, Streaming = true});
Console.WriteLine("\nCopilot:");await ResponseStreamer.SendAndPrintAsync( session, "Explain accessible names in three short bullet points.");Run it
Section titled “Run it”dotnet run --project workshop-appThe bullets should start appearing before the process exits:
Connected to the Copilot runtime: ...
Copilot:- Gives a control a programmatic identity.- Helps screen-reader users understand its purpose.- Connects visible labels to form controls.Troubleshooting this run
| Symptom | Fix |
|---|---|
| Text appears only at the end | Confirm Streaming = true is in this session’s SessionConfig. |
| The application exits before text appears | Confirm the helper awaits completed.Task after SendAsync. |
| Text is printed twice | Keep the when !receivedDelta guard on AssistantMessageEvent. |
You’re ready to add tools when: response text appears before the full answer is complete.
Check your understanding
Section titled “Check your understanding”When would SendAndWaitAsync be a better choice than event streaming?
Check your answer
Use SendAndWaitAsync for background work or simple request/response code that does not need
progressive output or intermediate events.
Complete Step 2 checkpoint
The completed Step 2 project is in
checkpoints/02-streaming.
using GitHub.Copilot;using HelloCopilotSDK.Helpers;
Console.WriteLine("=== Streaming from Copilot ===\n");
await using var client = new CopilotClient();await client.StartAsync();
var ping = await client.PingAsync("workshop");Console.WriteLine($"Connected to the Copilot runtime: {ping.Message}\n");
var selectedModel = await ModelSelector.SelectAsync(client);
await using var session = await client.CreateSessionAsync(new SessionConfig{ Model = selectedModel, Streaming = true});
Console.WriteLine("Copilot:");await ResponseStreamer.SendAndPrintAsync( session, "Explain accessible names in three short bullet points.");Continue to Step 3: Add application-owned knowledge.