Skip to content
All Skills

GitHub Copilot SDK C# Instructions

This file provides guidance on building C# applications using GitHub Copilot SDK.

Agent Orchestration|v1|Updated 7/14/2026|GitHub source
MCP get_skill({ skillId: "instructions-copilot-sdk-c-9358f28c" })

Use this skill with your agent

Create a free account and connect via MCP

Get Started Free
## 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 = "john@example.com",
                    Role = "Developer"
                };
            },
            "get_user",
            "Retrieve user information")
    ]
});
```
#github-copilot#m365#copilot#agentscopilot-clidotnet

Related Skills

More skills in Agent Orchestration

1. Product type search — what design patterns fit this product?

Design UI/UX systems with style guides, palettes, typography, and component specs for new interfaces

#github#broad-capabilityMIT

Acp Router

Route plain-language requests for Claude Code, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, Qwen, Kiro, Kimi, iFlow, Factory Droid, Kilocode, or explicit ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation. Codex chat binding defaults to the native Codex app-server plugin unless ACP is explicit or background spawn needs ACP.

#broad-capability#browserMIT

Add New Safe Output Type

Adding a New Safe Output Type to GitHub Agentic Workflows

#github#broad-capabilityMIT

Agent Governance

Patterns and techniques for adding governance, safety, and trust controls to AI agent systems. Use this skill when: - Building AI agents that call external tools (APIs, databases, file systems) - Implementing policy-based access controls for agent tool usage - Adding semantic intent classification to detect dangerous prompts - Creating trust scoring systems for multi-agent workflows - Building audit trails for agent actions and decisions - Enforcing rate limits, content filters, or tool restrictions on agents - Working with any agent framework (PydanticAI, CrewAI, OpenAI Agents, LangChain, AutoGen)

#github-copilot#agentMIT

Agentic Development

Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)

#claude-bootstrap#bootstrapMIT

Agentic Workflows

Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.

#broad-capability#agentic-workflowsMIT