Skip to main content
rule official · GitHub

copilot-sdk-csharp — GitHub Copilot rule: setup and usage

This rule guides C# work with the GitHub Copilot SDK, from client setup to session events. Use it when adding SDK features to a project.

Install into GitHub Copilot · Manually: put the files in place All methods and platforms ↓
.github/instructions/copilot-sdk-csharp.instructions.md
Open on GitHub

What copilot-sdk-csharp does

The rule applies to **.cs and **.csproj files. It covers NuGet installation, client configuration, CLI startup, and session management. For event handling, it recommends waiting with TaskCompletionSource; event subscriptions can be removed through the returned IDisposable.

It also describes streaming responses and custom tools built with AIFunctionFactory. The SDK is in technical preview and may introduce breaking changes. Projects need .NET 10.0 or later, plus GitHub Copilot CLI available in PATH; without the CLI, the client cannot start its process.

What it helps with

  • add the GitHub Copilot SDK through NuGet
  • configure the CLI process from a C# client
  • create or resume a Copilot session
  • handle session events and streamed responses
  • define typed custom tools with AIFunctionFactory

Example prompts for the agent

  • Configure this C# project for the GitHub Copilot SDK using this rule.
  • Add streaming responses and session event handling according to this rule.
  • Create a custom tool with AIFunctionFactory following this rule.

How to install copilot-sdk-csharp

Manually: put the files in place

.github/instructions/copilot-sdk-csharp.instructions.md

The applyTo field sets which files it applies to

Step by step, with commands for each platform: AGENTS.md vs CLAUDE.md: what they are and which one to use

Questions about copilot-sdk-csharp

How do I install copilot-sdk-csharp in GitHub Copilot?

Add the rule as .github/instructions/copilot-sdk-csharp.instructions.md in your repository.

What does copilot-sdk-csharp do?

It provides C# guidance for the GitHub Copilot SDK, including client setup, session management, event handling, streaming, and custom tools.

What does a project need to use this rule?

Use .NET 10.0 or later and make sure GitHub Copilot CLI is available in PATH. The SDK is in technical preview and may have breaking changes.

Source text

copilot-sdk-csharp.instructions.md · MIT
---
applyTo: '**.cs, **.csproj'
description: 'This file provides guidance on building C# applications using GitHub Copilot SDK.'
name: 'GitHub Copilot SDK C# Instructions'
---

## Core Principles

- The SDK is in technical preview and may have breaking changes
- Requires .NET 10.0 or later
- Requires GitHub Copilot CLI installed and in PATH
- Uses async/await patterns throughout
- Implements IAsyncDisposable for resource cleanup

## Installation

Always install via NuGet:
```bash
dotnet add package GitHub.Copilot.SDK
```

## Client Initialization

### Basic Client Setup

```csharp
await using var client = new CopilotClient();
await client.StartAsync();
```

### Client Configuration Options

When creating a CopilotClient, use `CopilotClientOptions`:

- `CliPath` - Path to CLI executable (default: "copilot" from PATH)
- `CliArgs` - Extra arguments prepended before SDK-managed flags
- `CliUrl` - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
- `Port` - Server port (default: 0 for random)
- `UseStdio` - Use stdio transport instead of TCP (default: true)
- `LogLevel` - Log level (default: "info")
- `AutoStart` - Auto-start server (default: true)
- `AutoRestart` - Auto-restart on crash (default: true)
- `Cwd` - Working directory for the CLI process
- `Environment` - Environment variables for the CLI process
- `Logger` - ILogger instance for SDK logging

### Manual Server Control

For explicit control:
```csharp
var client = new CopilotClient(new CopilotClientOptions { AutoStart = false });
await client.StartAsync();
// Use client...
await client.StopAsync();
```

Use `ForceStopAsync()` when `StopAsync()` takes too long.

## Session Management

### Creating Sessions

Use `SessionConfig` for configuration:

```csharp
await using var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
    Streaming = true,
    Tools = [...],
    SystemMessage = new SystemMessageConfig { ... },
    AvailableTools = ["tool1", "tool2"],
    ExcludedTools = ["tool3"],
    Provider = new ProviderConfig { ... }
});
```

### Session Config Options

- `SessionId` - Custom session ID
- `Model` - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
- `Tools` - Custom tools exposed to the CLI
- `SystemMessage` - System message customization
- `AvailableTools` - Allowlist of tool names
- `ExcludedTools` - Blocklist of tool names
- `Provider` - Custom API provider configuration (BYOK)
- `Streaming` - Enable streaming response chunks (default: false)

### Resuming Sessions

```csharp
var session = await client.ResumeSessionAsync(sessionId, new ResumeSessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    // ...
});
```

### Session Operations

- `session.SessionId` - Get session identifier
- `session.SendAsync(new MessageOptions { Prompt = "...", Attachments = [...] })` - Send message
- `session.AbortAsync()` - Abort current processing
- `session.GetMessagesAsync()` - Get all events/messages
- `await session.DisposeAsync()` - Clean up resources

## Event Handling

### Event Subscription Pattern

ALWAYS use TaskCompletionSource for waiting on session events:

```csharp
var done = new TaskCompletionSource();

session.On(evt =>
{
    if (evt is AssistantMessageEvent msg)
    {
        Console.WriteLine(msg.Data.Content);
    }
    else if (evt is SessionIdleEvent)
    {
        done.SetResult();
    }
});

await session.SendAsync(new MessageOptions { Prompt = "..." });
await done.Task;
```

### Unsubscribing from Events

The `On()` method returns an IDisposable:

```csharp
var subscription = session.On(evt => { /* handler */ });
// Later...
subscription.Dispose();
```

### Event Types

Use pattern matching or switch expressions for event handling:

```csharp
session.On(evt =>
{
    switch (evt)
    {
        case UserMessageEvent userMsg:
            // Handle user message
            break;
        case AssistantMessageEvent assistantMsg:
            Console.WriteLine(assistantMsg.Data.Content);
            break;
        case ToolExecutionStartEvent toolStart:
            // Tool execution started
            break;
        case ToolExecutionCompleteEvent toolComplete:
            // Tool execution completed
            break;
        case SessionStartEvent start:
            // Session started
            break;
        case SessionIdleEvent idle:
            // Session is idle (processing complete)
            break;
        case SessionErrorEvent error:
            Console.WriteLine($"Error: {error.Data.Message}");
            break;
    }
});
```

## Streaming Responses

### Enabling Streaming

Set `Streaming = true` in SessionConfig:

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
    Streaming = true
});
```

### Handling Streaming Events

Handle both delta events (incremental) and final events:

```csharp
var done = new TaskCompletionSource();

session.On(evt =>
{
    switch (evt)
    {
        case AssistantMessageDeltaEvent delta:
            // Incremental text chunk
            Console.Write(delta.Data.DeltaContent);
            break;
        case AssistantReasoningDeltaEvent reasoningDelta:
            // Incremental reasoning chunk (model-dependent)
            Console.Write(reasoningDelta.Data.DeltaContent);
            break;
        case AssistantMessageEvent msg:
            // Final complete message
            Console.WriteLine("\n--- Final ---");
            Console.WriteLine(msg.Data.Content);
            break;
        case AssistantReasoningEvent reasoning:
            // Final reasoning content
            Console.WriteLine("--- Reasoning ---");
            Console.WriteLine(reasoning.Data.Content);
            break;
        case SessionIdleEvent:
            done.SetResult();
            break;
    }
});

await session.SendAsync(new MessageOptions { Prompt = "Tell me a story" });
await done.Task;
```

Note: Final events (`AssistantMessageEvent`, `AssistantReasoningEvent`) are ALWAYS sent regardless of streaming setting.

## Custom Tools

### Defining Tools with AIFunctionFactory

Use `Microsoft.Extensions.AI.AIFunctionFactory.Create` for type-safe tools:

```csharp
using Microsoft.Extensions.AI;
using System.ComponentModel;

var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
    Tools = [
        AIFunctionFactory.Create(
            async ([Description("Issue ID")] string id) => {
                var issue = await FetchIssueAsync(id);
                return issue;
            },
            "lookup_issue",
            "Fetch issue details from tracker"),
    ]
});
```

### Tool Return Types

- Return any JSON-serializable value (automatically wrapped)
- Or return `ToolResultAIContent` wrapping `ToolResultObject` for full control over metadata

### Tool Execution Flow

When Copilot invokes a tool, the client automatically:
1. Runs your handler function
2. Serializes the return value
3. Responds to the CLI

## System Message Customization

### Append Mode (Default - Preserves Guardrails)

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
    SystemMessage = new SystemMessageConfig
    {
        Mode = SystemMessageMode.Append,
        Content = @"
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
"
    }
});
```

### Replace Mode (Full Control - Removes Guardrails)

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
    SystemMessage = new SystemMessageConfig
    {
        Mode = SystemMessageMode.Replace,
        Content = "You are a helpful assistant."
    }
});
```

## File Attachments

Attach files to messages using `UserMessageDataAttachmentsItem`:

```csharp
await session.SendAsync(new MessageOptions
{
    Prompt = "Analyze this file",
    Attachments = new List<UserMessageDataAttachmentsItem>
    {
        new UserMessageDataAttachmentsItem
        {
            Type = UserMessageDataAttachmentsItemType.File,
            Path = "/path/to/file.cs",
            DisplayName = "My File"
        }
    }
});
```

## Message Delivery Modes

Use the `Mode` property in `MessageOptions`:

- `"enqueue"` - Queue message for processing
- `"immediate"` - Process message immediately

```csharp
await session.SendAsync(new MessageOptions
{
    Prompt = "...",
    Mode = "enqueue"
});
```

## Multiple Sessions

Sessions are independent and can run concurrently:

```csharp
var session1 = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5",
});
var session2 = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "claude-sonnet-4.5",
});

await session1.SendAsync(new MessageOptions { Prompt = "Hello from session 1" });
await session2.SendAsync(new MessageOptions { Prompt = "Hello from session 2" });
```

## Bring Your Own Key (BYOK)

Use custom API providers via `ProviderConfig`:

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Provider = new ProviderConfig
    {
        Type = "openai",
        BaseUrl = "https://api.openai.com/v1",
        ApiKey = "your-api-key"
    }
});
```

## Session Lifecycle Management

### Listing Sessions

```csharp
var sessions = await client.ListSessionsAsync();
foreach (var metadata in sessions)
{
    Console.WriteLine($"Session: {metadata.SessionId}");
}
```

### Deleting Sessions

```csharp
await client.DeleteSessionAsync(sessionId);
```

### Checking Connection State

```csharp
var state = client.State;
```

## Error Handling

### Standard Exception Handling

```csharp
try
{
    var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll });
    await session.SendAsync(new MessageOptions { Prompt = "Hello" });
}
catch (StreamJsonRpc.RemoteInvocationException ex)
{
    Console.Error.WriteLine($"JSON-RPC Error: {ex.Message}");
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
}
```

### Session Error Events

Monitor `SessionErrorEvent` for runtime errors:

```csharp
session.On(evt =>
{
    if (evt is SessionErrorEvent error)
    {
        Console.Error.WriteLine($"Session Error: {error.Data.Message}");
    }
});
```

## Connectivity Testing

Use PingAsync to verify server connectivity:

```csharp
var response = await client.PingAsync("test message");
```

## Resource Cleanup

### Automatic Cleanup with Using

ALWAYS use `await using` for automatic disposal:

```csharp
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll });
// Resources automatically cleaned up
```

### Manual Cleanup

If not using `await using`:

```csharp
var client = new CopilotClient();
try
{
    await client.StartAsync();
    // Use client...
}
finally
{
    await client.StopAsync();
}
```

## Best Practices

1. **Always use `await using`** for CopilotClient and CopilotSession
2. **Use TaskCompletionSource** to wait for SessionIdleEvent
3. **Handle SessionErrorEvent** for robust error handling
4. **Use pattern matching** (switch expressions) for event handling
5. **Enable streaming** for better UX in interactive scenarios
6. **Use AIFunctionFactory** for type-safe tool definitions
7. **Dispose event subscriptions** when no longer needed
8. **Use SystemMessageMode.Append** to preserve safety guardrails
9. **Provide descriptive tool names and descriptions** for better model understanding
10. **Handle both delta and final events** when streaming is enabled

## Common Patterns

### Simple Query-Response

```csharp
await using var client = new CopilotClient();
await client.StartAsync();

await using var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Model = "gpt-5"
});

var done = new TaskCompletionSource();

session.On(evt =>
{
    if (evt is AssistantMessageEvent msg)
    {
        Console.WriteLine(msg.Data.Content);
    }
    else if (evt is SessionIdleEvent)
    {
        done.SetResult();
    }
});

await session.SendAsync(new MessageOptions { Prompt = "What is 2+2?" });
await done.Task;
```

### Multi-Turn Conversation

```csharp
await using var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll });

async Task SendAndWait(string prompt)
{
    var done = new TaskCompletionSource();
    var subscription = session.On(evt =>
    {
        if (evt is AssistantMessageEvent msg)
        {
            Console.WriteLine(msg.Data.Content);
        }
        else if (evt is SessionIdleEvent)
        {
            done.SetResult();
        }
    });

    await session.SendAsync(new MessageOptions { Prompt = prompt });
    await done.Task;
    subscription.Dispose();
}

await SendAndWait("What is the capital of France?");
await SendAndWait("What is its population?");
```

### Tool with Complex Return Type

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
    OnPermissionRequest = PermissionHandler.ApproveAll,
    Tools = [
        AIFunctionFactory.Create(
            ([Description("User ID")] string userId) => {
                return new {
                    Id = userId,
                    Name = "John Doe",
                    Email = "[email protected]",
                    Role = "Developer"
                };
            },
            "get_user",
            "Retrieve user information")
    ]
});
```

© github · MIT · github/awesome-copilot

Similar IDE rules

All IDE rules for Programming languages →

From the same set732

The whole set →
copilot-sdk-python Coding instructions for GitHub Copilot when building Python apps with github-copilot-sdk. Guides async client initialization, session setup, tools, and streaming events. ★ 40,418repo stars copilot-sdk-nodejs Guidance for Node.js and TypeScript apps built with the GitHub Copilot SDK, covering client startup, sessions, events, and streamed output. ★ 40,418repo stars copilot-sdk-java A rule that tells Copilot how to build Java apps on the GitHub Copilot SDK: client setup, sessions, async calls. ★ 40,418repo stars copilot-sdk-go Instructions for developing Go applications with GitHub Copilot SDK. Guides the assistant on client initialization, streaming sessions, and event channels. ★ 40,418repo stars convert-jpa-to-spring-data-cosmos A step-by-step Copilot rule for moving a Spring Boot app off JPA and onto Azure Cosmos DB with Spring Data Cosmos. Useful once the migration is already decided. ★ 40,418repo stars convert-cassandra-to-spring-data-cosmos Step-by-step instructions for converting Spring Boot Cassandra projects to Azure Cosmos DB with spring-data-cosmos. Applies to Java code, build files, and application properties. ★ 40,418repo stars context7 This rule brings Context7 into tasks that depend on current, version-specific API documentation. It is useful when the workspace does not contain the needed external reference. ★ 40,418repo stars context-engineering A GitHub Copilot rule on file layout, types and comments, so suggestions match your project instead of drifting into generic answers. ★ 40,418repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor