BrandGhost
Session Hooks and Event Handling in GitHub Copilot SDK for C#

Session Hooks and Event Handling in GitHub Copilot SDK for C#

When I first started building AI-powered applications with the GitHub Copilot SDK, I quickly realized that understanding session hooks and event handling in GitHub Copilot SDK was critical for creating production-ready systems. Without proper observability into what's happening during AI interactions, you're flying blind. You can't debug effectively, you can't optimize performance, and you definitely can't meet enterprise requirements for logging and monitoring. The SDK's event model gives you the hooks you need to intercept requests, process streaming responses token by token, handle errors gracefully, and build robust telemetry pipelines. In this article, I'll show you exactly how to leverage these capabilities to build reactive, observable AI applications in C#.

Understanding Session Hooks and Event Handling in the Copilot SDK

The GitHub Copilot SDK for .NET provides a comprehensive event-driven architecture that allows you to hook into virtually every stage of the AI interaction lifecycle. This event model is designed around the principle that you need visibility and control at each phase -- from the moment a request leaves your application until the final token arrives in the response stream.

The core events available in the SDK (accessed via session.On()) include AssistantMessageEvent for complete messages, AssistantMessageDeltaEvent for streaming tokens, ToolExecutionStartEvent and ToolExecutionCompleteEvent for tool lifecycle, SessionErrorEvent for failures, SessionIdleEvent when responses complete, and SessionCompactionStartEvent/SessionCompactionCompleteEvent for context management. Each event type serves a specific purpose in the request lifecycle.

Session hooks (configured via SessionConfig.Hooks) differ from events in an important way. While events are reactive and fire after something has happened, hooks can intercept and modify the flow of execution. The OnUserPromptSubmitted hook, for example, runs before a prompt is sent, giving you the opportunity to validate, log, modify the prompt, or throw an exception to cancel the operation. The OnPreToolUse and OnPostToolUse hooks let you inspect and transform tool execution. This distinction becomes critical when you're implementing features like rate limiting, request validation, or context injection.

Source: GitHub Copilot SDK .NET Documentation

The event model integrates naturally with C# async patterns and supports both synchronous and asynchronous event handlers. When implementing session hooks and event handling in the GitHub Copilot SDK, this means you can perform async operations like database logging or external API calls within your event handlers without blocking the main request flow.

Subscribing to AssistantMessageDeltaEvent

The AssistantMessageDeltaEvent is the most frequently used event in the Copilot SDK because it gives you access to streaming responses as they arrive. When you're building interactive user interfaces or need to process responses incrementally, this event is your primary tool.

Subscribing to events happens through the session.On() method. You pass a delegate that receives event objects and use pattern matching (typically a switch expression) to handle different event types. The delta event fires once for each token or chunk of tokens received from the model, and the delta.Data.DeltaContent property contains the incremental content.

Here's a practical example that demonstrates how to subscribe to and process delta events in a C# application:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;

public class StreamingResponseHandler
{
    private readonly ILogger<StreamingResponseHandler> _logger;
    private readonly StringBuilder _completeResponse;

    public StreamingResponseHandler(ILogger<StreamingResponseHandler> logger)
    {
        _logger = logger;
        _completeResponse = new StringBuilder();
    }

    public async Task ProcessStreamingRequest(CopilotSession session, string userPrompt)
    {
        _completeResponse.Clear();

        // Subscribe to events using session.On()
        session.On(evt =>
        {
            switch (evt)
            {
                case AssistantMessageDeltaEvent delta:
                    _completeResponse.Append(delta.Data.DeltaContent);
                    _logger.LogDebug("Received token: {Token}", delta.Data.DeltaContent);
                    break;

                case SessionIdleEvent:
                    _logger.LogInformation("Complete response: {Response}", 
                        _completeResponse.ToString());
                    break;

                case SessionErrorEvent err:
                    _logger.LogError("Error during request: {Message}", err.Data.Message);
                    break;
            }
        });

        await session.SendAsync(new MessageOptions { Prompt = userPrompt });
    }
}

This pattern is incredibly powerful because it separates the concern of processing individual tokens from the overall request flow. The session.On() method can be called multiple times to register multiple event handlers, and each should be invoked for matching events. One handler might update the UI, another might accumulate tokens for logging, and a third might perform real-time analysis on the content.

Implementing Request Interceptors with SessionHooks

Request interception in the Copilot SDK happens through the SessionHooks.OnUserPromptSubmitted hook, which executes before a prompt is sent to the model. When working with session hooks and event handling in the GitHub Copilot SDK, this hook is essential for implementing cross-cutting concerns like logging, validation, authentication checks, and rate limiting. I use this hook heavily in production systems because it provides a clean separation of concerns without cluttering your business logic.

The OnUserPromptSubmitted hook receives the user's prompt string and returns a potentially modified prompt string. You can inspect the prompt, log it, validate it, transform it, or throw an exception to cancel the request entirely. This is your opportunity to implement validation logic, rate limiting, or content filtering before any network I/O occurs.

Here's a practical example that demonstrates request interception with logging, validation, and rate limiting:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;
using System.Threading.RateLimiting;

public class RequestInterceptorService
{
    private readonly ILogger<RequestInterceptorService> _logger;
    private readonly RateLimiter _rateLimiter;

    public RequestInterceptorService(ILogger<RequestInterceptorService> logger)
    {
        _logger = logger;
        
        // Configure a token bucket rate limiter
        _rateLimiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
        {
            TokenLimit = 10,
            ReplenishmentPeriod = TimeSpan.FromMinutes(1),
            TokensPerPeriod = 10,
            QueueLimit = 5
        });
    }

    public SessionConfig ConfigureSession()
    {
        // Request interception and modification happens via SessionHooks.OnUserPromptSubmitted
        return new SessionConfig
        {
            Model = "gpt-5",
            Hooks = new SessionHooks
            {
                OnUserPromptSubmitted = async (prompt) =>
                {
                    _logger.LogInformation("Sending request with prompt length: {Length}",
                        prompt.Length);

                    // Validate the prompt
                    if (!ValidatePrompt(prompt))
                    {
                        _logger.LogWarning("Prompt validation failed");
                        throw new InvalidOperationException("Prompt validation failed");
                    }

                    // Apply rate limiting
                    using var lease = await _rateLimiter.AcquireAsync(1);
                    if (!lease.IsAcquired)
                    {
                        _logger.LogWarning("Rate limit exceeded, request blocked");
                        throw new InvalidOperationException("Rate limit exceeded");
                    }

                    _logger.LogDebug("Prompt content length: {Length}", prompt.Length);

                    // Return potentially modified prompt
                    return prompt;
                }
            }
        };
    }

    private bool ValidatePrompt(string prompt)
    {
        // Implement your validation logic
        if (string.IsNullOrWhiteSpace(prompt))
        {
            return false;
        }

        // Validate prompt length
        if (prompt.Length > 32000)
        {
            return false;
        }

        return true;
    }
}

This hook pattern is particularly valuable because it runs before the prompt is sent to the model. If your validation fails or rate limiting kicks in, you've saved the round trip to the API. The OnUserPromptSubmitted hook returns a potentially modified prompt, allowing you to inject additional context, modify the user's input, or implement content filtering. Note that to fully cancel a request, you would throw an exception from the hook.

Handling Tool Call Events

When you register tools with the Copilot SDK, the model can decide to invoke those tools during the conversation flow. The SDK exposes events that fire throughout the tool call lifecycle, giving you visibility into when tools are selected, what parameters are being passed, and what results are returned.

Tool call events are critical for debugging and observability because they show you exactly how the model is using your registered tools. In my experience, this is where you'll catch most issues with tool definitions -- incorrect parameter schemas, missing required fields, or tools being invoked in unexpected sequences.

The tool call lifecycle includes three key phases captured by events. First, the ToolExecutionStartEvent fires when the model decides to invoke a tool. This provides the tool name via e.Data.ToolName. Second, your tool implementation executes (you can intercept this with SessionHooks.OnPreToolUse and OnPostToolUse). Third, the ToolExecutionCompleteEvent fires when the tool completes. Errors during tool execution may be captured via SessionErrorEvent (event ordering is based on observed SDK behavior in v0.x -- this is not formally guaranteed in official documentation and may change).

Here's an example that demonstrates comprehensive tool call event handling:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;
using System.Diagnostics;

public class ToolCallObserver
{
    private readonly ILogger<ToolCallObserver> _logger;
    private readonly Dictionary<string, Stopwatch> _toolTimings;

    public ToolCallObserver(ILogger<ToolCallObserver> logger)
    {
        _logger = logger;
        _toolTimings = new Dictionary<string, Stopwatch>();
    }

    public void AttachToSession(CopilotSession session)
    {
        session.On(evt =>
        {
            switch (evt)
            {
                case ToolExecutionStartEvent toolStart:
                    OnToolExecutionStart(toolStart);
                    break;

                case ToolExecutionCompleteEvent toolComplete:
                    OnToolExecutionComplete(toolComplete);
                    break;

                case SessionErrorEvent err when err.Data.Message.Contains("tool"):
                    OnToolError(err);
                    break;
            }
        });
    }

    private void OnToolExecutionStart(ToolExecutionStartEvent e)
    {
        var toolName = e.Data.ToolName;
        _logger.LogInformation("Tool execution started: {ToolName}", toolName);

        // Log the parameters (sanitize sensitive data in production)
        _logger.LogDebug("Tool name: {ToolName}", toolName);

        // Start timing the tool execution
        var stopwatch = Stopwatch.StartNew();
        _toolTimings[toolName] = stopwatch;
    }

    private void OnToolExecutionComplete(ToolExecutionCompleteEvent e)
    {
        var toolName = e.Data.ToolName;

        if (_toolTimings.TryGetValue(toolName, out var stopwatch))
        {
            stopwatch.Stop();
            _logger.LogInformation("Tool {ToolName} completed in {Duration}ms",
                toolName, stopwatch.ElapsedMilliseconds);
            _toolTimings.Remove(toolName);
        }

        _logger.LogDebug("Tool {ToolName} execution complete", toolName);
    }

    private void OnToolError(SessionErrorEvent e)
    {
        _logger.LogError("Tool execution failed: {ErrorMessage}", e.Data.Message);
    }
}

This observer pattern is invaluable during development because you can see exactly what the model is trying to do with your tools. In production, these events feed into your observability pipeline and help you identify performance bottlenecks or misconfigurations.

Error Handling and Fault Tolerance

Error events in the Copilot SDK provide structured information about failures at any stage of the request lifecycle. These events are your first line of defense for building resilient AI applications that can gracefully handle transient failures, rate limiting, and unexpected errors.

The SDK distinguishes between different error types through specialized event args that include error codes, messages, and whether the error is retryable (verify current error handling contracts in the SDK repository). Understanding these distinctions is critical because your response should differ based on the error type. A transient network error warrants a retry with exponential backoff, while an authentication error requires immediate user intervention.

Here's a comprehensive example implementing error handling with retry patterns:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;

public class ResilientCopilotService
{
    private readonly ILogger<ResilientCopilotService> _logger;
    private readonly AsyncRetryPolicy _retryPolicy;

    public ResilientCopilotService(ILogger<ResilientCopilotService> logger)
    {
        _logger = logger;
        
        _retryPolicy = Policy
            .Handle<Exception>()
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt => 
                    TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (exception, timeSpan, retryCount, context) =>
                {
                    _logger.LogWarning(exception,
                        "Retry {RetryCount} after {Delay}s due to: {Message}",
                        retryCount, timeSpan.TotalSeconds, exception.Message);
                });
    }

    public async Task<string> SendRequestWithRetry(
        CopilotSession session, 
        string userMessage)
    {
        var result = new StringBuilder();
        Exception? lastError = null;

        try
        {
            await _retryPolicy.ExecuteAsync(async () =>
            {
                result.Clear();
                lastError = null;

                session.On(evt =>
                {
                    switch (evt)
                    {
                        case AssistantMessageDeltaEvent delta:
                            result.Append(delta.Data.DeltaContent);
                            break;

                        case SessionErrorEvent err:
                            lastError = new Exception(err.Data.Message);
                            _logger.LogError("Error during request: {Message}", err.Data.Message);
                            break;

                        case SessionIdleEvent when lastError != null:
                            throw lastError;
                    }
                });

                await session.SendAsync(new MessageOptions { Prompt = userMessage });

                if (lastError != null)
                {
                    throw lastError;
                }
            });

            return result.ToString();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Request failed after retries, implementing graceful degradation");
            return GetFallbackResponse();
        }
    }

    private string GetFallbackResponse()
    {
        return "I apologize, but I'm unable to process your request at this time. Please try again later.";
    }
}

This pattern combines event-driven error detection with policy-based retry logic. The error event gives you immediate visibility into what went wrong, while the Polly retry policy handles the mechanics of retrying with exponential backoff. Together, they create a robust fault tolerance layer that's essential for production systems.

Building an Observability Layer

A production-ready AI application needs comprehensive observability, and the Copilot SDK's event model provides the perfect foundation for building a unified telemetry pipeline. By composing multiple event handlers, you can capture metrics, traces, and logs that feed into your monitoring infrastructure.

The key to effective observability is standardization. Rather than scattering logging statements throughout your code, you create a centralized observer that subscribes to all relevant events and emits structured telemetry. This approach integrates naturally with OpenTelemetry, which has become the standard for distributed tracing and metrics in modern .NET applications.

Here's an example that demonstrates building a comprehensive observability layer:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Diagnostics.Metrics;

public class CopilotObservabilityLayer
{
    private readonly ILogger<CopilotObservabilityLayer> _logger;
    private readonly ActivitySource _activitySource;
    private readonly Meter _meter;
    private readonly Counter<long> _requestCounter;
    private readonly Histogram<double> _requestDuration;
    private readonly Counter<long> _tokenCounter;

    public CopilotObservabilityLayer(
        ILogger<CopilotObservabilityLayer> logger,
        string serviceName = "CopilotService")
    {
        _logger = logger;
        _activitySource = new ActivitySource(serviceName);
        _meter = new Meter(serviceName);

        // Define metrics
        _requestCounter = _meter.CreateCounter<long>(
            "copilot.requests.total",
            description: "Total number of Copilot API requests");

        _requestDuration = _meter.CreateHistogram<double>(
            "copilot.requests.duration",
            unit: "ms",
            description: "Duration of Copilot API requests");

        _tokenCounter = _meter.CreateCounter<long>(
            "copilot.tokens.total",
            description: "Total tokens received from Copilot API");
    }

    public SessionConfig AttachToSession()
    {
        return new SessionConfig
        {
            Model = "gpt-5",
            Hooks = new SessionHooks
            {
                OnSessionStart = async () =>
                {
                    var activity = _activitySource.StartActivity("CopilotRequest");
                    _requestCounter.Add(1, new KeyValuePair<string, object?>("status", "started"));
                    _logger.LogInformation("Session started at {Timestamp}", DateTime.UtcNow);
                    await Task.CompletedTask;
                },

                OnSessionEnd = async () =>
                {
                    _requestCounter.Add(1, new KeyValuePair<string, object?>("status", "completed"));
                    _logger.LogInformation("Session ended");
                    Activity.Current?.Stop();
                    await Task.CompletedTask;
                },

                OnErrorOccurred = async (error) =>
                {
                    _requestCounter.Add(1, 
                        new KeyValuePair<string, object?>("status", "error"));

                    _logger.LogError("Session error: {Message}", error.Message);
                    Activity.Current?.SetStatus(ActivityStatusCode.Error, error.Message);
                    await Task.CompletedTask;
                }
            }
        };
    }

    public void AttachEventHandlers(CopilotSession session)
    {
        var requestStart = DateTime.UtcNow;
        var tokenCount = 0L;

        session.On(evt =>
        {
            switch (evt)
            {
                case AssistantMessageDeltaEvent delta:
                    var tokens = delta.Data.DeltaContent?.Length ?? 0;
                    tokenCount += tokens;
                    _tokenCounter.Add(tokens);
                    _logger.LogTrace("Received {Tokens} tokens", tokens);
                    break;

                case SessionIdleEvent:
                    var duration = (DateTime.UtcNow - requestStart).TotalMilliseconds;
                    _requestDuration.Record(duration, 
                        new KeyValuePair<string, object?>("status", "completed"));
                    _logger.LogInformation(
                        "Request completed in {Duration}ms with {TotalTokens} total tokens",
                        duration, tokenCount);
                    break;

                case SessionErrorEvent err:
                    var errorDuration = (DateTime.UtcNow - requestStart).TotalMilliseconds;
                    _requestDuration.Record(errorDuration,
                        new KeyValuePair<string, object?>("status", "error"));
                    _logger.LogError("Request failed after {Duration}ms: {Message}",
                        errorDuration, err.Data.Message);
                    break;
            }
        });
    }
}

This observability layer captures the complete picture of your AI interactions. The metrics feed into dashboards showing request rates, latency distributions, and error rates. The traces provide detailed visibility into individual requests, including tool calls and streaming behavior. The structured logs give you the details needed for debugging specific issues.

I recommend integrating this with the OpenTelemetry .NET SDK for automatic export to your monitoring backend of choice. Whether you're using Application Insights, Datadog, or Prometheus, this pattern gives you production-grade observability with minimal overhead.

Session Lifecycle Hooks

Session lifecycle events provide hooks into the creation and disposal of CopilotSession instances. These events are critical for resource management, connection pooling, audit trails, and ensuring proper cleanup of resources associated with AI interactions.

The lifecycle events include session created, session started, session idle, and session disposed. Each event serves a specific purpose in managing the session's lifetime. Session created fires when a new session instance is constructed but before any requests are sent. Session started fires when the first request begins. Session idle fires after a configurable period of inactivity. Session disposed fires during cleanup and is your opportunity to release resources (event ordering is based on observed SDK behavior in v0.x -- this is not formally guaranteed in official documentation and may change).

Here's an example implementing session lifecycle management with audit trails:

using GitHub.Copilot.SDK;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;

public class SessionLifecycleManager
{
    private readonly ILogger<SessionLifecycleManager> _logger;
    private readonly ConcurrentDictionary<string, SessionAuditRecord> _auditTrail;

    public SessionLifecycleManager(ILogger<SessionLifecycleManager> logger)
    {
        _logger = logger;
        _auditTrail = new ConcurrentDictionary<string, SessionAuditRecord>();
    }

    public SessionConfig ConfigureSession(string userId)
    {
        var sessionId = Guid.NewGuid().ToString();

        return new SessionConfig
        {
            Model = "gpt-5",
            Hooks = new SessionHooks
            {
                OnSessionStart = async () =>
                {
                    var record = new SessionAuditRecord
                    {
                        SessionId = sessionId,
                        UserId = userId,
                        CreatedAt = DateTime.UtcNow,
                        RequestCount = 0
                    };

                    _auditTrail[sessionId] = record;

                    _logger.LogInformation("Session {SessionId} created for user {UserId}",
                        sessionId, userId);

                    await Task.CompletedTask;
                },

                OnUserPromptSubmitted = async (prompt) =>
                {
                    if (_auditTrail.TryGetValue(sessionId, out var record))
                    {
                        record.RequestCount++;
                        record.LastRequestAt = DateTime.UtcNow;
                    }
                    return prompt;
                },

                OnSessionEnd = async () =>
                {
                    if (_auditTrail.TryRemove(sessionId, out var record))
                    {
                        record.DisposedAt = DateTime.UtcNow;
                        record.Duration = record.DisposedAt - record.CreatedAt;

                        _logger.LogInformation(
                            "Session {SessionId} disposed after {Duration}s with {RequestCount} requests",
                            sessionId, record.Duration.TotalSeconds, record.RequestCount);

                        await PersistAuditRecord(record);
                    }
                }
            }
        };
    }

    private async Task PersistAuditRecord(SessionAuditRecord record)
    {
        // Implementation would save to database
        await Task.CompletedTask;
    }

    private class SessionAuditRecord
    {
        public string SessionId { get; set; } = string.Empty;
        public string UserId { get; set; } = string.Empty;
        public DateTime CreatedAt { get; set; }
        public DateTime? LastRequestAt { get; set; }
        public DateTime? DisposedAt { get; set; }
        public TimeSpan Duration { get; set; }
        public int RequestCount { get; set; }
    }
}

This pattern ensures that every session is tracked from creation to disposal. The audit trail captures essential information about usage patterns, which is valuable for both operational monitoring and compliance requirements. In production systems, I always implement session lifecycle hooks because they provide the foundation for resource management and capacity planning.

For more details on managing sessions effectively, see my article on Managing Sessions and Context in GitHub Copilot SDK.

Frequently Asked Questions

What session hooks are available in the GitHub Copilot SDK?

The GitHub Copilot SDK provides hooks via SessionConfig.Hooks and events via session.On(). Hooks (SessionHooks) include: OnSessionStart, OnSessionEnd, OnUserPromptSubmitted (for modifying prompts before they're sent), OnPreToolUse and OnPostToolUse (for tool execution), and OnErrorOccurred. Events accessed via session.On() include: AssistantMessageEvent, AssistantMessageDeltaEvent, SessionIdleEvent, SessionErrorEvent, ToolExecutionStartEvent, ToolExecutionCompleteEvent, SessionCompactionStartEvent, and SessionCompactionCompleteEvent. Hooks can modify the flow, while events provide observability.

How do I add multiple event handlers in the Copilot SDK?

You can call session.On() multiple times to register multiple event handlers. Each handler should be invoked when matching events fire. For example, you could register separate handlers for logging, metrics collection, and UI updates:

session.On(evt => { /* logging handler */ });
session.On(evt => { /* metrics handler */ });
session.On(evt => { /* UI update handler */ });

All registered handlers should be invoked in most cases for relevant events. I recommend creating wrapper classes that encapsulate related event handling logic to keep your code organized and maintainable.

Can I cancel a request from a hook?

Yes, you can cancel a request by throwing an exception from the OnUserPromptSubmitted hook in SessionHooks. This prevents the request from being sent to the API. This is useful for implementing validation, rate limiting, or circuit breaker patterns. However, once a request has been sent and streaming has begun, you cannot cancel it from an event handler. For streaming requests, you need to handle cancellation at the session level or through proper session disposal.

How do hooks relate to middleware in ASP.NET Core?

The concept is similar but the implementation differs. ASP.NET Core middleware forms a pipeline where each component can process the request, call the next middleware, and then process the response. The Copilot SDK's hooks (via SessionHooks) are more callback-oriented -- you provide delegates that execute at specific points in the lifecycle. You can think of OnUserPromptSubmitted as similar to pre-processing middleware. The event system (via session.On()) is multicast, meaning multiple independent handlers can subscribe to the same event types, whereas middleware typically forms a chain. If you're familiar with middleware patterns from ASP.NET Core, you'll find the SDK's hook and event model intuitive, just with a different execution model.

Conclusion

Session hooks and event handling in the GitHub Copilot SDK provide the foundation for building production-ready AI applications in C#. The event-driven architecture gives you comprehensive visibility into every stage of the AI interaction lifecycle, from request validation through streaming responses to error handling and session cleanup. By leveraging these hooks effectively, you can implement observability, fault tolerance, audit trails, and reactive UI patterns that make your AI applications robust and maintainable.

The patterns I've demonstrated in this article -- delta event processing, request interception, tool call observation, error handling, and lifecycle management -- form the core of how I build with the SDK in production. These aren't optional niceties; they're essential capabilities for any system that needs to be reliable, debuggable, and observable.

For a comprehensive overview of the SDK's capabilities, start with the GitHub Copilot SDK for .NET: Complete Developer Guide. To understand the foundational concepts, read CopilotClient and CopilotSession: Core Concepts in C#. For more advanced patterns that build on these hooks, see Advanced GitHub Copilot SDK: Tools, Hooks, and Multi-Agent. You can find the official SDK documentation and examples in the GitHub Copilot SDK .NET repository.

Getting Started with GitHub Copilot SDK in C#: Installation, Setup, and First Conversation

Getting started with GitHub Copilot SDK in C#: master installation, CopilotClient setup, streaming responses, and build your first .NET AI app.

Custom AI Tools with AIFunctionFactory in GitHub Copilot SDK for C#

Learn to build custom AI tools with AIFunctionFactory in GitHub Copilot SDK for C#. Working code examples and best practices included.

GitHub Copilot SDK for .NET: Complete Developer Guide

Learn the GitHub Copilot SDK for .NET in this complete developer guide. Build custom AI agents with CopilotClient, CopilotSession, streaming, tools, and multi-model support in C#.

An error has occurred. This application may no longer respond until reloaded. Reload