mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 18:35:14 +00:00
Adding copilot-sdk stuff
This commit is contained in:
550
instructions/copilot-sdk-csharp.instructions.md
Normal file
550
instructions/copilot-sdk-csharp.instructions.md
Normal file
@@ -0,0 +1,550 @@
|
||||
---
|
||||
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
|
||||
{
|
||||
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 { ... });
|
||||
```
|
||||
|
||||
### 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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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 { Model = "gpt-5" });
|
||||
var session2 = await client.CreateSessionAsync(new SessionConfig { 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
|
||||
{
|
||||
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();
|
||||
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();
|
||||
// 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
|
||||
{
|
||||
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();
|
||||
|
||||
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
|
||||
{
|
||||
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")
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user