Moving the copilot-sdk cookbook content in here

This commit is contained in:
Aaron Powell
2026-01-29 14:29:36 +11:00
parent ccdfd66cc2
commit f59e0b4080
53 changed files with 5385 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# Runnable Recipe Examples
This folder contains standalone, executable C# examples for each cookbook recipe. These are [file-based apps](https://learn.microsoft.com/dotnet/core/sdk/file-based-apps) that can be run directly with `dotnet run`.
## Prerequisites
- .NET 10.0 or later
- GitHub Copilot SDK package (referenced automatically)
## Running Examples
Each `.cs` file is a complete, runnable program. Simply use:
```bash
dotnet run <filename>.cs
```
### Available Recipes
| Recipe | Command | Description |
| -------------------- | ------------------------------------ | ------------------------------------------ |
| Error Handling | `dotnet run error-handling.cs` | Demonstrates error handling patterns |
| Multiple Sessions | `dotnet run multiple-sessions.cs` | Manages multiple independent conversations |
| Managing Local Files | `dotnet run managing-local-files.cs` | Organizes files using AI grouping |
| PR Visualization | `dotnet run pr-visualization.cs` | Generates PR age charts |
| Persisting Sessions | `dotnet run persisting-sessions.cs` | Save and resume sessions across restarts |
### Examples with Arguments
**PR Visualization with specific repo:**
```bash
dotnet run pr-visualization.cs -- --repo github/copilot-sdk
```
**Managing Local Files (edit the file to change target folder):**
```bash
# Edit the targetFolder variable in managing-local-files.cs first
dotnet run managing-local-files.cs
```
## File-Based Apps
These examples use .NET's file-based app feature, which allows single-file C# programs to:
- Run without a project file
- Automatically reference common packages
- Support top-level statements
## Learning Resources
- [.NET File-Based Apps Documentation](https://learn.microsoft.com/en-us/dotnet/core/sdk/file-based-apps)
- [GitHub Copilot SDK Documentation](https://github.com/github/copilot-sdk/blob/main/dotnet/README.md)
- [Parent Cookbook](../README.md)

View File

@@ -0,0 +1,38 @@
#:package GitHub.Copilot.SDK@*
#:property PublishAot=false
using GitHub.Copilot.SDK;
var client = new CopilotClient();
try
{
await client.StartAsync();
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5"
});
var done = new TaskCompletionSource<string>();
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
{
done.SetResult(msg.Data.Content);
}
});
await session.SendAsync(new MessageOptions { Prompt = "Hello!" });
var response = await done.Task;
Console.WriteLine(response);
await session.DisposeAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
await client.StopAsync();
}

View File

@@ -0,0 +1,56 @@
#:package GitHub.Copilot.SDK@*
#:property PublishAot=false
using GitHub.Copilot.SDK;
// Create and start client
await using var client = new CopilotClient();
await client.StartAsync();
// Define tools for file operations
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5"
});
// Wait for completion
var done = new TaskCompletionSource();
session.On(evt =>
{
switch (evt)
{
case AssistantMessageEvent msg:
Console.WriteLine($"\nCopilot: {msg.Data.Content}");
break;
case ToolExecutionStartEvent toolStart:
Console.WriteLine($" → Running: {toolStart.Data.ToolName} ({toolStart.Data.ToolCallId})");
break;
case ToolExecutionCompleteEvent toolEnd:
Console.WriteLine($" ✓ Completed: {toolEnd.Data.ToolCallId}");
break;
case SessionIdleEvent:
done.SetResult();
break;
}
});
// Ask Copilot to organize files
// Change this to your target folder
var targetFolder = @"C:\Users\Me\Downloads";
await session.SendAsync(new MessageOptions
{
Prompt = $"""
Analyze the files in "{targetFolder}" and organize them into subfolders.
1. First, list all files and their metadata
2. Preview grouping by file extension
3. Create appropriate subfolders (e.g., "images", "documents", "videos")
4. Move each file to its appropriate subfolder
Please confirm before moving any files.
"""
});
await done.Task;

View File

@@ -0,0 +1,35 @@
#:package GitHub.Copilot.SDK@*
#:property PublishAot=false
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await client.StartAsync();
// Create multiple independent sessions
var session1 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
var session2 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
var session3 = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" });
Console.WriteLine("Created 3 independent sessions");
// Each session maintains its own conversation history
await session1.SendAsync(new MessageOptions { Prompt = "You are helping with a Python project" });
await session2.SendAsync(new MessageOptions { Prompt = "You are helping with a TypeScript project" });
await session3.SendAsync(new MessageOptions { Prompt = "You are helping with a Go project" });
Console.WriteLine("Sent initial context to all sessions");
// Follow-up messages stay in their respective contexts
await session1.SendAsync(new MessageOptions { Prompt = "How do I create a virtual environment?" });
await session2.SendAsync(new MessageOptions { Prompt = "How do I set up tsconfig?" });
await session3.SendAsync(new MessageOptions { Prompt = "How do I initialize a module?" });
Console.WriteLine("Sent follow-up questions to each session");
// Clean up all sessions
await session1.DisposeAsync();
await session2.DisposeAsync();
await session3.DisposeAsync();
Console.WriteLine("All sessions destroyed successfully");

View File

@@ -0,0 +1,38 @@
#:package GitHub.Copilot.SDK@*
#:property PublishAot=false
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await client.StartAsync();
// Create session with a memorable ID
var session = await client.CreateSessionAsync(new SessionConfig
{
SessionId = "user-123-conversation",
Model = "gpt-5"
});
await session.SendAsync(new MessageOptions { Prompt = "Let's discuss TypeScript generics" });
Console.WriteLine($"Session created: {session.SessionId}");
// Destroy session but keep data on disk
await session.DisposeAsync();
Console.WriteLine("Session destroyed (state persisted)");
// Resume the previous session
var resumed = await client.ResumeSessionAsync("user-123-conversation");
Console.WriteLine($"Resumed: {resumed.SessionId}");
await resumed.SendAsync(new MessageOptions { Prompt = "What were we discussing?" });
// List sessions
var sessions = await client.ListSessionsAsync();
Console.WriteLine("Sessions: " + string.Join(", ", sessions.Select(s => s.SessionId)));
// Delete session permanently
await client.DeleteSessionAsync("user-123-conversation");
Console.WriteLine("Session deleted");
await resumed.DisposeAsync();
await client.StopAsync();

View File

@@ -0,0 +1,204 @@
#:package GitHub.Copilot.SDK@*
#:property PublishAot=false
using System.Diagnostics;
using GitHub.Copilot.SDK;
// ============================================================================
// Git & GitHub Detection
// ============================================================================
bool IsGitRepo()
{
try
{
var proc = Process.Start(new ProcessStartInfo
{
FileName = "git",
Arguments = "rev-parse --git-dir",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
});
proc?.WaitForExit();
return proc?.ExitCode == 0;
}
catch
{
return false;
}
}
string? GetGitHubRemote()
{
try
{
var proc = Process.Start(new ProcessStartInfo
{
FileName = "git",
Arguments = "remote get-url origin",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
});
var remoteUrl = proc?.StandardOutput.ReadToEnd().Trim();
proc?.WaitForExit();
if (string.IsNullOrEmpty(remoteUrl)) return null;
// Handle SSH: git@github.com:owner/repo.git
var sshMatch = System.Text.RegularExpressions.Regex.Match(
remoteUrl, @"git@github\.com:(.+/.+?)(?:\.git)?$");
if (sshMatch.Success) return sshMatch.Groups[1].Value;
// Handle HTTPS: https://github.com/owner/repo.git
var httpsMatch = System.Text.RegularExpressions.Regex.Match(
remoteUrl, @"https://github\.com/(.+/.+?)(?:\.git)?$");
if (httpsMatch.Success) return httpsMatch.Groups[1].Value;
return null;
}
catch
{
return null;
}
}
string? ParseRepoArg(string[] args)
{
var repoIndex = Array.IndexOf(args, "--repo");
if (repoIndex != -1 && repoIndex + 1 < args.Length)
{
return args[repoIndex + 1];
}
return null;
}
string PromptForRepo()
{
Console.Write("Enter GitHub repo (owner/repo): ");
return Console.ReadLine()?.Trim() ?? "";
}
// ============================================================================
// Main Application
// ============================================================================
Console.WriteLine("🔍 PR Age Chart Generator\n");
// Determine the repository
var repo = ParseRepoArg(args);
if (!string.IsNullOrEmpty(repo))
{
Console.WriteLine($"📦 Using specified repo: {repo}");
}
else if (IsGitRepo())
{
var detected = GetGitHubRemote();
if (detected != null)
{
repo = detected;
Console.WriteLine($"📦 Detected GitHub repo: {repo}");
}
else
{
Console.WriteLine("⚠️ Git repo found but no GitHub remote detected.");
repo = PromptForRepo();
}
}
else
{
Console.WriteLine("📁 Not in a git repository.");
repo = PromptForRepo();
}
if (string.IsNullOrEmpty(repo) || !repo.Contains('/'))
{
Console.WriteLine("❌ Invalid repo format. Expected: owner/repo");
return;
}
var parts = repo.Split('/');
var owner = parts[0];
var repoName = parts[1];
// Create Copilot client - no custom tools needed!
await using var client = new CopilotClient(new CopilotClientOptions { LogLevel = "error" });
await client.StartAsync();
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
SystemMessage = new SystemMessageConfig
{
Content = $"""
<context>
You are analyzing pull requests for the GitHub repository: {owner}/{repoName}
The current working directory is: {Environment.CurrentDirectory}
</context>
<instructions>
- Use the GitHub MCP Server tools to fetch PR data
- Use your file and code execution tools to generate charts
- Save any generated images to the current working directory
- Be concise in your responses
</instructions>
"""
}
});
// Set up event handling
session.On(evt =>
{
switch (evt)
{
case AssistantMessageEvent msg:
Console.WriteLine($"\n🤖 {msg.Data.Content}\n");
break;
case ToolExecutionStartEvent toolStart:
Console.WriteLine($" ⚙️ {toolStart.Data.ToolName}");
break;
}
});
// Initial prompt - let Copilot figure out the details
Console.WriteLine("\n📊 Starting analysis...\n");
await session.SendAsync(new MessageOptions
{
Prompt = $"""
Fetch the open pull requests for {owner}/{repoName} from the last week.
Calculate the age of each PR in days.
Then generate a bar chart image showing the distribution of PR ages
(group them into sensible buckets like <1 day, 1-3 days, etc.).
Save the chart as "pr-age-chart.png" in the current directory.
Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale.
"""
});
// Interactive loop
Console.WriteLine("\n💡 Ask follow-up questions or type \"exit\" to quit.\n");
Console.WriteLine("Examples:");
Console.WriteLine(" - \"Expand to the last month\"");
Console.WriteLine(" - \"Show me the 5 oldest PRs\"");
Console.WriteLine(" - \"Generate a pie chart instead\"");
Console.WriteLine(" - \"Group by author instead of age\"");
Console.WriteLine();
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine()?.Trim();
if (string.IsNullOrEmpty(input)) continue;
if (input.ToLower() is "exit" or "quit")
{
Console.WriteLine("👋 Goodbye!");
break;
}
await session.SendAsync(new MessageOptions { Prompt = input });
}