This guide walks you through every step needed to embed the GitHub Copilot SDK into a C# application — from a blank project to a fully working AI-powered assistant — using Visual Studio or VS Code with the C# Dev Kit.
| Requirement | Version | Notes |
|---|---|---|
| .NET SDK | 8.0 or later | dotnet --version to check |
| GitHub Copilot CLI | latest | Installation guide |
| GitHub Copilot subscription | — | Free tier available; required unless using BYOK |
| IDE | Visual Studio 2022 17.8+ or VS Code with C# Dev Kit |
Verify the CLI is installed and authenticated:
copilot --version
copilot auth status- Open Visual Studio → Create a new project
- Select Console App (.NET) → click Next
- Enter a project name, e.g.
MyCopilotApp→ click Next - Select .NET 8.0 → click Create
mkdir MyCopilotApp && cd MyCopilotApp
dotnet new console --framework net8.0
code .mkdir MyCopilotApp && cd MyCopilotApp
dotnet new console --framework net8.0- Right-click the project → Manage NuGet Packages
- Search for
GitHub.Copilot.SDK - Click Install
dotnet add package GitHub.Copilot.SDKVerify the package is referenced in your .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GitHub.Copilot.SDK" Version="*" />
</ItemGroup>
</Project>Replace the contents of Program.cs with:
using GitHub.Copilot.SDK;
// 1. Create the client (auto-starts the Copilot CLI process)
await using var client = new CopilotClient();
// 2. Open a session
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
OnPermissionRequest = PermissionHandler.ApproveAll
});
// 3. Subscribe to events
var done = new TaskCompletionSource();
session.On(evt =>
{
switch (evt)
{
case AssistantMessageEvent msg:
Console.WriteLine($"Copilot: {msg.Data.Content}");
break;
case SessionIdleEvent:
done.SetResult();
break;
case SessionErrorEvent err:
Console.Error.WriteLine($"Error: {err.Data.Message}");
done.TrySetResult();
break;
}
});
// 4. Send a message and wait for the response
Console.Write("You: ");
var prompt = Console.ReadLine() ?? "Hello!";
await session.SendAsync(new MessageOptions { Prompt = prompt });
await done.Task;dotnet runYou will be prompted for input and receive a Copilot response in the terminal.
Extend the example into a multi-turn conversation:
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
OnPermissionRequest = PermissionHandler.ApproveAll
});
Console.WriteLine("Chat with Copilot — press Ctrl+C to exit\n");
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine()?.Trim();
if (string.IsNullOrEmpty(input)) continue;
// SendAndWaitAsync blocks until the session becomes idle
var reply = await session.SendAndWaitAsync(new MessageOptions { Prompt = input });
Console.WriteLine($"\nCopilot: {reply?.Data.Content}\n");
}Receive partial chunks as the model generates them:
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Streaming = true,
OnPermissionRequest = PermissionHandler.ApproveAll
});
var done = new TaskCompletionSource();
session.On(evt =>
{
switch (evt)
{
case AssistantMessageDeltaEvent delta:
// Print each chunk as it arrives (no newline yet)
Console.Write(delta.Data.DeltaContent);
break;
case AssistantReasoningDeltaEvent reasoning:
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write(reasoning.Data.DeltaContent);
Console.ResetColor();
break;
case AssistantMessageEvent:
// Full message received — newline after the stream
Console.WriteLine();
break;
case SessionIdleEvent:
done.SetResult();
break;
}
});
Console.Write("You: ");
var prompt = Console.ReadLine() ?? "Explain async/await in C#";
await session.SendAsync(new MessageOptions { Prompt = prompt });
await done.Task;Expose your own C# methods to the model using AIFunctionFactory (from the Microsoft.Extensions.AI package, already included transitively):
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
using System.ComponentModel;
// Simulated data source
static async Task<string> GetOrderStatus(string orderId)
{
await Task.Delay(50); // simulate async I/O
return orderId switch
{
"ORD-001" => "Shipped — arriving tomorrow",
"ORD-002" => "Processing",
_ => "Order not found"
};
}
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Tools =
[
AIFunctionFactory.Create(
async ([Description("Order identifier, e.g. ORD-001")] string orderId) =>
await GetOrderStatus(orderId),
"get_order_status",
"Retrieve the current status of a customer order")
],
OnPermissionRequest = PermissionHandler.ApproveAll
});
var done = new TaskCompletionSource();
session.On(evt =>
{
switch (evt)
{
case ToolExecutionStartEvent tool:
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($" [calling tool: {tool.Data.ToolName}]");
Console.ResetColor();
break;
case AssistantMessageEvent msg:
Console.WriteLine($"Copilot: {msg.Data.Content}");
break;
case SessionIdleEvent:
done.SetResult();
break;
}
});
await session.SendAsync(new MessageOptions
{
Prompt = "What is the status of order ORD-001 and ORD-002?"
});
await done.Task;Send files alongside your prompt so the model can read, analyse, or edit them:
await session.SendAsync(new MessageOptions
{
Prompt = "Review this file for potential bugs and suggest fixes.",
Attachments =
[
new UserMessageDataAttachmentsItem
{
Type = UserMessageDataAttachmentsItemType.File,
Path = "/absolute/path/to/MyService.cs",
DisplayName = "MyService.cs"
}
]
});Intercept events to add logging, validation, or policy enforcement:
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
OnPermissionRequest = PermissionHandler.ApproveAll,
Hooks = new SessionHooks
{
// Inspect (and optionally block) every tool call
OnPreToolUse = async (input, _) =>
{
Console.WriteLine($"[pre-tool] {input.ToolName}");
return new PreToolUseHookOutput { PermissionDecision = "allow" };
},
// Post-process tool results
OnPostToolUse = async (input, _) =>
{
Console.WriteLine($"[post-tool] {input.ToolName} finished");
return new PostToolUseHookOutput();
},
// Modify or log user prompts before they reach the model
OnUserPromptSubmitted = async (input, _) =>
{
Console.WriteLine($"[prompt] {input.Prompt}");
return new UserPromptSubmittedHookOutput { ModifiedPrompt = input.Prompt };
}
}
});By default sessions persist their workspace to ~/.copilot/session-state/{sessionId}/. You can resume them later:
// --- First run: create and use a session ---
await using var client = new CopilotClient();
string sessionId;
{
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
OnPermissionRequest = PermissionHandler.ApproveAll
});
sessionId = session.SessionId;
Console.WriteLine($"Session ID: {sessionId}");
var reply = await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Remember: the project is called 'Orion'"
});
Console.WriteLine(reply?.Data.Content);
} // session disposed — data stays on disk
// --- Later (or next app run): resume the same session ---
{
await using var resumed = await client.ResumeSessionAsync(sessionId);
var reply = await resumed.SendAndWaitAsync(new MessageOptions
{
Prompt = "What was the project name I told you earlier?"
});
Console.WriteLine(reply?.Data.Content);
}Let the model ask the user for clarification:
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
OnPermissionRequest = PermissionHandler.ApproveAll,
OnUserInputRequest = async (request, _) =>
{
Console.WriteLine($"\nCopilot asks: {request.Question}");
if (request.Choices?.Count > 0)
{
Console.WriteLine("Options: " + string.Join(" / ", request.Choices));
}
Console.Write("Your answer: ");
var answer = Console.ReadLine() ?? string.Empty;
return new UserInputResponse
{
Answer = answer,
WasFreeform = true
};
}
});try
{
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync();
var reply = await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Hello"
});
Console.WriteLine(reply?.Data.Content);
}
catch (IOException ex)
{
// CLI process communication failure
Console.Error.WriteLine($"Communication error: {ex.Message}");
}
catch (InvalidOperationException ex)
{
// SDK misuse (e.g. session already disposed)
Console.Error.WriteLine($"Usage error: {ex.Message}");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Unexpected error: {ex.Message}");
}- Install the GitHub Copilot extension (built-in from 17.10+) for inline suggestions while writing SDK code.
- Use F12 to navigate to SDK source or generated types.
- Use Quick Actions (Ctrl+.) to auto-import
GitHub.Copilot.SDKnamespaces. - Enable nullable reference types in your
.csproj(<Nullable>enable</Nullable>) — the SDK ships with full nullability annotations.
- Install extensions:
- C# Dev Kit (
ms-dotnettools.csdevkit) - GitHub Copilot (
GitHub.copilot)
- C# Dev Kit (
- Open the project folder:
code . - Restore packages:
dotnet restore(or the Dev Kit does this automatically). - Run/debug with F5 — select C#: Launch profile.
- Use
Ctrl+Spacefor IntelliSense on SDK types.
- Open the
.csprojor solution file. - NuGet packages are restored automatically.
- Use Alt+Enter → Import namespace to add
using GitHub.Copilot.SDK;. - Attach the debugger with Shift+F9.
Collect all assistant messages after a single send:
var messages = new List<string>();
var done = new TaskCompletionSource();
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
messages.Add(msg.Data.Content);
else if (evt is SessionIdleEvent)
done.SetResult();
});
await session.SendAsync(new MessageOptions { Prompt = "List five SOLID principles" });
await done.Task;
foreach (var m in messages)
Console.WriteLine(m);Abort a long-running response:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
cts.Token.Register(async () =>
{
await session.AbortAsync();
Console.WriteLine("Request aborted.");
});
await session.SendAsync(new MessageOptions { Prompt = "Write me a 10 000-word essay" });await using var client = new CopilotClient();
var sessionA = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-4.1" });
var sessionB = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" });
// Run both concurrently
var taskA = sessionA.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 2?" });
var taskB = sessionB.SendAndWaitAsync(new MessageOptions { Prompt = "What is 3 + 3?" });
var results = await Task.WhenAll(taskA, taskB);
Console.WriteLine($"GPT: {results[0]?.Data.Content}");
Console.WriteLine($"Claude: {results[1]?.Data.Content}");No extra configuration needed — the SDK uses the credentials stored by copilot auth login.
export COPILOT_GITHUB_TOKEN=ghp_...
dotnet runvar client = new CopilotClient(new CopilotClientOptions
{
GitHubToken = Environment.GetEnvironmentVariable("MY_TOKEN")
});var session = await client.CreateSessionAsync(new SessionConfig
{
Provider = new ProviderConfig
{
Type = "openai",
BaseUrl = "https://api.openai.com/v1",
ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
}
});See the Authentication Guide and BYOK Guide for more details.
A ready-to-run project structure:
MyCopilotApp/
├── MyCopilotApp.csproj
├── Program.cs
└── Services/
└── WeatherService.cs
MyCopilotApp.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GitHub.Copilot.SDK" Version="*" />
</ItemGroup>
</Project>Services/WeatherService.cs
namespace MyCopilotApp.Services;
public static class WeatherService
{
private static readonly Dictionary<string, string> _data = new()
{
["Kyiv"] = "12°C, partly cloudy",
["London"] = "8°C, rainy",
["Tokyo"] = "22°C, sunny",
};
public static Task<string> GetWeatherAsync(string city) =>
Task.FromResult(_data.TryGetValue(city, out var w) ? w : "No data available");
}Program.cs
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
using MyCopilotApp.Services;
using System.ComponentModel;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Streaming = true,
OnPermissionRequest = PermissionHandler.ApproveAll,
Tools =
[
AIFunctionFactory.Create(
async ([Description("City name")] string city) =>
await WeatherService.GetWeatherAsync(city),
"get_weather",
"Get current weather for a city")
]
});
Console.WriteLine("Weather Assistant — Ctrl+C to exit\n");
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine()?.Trim();
if (string.IsNullOrEmpty(input)) continue;
Console.Write("Copilot: ");
var done = new TaskCompletionSource();
using var _ = session.On(evt =>
{
switch (evt)
{
case AssistantMessageDeltaEvent delta:
Console.Write(delta.Data.DeltaContent);
break;
case AssistantMessageEvent:
Console.WriteLine();
break;
case ToolExecutionStartEvent tool:
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"[{tool.Data.ToolName}] ");
Console.ResetColor();
break;
case SessionIdleEvent:
done.TrySetResult();
break;
}
});
await session.SendAsync(new MessageOptions { Prompt = input });
await done.Task;
Console.WriteLine();
}- .NET API Reference — Full API surface, all options and overloads
- Session Persistence — Long-running and resumable sessions
- Custom Agents — Define specialized sub-agents
- Authentication Guide — Auth options for production deployments
- Setup Guides — Deployment patterns (local, backend, bundled)