Skip to content

Latest commit

 

History

History
691 lines (541 loc) · 17.3 KB

File metadata and controls

691 lines (541 loc) · 17.3 KB

Practical Guide: Integrating the Copilot SDK into a C# Project

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.


Prerequisites

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

Step 1 — Create a New C# Project

Visual Studio

  1. Open Visual Studio → Create a new project
  2. Select Console App (.NET) → click Next
  3. Enter a project name, e.g. MyCopilotApp → click Next
  4. Select .NET 8.0 → click Create

VS Code

mkdir MyCopilotApp && cd MyCopilotApp
dotnet new console --framework net8.0
code .

.NET CLI (any terminal)

mkdir MyCopilotApp && cd MyCopilotApp
dotnet new console --framework net8.0

Step 2 — Install the NuGet Package

Visual Studio — NuGet Package Manager

  1. Right-click the project → Manage NuGet Packages
  2. Search for GitHub.Copilot.SDK
  3. Click Install

.NET CLI

dotnet add package GitHub.Copilot.SDK

Verify 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>

Step 3 — Write the Minimal Integration

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;

Run it

dotnet run

You will be prompted for input and receive a Copilot response in the terminal.


Step 4 — Interactive Chat Loop

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");
}

Step 5 — Enable Streaming Responses

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;

Step 6 — Add Custom Tools

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;

Step 7 — Attach File Context

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"
        }
    ]
});

Step 8 — Session Hooks

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 };
        }
    }
});

Step 9 — Persist Sessions (Infinite Sessions)

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);
}

Step 10 — User Input Requests (ask_user)

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
        };
    }
});

Error Handling

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}");
}

IDE Setup Tips

Visual Studio 2022

  • 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.SDK namespaces.
  • Enable nullable reference types in your .csproj (<Nullable>enable</Nullable>) — the SDK ships with full nullability annotations.

VS Code with C# Dev Kit

  1. Install extensions:
    • C# Dev Kit (ms-dotnettools.csdevkit)
    • GitHub Copilot (GitHub.copilot)
  2. Open the project folder: code .
  3. Restore packages: dotnet restore (or the Dev Kit does this automatically).
  4. Run/debug with F5 — select C#: Launch profile.
  5. Use Ctrl+Space for IntelliSense on SDK types.

Rider (JetBrains)

  1. Open the .csproj or solution file.
  2. NuGet packages are restored automatically.
  3. Use Alt+EnterImport namespace to add using GitHub.Copilot.SDK;.
  4. Attach the debugger with Shift+F9.

Common Patterns

Pattern: Fire-and-Collect

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);

Pattern: Cancellation

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" });

Pattern: Multiple Parallel Sessions

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}");

Authentication Options

Default (signed-in CLI user)

No extra configuration needed — the SDK uses the credentials stored by copilot auth login.

GitHub Token via environment variable

export COPILOT_GITHUB_TOKEN=ghp_...
dotnet run

GitHub Token in code

var client = new CopilotClient(new CopilotClientOptions
{
    GitHubToken = Environment.GetEnvironmentVariable("MY_TOKEN")
});

BYOK (Bring Your Own Key)

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.


Full Project Example

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();
}

What to Read Next