Skip to content

Step 2: Stream a response

Time: 10 minutes

Response text will arrive while the session is still working, and the application will know when the turn has finished.

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:

  • AssistantMessageDeltaEvent contains each new piece of response text.
  • AssistantMessageEvent contains the completed message.
  • SessionIdleEvent means the turn and any tool work have finished.
  • SessionErrorEvent reports a failed turn.

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.

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.

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.");
Terminal window
dotnet run --project workshop-app

The 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
SymptomFix
Text appears only at the endConfirm Streaming = true is in this session’s SessionConfig.
The application exits before text appearsConfirm the helper awaits completed.Task after SendAsync.
Text is printed twiceKeep the when !receivedDelta guard on AssistantMessageEvent.

You’re ready to add tools when: response text appears before the full answer is complete.

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.

Reset all workshop progress?

This clears completed lessons and checked task items stored in this browser. This action cannot be undone.