diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index d721e208ff..cbddb41dad 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -89,6 +89,7 @@
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 8b1b00fd2b..2a9b81d44b 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -65,6 +65,7 @@
+
@@ -395,6 +396,7 @@
+
@@ -424,6 +426,7 @@
+
@@ -438,6 +441,7 @@
+
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Agent_With_GithubCopilot.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Agent_With_GithubCopilot.csproj
new file mode 100644
index 0000000000..69d7086d5e
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Agent_With_GithubCopilot.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Program.cs
new file mode 100644
index 0000000000..b233259dcc
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Program.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create a GitHub Copilot agent with shell command permissions.
+
+using GitHub.Copilot.SDK;
+using Microsoft.Agents.AI;
+
+// Permission handler that prompts the user for approval
+static Task PromptPermission(PermissionRequest request, PermissionInvocation invocation)
+{
+ Console.WriteLine($"\n[Permission Request: {request.Kind}]");
+ Console.Write("Approve? (y/n): ");
+
+ string? input = Console.ReadLine()?.Trim().ToUpperInvariant();
+ string kind = input is "Y" or "YES" ? "approved" : "denied-interactively-by-user";
+
+ return Task.FromResult(new PermissionRequestResult { Kind = kind });
+}
+
+// Create and start a Copilot client
+await using CopilotClient copilotClient = new();
+await copilotClient.StartAsync();
+
+// Create an agent with a session config that enables permission handling
+SessionConfig sessionConfig = new()
+{
+ OnPermissionRequest = PromptPermission,
+};
+
+AIAgent agent = copilotClient.AsAIAgent(sessionConfig, ownsClient: true);
+
+// Toggle between streaming and non-streaming modes
+bool useStreaming = true;
+
+string prompt = "List all files in the current directory";
+Console.WriteLine($"User: {prompt}\n");
+
+if (useStreaming)
+{
+ await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(prompt))
+ {
+ Console.Write(update);
+ }
+
+ Console.WriteLine();
+}
+else
+{
+ AgentResponse response = await agent.RunAsync(prompt);
+ Console.WriteLine(response);
+}
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/README.md
new file mode 100644
index 0000000000..885988dbcb
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/README.md
@@ -0,0 +1,76 @@
+# Prerequisites
+
+> **⚠️ WARNING: Container Recommendation**
+>
+> GitHub Copilot can execute tools and commands that may interact with your system. For safety, it is strongly recommended to run this sample in a containerized environment (e.g., Docker, Dev Container) to avoid unintended consequences to your machine.
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 10 SDK or later
+- GitHub Copilot CLI installed and available in your PATH (or provide a custom path)
+
+## Setting up GitHub Copilot CLI
+
+To use this sample, you need to have the GitHub Copilot CLI installed. You can install it by following the instructions at:
+https://github.com/github/copilot-sdk
+
+Once installed, ensure the `copilot` command is available in your PATH, or configure a custom path using `CopilotClientOptions`.
+
+## Running the Sample
+
+No additional environment variables are required if using default configuration. The sample will:
+
+1. Create a GitHub Copilot client with default options
+2. Create an AI agent using the Copilot SDK
+3. Send a message to the agent
+4. Display the response
+
+Run the sample:
+
+```powershell
+dotnet run
+```
+
+## Advanced Usage
+
+You can customize the agent by providing additional configuration:
+
+```csharp
+using GitHub.Copilot.SDK;
+using Microsoft.Agents.AI;
+
+// Create and start a Copilot client
+await using CopilotClient copilotClient = new();
+await copilotClient.StartAsync();
+
+// Create session configuration with specific model
+SessionConfig sessionConfig = new()
+{
+ Model = "claude-opus-4.5",
+ Streaming = false
+};
+
+// Create an agent with custom configuration using the extension method
+AIAgent agent = copilotClient.AsAIAgent(
+ sessionConfig,
+ ownsClient: true,
+ id: "my-copilot-agent",
+ name: "My Copilot Assistant",
+ description: "A helpful AI assistant powered by GitHub Copilot"
+);
+
+// Use the agent - ask it to write code for us
+AgentResponse response = await agent.RunAsync("Write a small .NET 10 C# hello world single file application");
+Console.WriteLine(response);
+```
+
+## Streaming Responses
+
+To get streaming responses:
+
+```csharp
+await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a C# function to calculate Fibonacci numbers"))
+{
+ Console.Write(update.Text);
+}
+```
diff --git a/dotnet/samples/GettingStarted/AgentProviders/README.md b/dotnet/samples/GettingStarted/AgentProviders/README.md
index 964e560c9a..4b276215db 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/README.md
@@ -22,6 +22,7 @@ See the README.md for each sample for the prerequisites for that sample.
|[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service|
|[Creating an AIAgent with Azure OpenAI Responses](./Agent_With_AzureOpenAIResponses/)|This sample demonstrates how to create an AIAgent using Azure OpenAI Responses as the underlying inference service|
|[Creating an AIAgent with a custom implementation](./Agent_With_CustomImplementation/)|This sample demonstrates how to create an AIAgent with a custom implementation|
+|[Creating an AIAgent with GitHub Copilot](./Agent_With_GithubCopilot/)|This sample demonstrates how to create an AIAgent using GitHub Copilot SDK as the underlying inference service|
|[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service|
|[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service|
|[Creating an AIAgent with OpenAI Assistants](./Agent_With_OpenAIAssistants/)|This sample demonstrates how to create an AIAgent using OpenAI Assistants as the underlying inference service.WARNING: The Assistants API is deprecated and will be shut down. For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration|
diff --git a/dotnet/src/Microsoft.Agents.AI.GithubCopilot/CopilotClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/CopilotClientExtensions.cs
new file mode 100644
index 0000000000..ea20d46c46
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/CopilotClientExtensions.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.GithubCopilot;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace GitHub.Copilot.SDK;
+
+///
+/// Provides extension methods for
+/// to simplify the creation of GitHub Copilot agents.
+///
+///
+/// These extensions bridge the gap between GitHub Copilot SDK client objects
+/// and the Microsoft Agent Framework.
+///
+/// They allow developers to easily create AI agents that can interact
+/// with GitHub Copilot by handling the conversion from Copilot clients to
+/// instances that implement the interface.
+///
+///
+public static class CopilotClientExtensions
+{
+ ///
+ /// Retrieves an instance of for a GitHub Copilot client.
+ ///
+ /// The to use for the agent.
+ /// Optional session configuration for the agent.
+ /// Whether the agent owns the client and should dispose it. Default is false.
+ /// The unique identifier for the agent.
+ /// The name of the agent.
+ /// The description of the agent.
+ /// An instance backed by the GitHub Copilot client.
+ public static AIAgent AsAIAgent(
+ this CopilotClient client,
+ SessionConfig? sessionConfig = null,
+ bool ownsClient = false,
+ string? id = null,
+ string? name = null,
+ string? description = null)
+ {
+ Throw.IfNull(client);
+
+ return new GithubCopilotAgent(client, sessionConfig, ownsClient, id, name, description);
+ }
+
+ ///
+ /// Retrieves an instance of for a GitHub Copilot client.
+ ///
+ /// The to use for the agent.
+ /// Whether the agent owns the client and should dispose it. Default is false.
+ /// The unique identifier for the agent.
+ /// The name of the agent.
+ /// The description of the agent.
+ /// The tools to make available to the agent.
+ /// Optional instructions to append as a system message.
+ /// An instance backed by the GitHub Copilot client.
+ public static AIAgent AsAIAgent(
+ this CopilotClient client,
+ bool ownsClient = false,
+ string? id = null,
+ string? name = null,
+ string? description = null,
+ IList? tools = null,
+ string? instructions = null)
+ {
+ Throw.IfNull(client);
+
+ return new GithubCopilotAgent(client, ownsClient, id, name, description, tools, instructions);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgent.cs b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgent.cs
new file mode 100644
index 0000000000..481410b835
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgent.cs
@@ -0,0 +1,470 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using GitHub.Copilot.SDK;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.GithubCopilot;
+
+///
+/// Represents an that uses the GitHub Copilot SDK to provide agentic capabilities.
+///
+public sealed class GithubCopilotAgent : AIAgent, IAsyncDisposable
+{
+ private const string DefaultName = "GitHub Copilot Agent";
+ private const string DefaultDescription = "An AI agent powered by GitHub Copilot";
+
+ private readonly CopilotClient _copilotClient;
+ private readonly string? _id;
+ private readonly string _name;
+ private readonly string _description;
+ private readonly SessionConfig? _sessionConfig;
+ private readonly bool _ownsClient;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Copilot client to use for interacting with GitHub Copilot.
+ /// Optional session configuration for the agent.
+ /// Whether the agent owns the client and should dispose it. Default is false.
+ /// The unique identifier for the agent.
+ /// The name of the agent.
+ /// The description of the agent.
+ public GithubCopilotAgent(
+ CopilotClient copilotClient,
+ SessionConfig? sessionConfig = null,
+ bool ownsClient = false,
+ string? id = null,
+ string? name = null,
+ string? description = null)
+ {
+ _ = Throw.IfNull(copilotClient);
+
+ this._copilotClient = copilotClient;
+ this._sessionConfig = sessionConfig;
+ this._ownsClient = ownsClient;
+ this._id = id;
+ this._name = name ?? DefaultName;
+ this._description = description ?? DefaultDescription;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Copilot client to use for interacting with GitHub Copilot.
+ /// Whether the agent owns the client and should dispose it. Default is false.
+ /// The unique identifier for the agent.
+ /// The name of the agent.
+ /// The description of the agent.
+ /// The tools to make available to the agent.
+ /// Optional instructions to append as a system message.
+ public GithubCopilotAgent(
+ CopilotClient copilotClient,
+ bool ownsClient = false,
+ string? id = null,
+ string? name = null,
+ string? description = null,
+ IList? tools = null,
+ string? instructions = null)
+ : this(
+ copilotClient,
+ GetSessionConfig(tools, instructions),
+ ownsClient,
+ id,
+ name,
+ description)
+ {
+ }
+
+ ///
+ public sealed override ValueTask GetNewSessionAsync(CancellationToken cancellationToken = default)
+ => new(new GithubCopilotAgentSession());
+
+ ///
+ /// Get a new instance using an existing session id, to continue that conversation.
+ ///
+ /// The session id to continue.
+ /// A new instance.
+ public ValueTask GetNewSessionAsync(string sessionId)
+ => new(new GithubCopilotAgentSession() { SessionId = sessionId });
+
+ ///
+ public override ValueTask DeserializeSessionAsync(
+ JsonElement serializedSession,
+ JsonSerializerOptions? jsonSerializerOptions = null,
+ CancellationToken cancellationToken = default)
+ => new(new GithubCopilotAgentSession(serializedSession, jsonSerializerOptions));
+
+ ///
+ protected override Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ CancellationToken cancellationToken = default)
+ => this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
+
+ ///
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session = null,
+ AgentRunOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(messages);
+
+ // Ensure we have a valid session
+ session ??= await this.GetNewSessionAsync(cancellationToken).ConfigureAwait(false);
+ if (session is not GithubCopilotAgentSession typedSession)
+ {
+ throw new InvalidOperationException(
+ $"The provided session type {session.GetType()} is not compatible with the agent. Only GitHub Copilot agent created sessions are supported.");
+ }
+
+ // Ensure the client is started
+ await this.EnsureClientStartedAsync(cancellationToken).ConfigureAwait(false);
+
+ // Create or resume a session with streaming enabled
+ SessionConfig sessionConfig = this._sessionConfig != null
+ ? new SessionConfig
+ {
+ Model = this._sessionConfig.Model,
+ Tools = this._sessionConfig.Tools,
+ SystemMessage = this._sessionConfig.SystemMessage,
+ AvailableTools = this._sessionConfig.AvailableTools,
+ ExcludedTools = this._sessionConfig.ExcludedTools,
+ Provider = this._sessionConfig.Provider,
+ OnPermissionRequest = this._sessionConfig.OnPermissionRequest,
+ McpServers = this._sessionConfig.McpServers,
+ CustomAgents = this._sessionConfig.CustomAgents,
+ SkillDirectories = this._sessionConfig.SkillDirectories,
+ DisabledSkills = this._sessionConfig.DisabledSkills,
+ Streaming = true
+ }
+ : new SessionConfig { Streaming = true };
+
+ CopilotSession copilotSession;
+ if (typedSession.SessionId is not null)
+ {
+ copilotSession = await this._copilotClient.ResumeSessionAsync(
+ typedSession.SessionId,
+ this.CreateResumeConfig(),
+ cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ copilotSession = await this._copilotClient.CreateSessionAsync(sessionConfig, cancellationToken).ConfigureAwait(false);
+ typedSession.SessionId = copilotSession.SessionId;
+ }
+
+ try
+ {
+ Channel channel = Channel.CreateUnbounded();
+
+ // Subscribe to session events
+ using IDisposable subscription = copilotSession.On(evt =>
+ {
+ switch (evt)
+ {
+ case AssistantMessageDeltaEvent deltaEvent:
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(deltaEvent));
+ break;
+
+ case AssistantMessageEvent assistantMessage:
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(assistantMessage));
+ break;
+
+ case AssistantUsageEvent usageEvent:
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(usageEvent));
+ break;
+
+ case SessionIdleEvent idleEvent:
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(idleEvent));
+ channel.Writer.TryComplete();
+ break;
+
+ case SessionErrorEvent errorEvent:
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(errorEvent));
+ channel.Writer.TryComplete(new InvalidOperationException(
+ $"Session error: {errorEvent.Data?.Message ?? "Unknown error"}"));
+ break;
+
+ default:
+ // Handle all other event types by storing as RawRepresentation
+ channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(evt));
+ break;
+ }
+ });
+
+ List tempFiles = [];
+ try
+ {
+ // Build prompt from text content
+ string prompt = string.Join("\n", messages.Select(m => m.Text));
+
+ // Handle DataContent as attachments
+ List? attachments = await ProcessDataContentAttachmentsAsync(
+ messages,
+ tempFiles,
+ cancellationToken).ConfigureAwait(false);
+
+ // Send the message with attachments
+ MessageOptions messageOptions = new() { Prompt = prompt };
+ if (attachments is not null)
+ {
+ messageOptions.Attachments = [.. attachments];
+ }
+
+ await copilotSession.SendAsync(messageOptions, cancellationToken).ConfigureAwait(false);
+ // Yield updates as they arrive
+ await foreach (AgentResponseUpdate update in channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
+ {
+ yield return update;
+ }
+ }
+ finally
+ {
+ CleanupTempFiles(tempFiles);
+ }
+ }
+ finally
+ {
+ await copilotSession.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+
+ ///
+ protected override string? IdCore => this._id;
+
+ ///
+ public override string Name => this._name;
+
+ ///
+ public override string Description => this._description;
+
+ ///
+ /// Disposes the agent and releases resources.
+ ///
+ /// A value task representing the asynchronous dispose operation.
+ public async ValueTask DisposeAsync()
+ {
+ if (this._ownsClient)
+ {
+ await this._copilotClient.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+
+ private async Task EnsureClientStartedAsync(CancellationToken cancellationToken)
+ {
+ if (this._copilotClient.State != ConnectionState.Connected)
+ {
+ await this._copilotClient.StartAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private ResumeSessionConfig CreateResumeConfig()
+ {
+ return new ResumeSessionConfig
+ {
+ Tools = this._sessionConfig?.Tools,
+ Provider = this._sessionConfig?.Provider,
+ OnPermissionRequest = this._sessionConfig?.OnPermissionRequest,
+ McpServers = this._sessionConfig?.McpServers,
+ CustomAgents = this._sessionConfig?.CustomAgents,
+ SkillDirectories = this._sessionConfig?.SkillDirectories,
+ DisabledSkills = this._sessionConfig?.DisabledSkills,
+ Streaming = true
+ };
+ }
+
+ private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantMessageDeltaEvent deltaEvent)
+ {
+ TextContent textContent = new(deltaEvent.Data?.DeltaContent ?? string.Empty)
+ {
+ RawRepresentation = deltaEvent
+ };
+
+ return new AgentResponseUpdate(ChatRole.Assistant, [textContent])
+ {
+ AgentId = this.Id,
+ MessageId = deltaEvent.Data?.MessageId,
+ CreatedAt = deltaEvent.Timestamp
+ };
+ }
+
+ private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantMessageEvent assistantMessage)
+ {
+ TextContent textContent = new(assistantMessage.Data?.Content ?? string.Empty)
+ {
+ RawRepresentation = assistantMessage
+ };
+
+ return new AgentResponseUpdate(ChatRole.Assistant, [textContent])
+ {
+ AgentId = this.Id,
+ ResponseId = assistantMessage.Data?.MessageId,
+ MessageId = assistantMessage.Data?.MessageId,
+ CreatedAt = assistantMessage.Timestamp
+ };
+ }
+
+ private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantUsageEvent usageEvent)
+ {
+ UsageDetails usageDetails = new()
+ {
+ InputTokenCount = (int?)(usageEvent.Data?.InputTokens),
+ OutputTokenCount = (int?)(usageEvent.Data?.OutputTokens),
+ TotalTokenCount = (int?)((usageEvent.Data?.InputTokens ?? 0) + (usageEvent.Data?.OutputTokens ?? 0)),
+ CachedInputTokenCount = (int?)(usageEvent.Data?.CacheReadTokens),
+ AdditionalCounts = GetAdditionalCounts(usageEvent),
+ };
+
+ UsageContent usageContent = new(usageDetails)
+ {
+ RawRepresentation = usageEvent
+ };
+
+ return new AgentResponseUpdate(ChatRole.Assistant, [usageContent])
+ {
+ AgentId = this.Id,
+ CreatedAt = usageEvent.Timestamp
+ };
+ }
+
+ private static AdditionalPropertiesDictionary? GetAdditionalCounts(AssistantUsageEvent usageEvent)
+ {
+ if (usageEvent.Data is null)
+ {
+ return null;
+ }
+
+ AdditionalPropertiesDictionary? additionalCounts = null;
+
+ if (usageEvent.Data.CacheWriteTokens is double cacheWriteTokens)
+ {
+ additionalCounts ??= [];
+ additionalCounts[nameof(AssistantUsageData.CacheWriteTokens)] = (long)cacheWriteTokens;
+ }
+
+ if (usageEvent.Data.Cost is double cost)
+ {
+ additionalCounts ??= [];
+ additionalCounts[nameof(AssistantUsageData.Cost)] = (long)cost;
+ }
+
+ if (usageEvent.Data.Duration is double duration)
+ {
+ additionalCounts ??= [];
+ additionalCounts[nameof(AssistantUsageData.Duration)] = (long)duration;
+ }
+
+ return additionalCounts;
+ }
+
+ private AgentResponseUpdate ConvertToAgentResponseUpdate(SessionEvent sessionEvent)
+ {
+ // Handle arbitrary events by storing as RawRepresentation
+ AIContent content = new()
+ {
+ RawRepresentation = sessionEvent
+ };
+
+ return new AgentResponseUpdate(ChatRole.Assistant, [content])
+ {
+ AgentId = this.Id,
+ CreatedAt = sessionEvent.Timestamp
+ };
+ }
+
+ private static SessionConfig? GetSessionConfig(IList? tools, string? instructions)
+ {
+ List? mappedTools = tools is { Count: > 0 } ? tools.OfType().ToList() : null;
+ SystemMessageConfig? systemMessage = instructions is not null ? new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = instructions } : null;
+
+ if (mappedTools is null && systemMessage is null)
+ {
+ return null;
+ }
+
+ return new SessionConfig { Tools = mappedTools, SystemMessage = systemMessage };
+ }
+
+ private static readonly Dictionary s_mediaTypeExtensions = new(StringComparer.OrdinalIgnoreCase)
+ {
+ ["image/png"] = ".png",
+ ["image/jpeg"] = ".jpg",
+ ["image/jpg"] = ".jpg",
+ ["image/gif"] = ".gif",
+ ["image/webp"] = ".webp",
+ ["image/svg+xml"] = ".svg",
+ ["text/plain"] = ".txt",
+ ["text/html"] = ".html",
+ ["text/markdown"] = ".md",
+ ["application/json"] = ".json",
+ ["application/xml"] = ".xml",
+ ["application/pdf"] = ".pdf"
+ };
+
+ private static string GetExtensionForMediaType(string? mediaType)
+ {
+ return mediaType is not null && s_mediaTypeExtensions.TryGetValue(mediaType, out string? extension) ? extension : ".dat";
+ }
+
+ private static async Task?> ProcessDataContentAttachmentsAsync(
+ IEnumerable messages,
+ List tempFiles,
+ CancellationToken cancellationToken)
+ {
+ List? attachments = null;
+ foreach (ChatMessage message in messages)
+ {
+ foreach (AIContent content in message.Contents)
+ {
+ if (content is DataContent dataContent)
+ {
+ // Write DataContent to a temp file
+ string tempFilePath = Path.Combine(Path.GetTempPath(), $"agentframework_copilot_data_{Guid.NewGuid()}{GetExtensionForMediaType(dataContent.MediaType)}");
+ await File.WriteAllBytesAsync(tempFilePath, dataContent.Data.ToArray(), cancellationToken).ConfigureAwait(false);
+ tempFiles.Add(tempFilePath);
+
+ // Create attachment
+ attachments ??= [];
+ attachments.Add(new UserMessageDataAttachmentsItem
+ {
+ Type = UserMessageDataAttachmentsItemType.File,
+ Path = tempFilePath,
+ DisplayName = Path.GetFileName(tempFilePath)
+ });
+ }
+ }
+ }
+
+ return attachments;
+ }
+
+ private static void CleanupTempFiles(List tempFiles)
+ {
+ foreach (string tempFile in tempFiles)
+ {
+ try
+ {
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
+ }
+ catch
+ {
+ // Best effort cleanup
+ }
+ }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgentSession.cs
new file mode 100644
index 0000000000..3c7d9a6598
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotAgentSession.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+
+namespace Microsoft.Agents.AI.GithubCopilot;
+
+///
+/// Represents a session for a GitHub Copilot agent conversation.
+///
+public sealed class GithubCopilotAgentSession : AgentSession
+{
+ ///
+ /// Gets or sets the session ID for the GitHub Copilot conversation.
+ ///
+ public string? SessionId { get; internal set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal GithubCopilotAgentSession()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class from serialized data.
+ ///
+ /// The serialized thread data.
+ /// Optional JSON serialization options.
+ internal GithubCopilotAgentSession(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ // The JSON serialization uses camelCase
+ if (serializedThread.TryGetProperty("sessionId", out JsonElement sessionIdElement))
+ {
+ this.SessionId = sessionIdElement.GetString();
+ }
+ }
+
+ ///
+ public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ State state = new()
+ {
+ SessionId = this.SessionId
+ };
+
+ return JsonSerializer.SerializeToElement(
+ state,
+ GithubCopilotJsonUtilities.DefaultOptions.GetTypeInfo(typeof(State)));
+ }
+
+ internal sealed class State
+ {
+ public string? SessionId { get; set; }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotJsonUtilities.cs
new file mode 100644
index 0000000000..c5a6e24d8e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/GithubCopilotJsonUtilities.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Agents.AI.GithubCopilot;
+
+///
+/// Provides utility methods and configurations for JSON serialization operations within the GitHub Copilot agent implementation.
+///
+internal static partial class GithubCopilotJsonUtilities
+{
+ ///
+ /// Gets the default instance used for JSON serialization operations.
+ ///
+ public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
+
+ ///
+ /// Creates and configures the default JSON serialization options.
+ ///
+ /// The configured options.
+ private static JsonSerializerOptions CreateDefaultOptions()
+ {
+ // Copy the configuration from the source generated context.
+ JsonSerializerOptions options = new(JsonContext.Default.Options)
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ };
+
+ // Chain in the resolvers from both AgentAbstractionsJsonUtilities and our source generated context.
+ options.TypeInfoResolverChain.Clear();
+ options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!);
+ options.TypeInfoResolverChain.Add(JsonContext.Default.Options.TypeInfoResolver!);
+
+ options.MakeReadOnly();
+ return options;
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
+ UseStringEnumConverter = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString)]
+ [JsonSerializable(typeof(GithubCopilotAgentSession.State))]
+ [ExcludeFromCodeCoverage]
+ private sealed partial class JsonContext : JsonSerializerContext;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.GithubCopilot/Microsoft.Agents.AI.GithubCopilot.csproj b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/Microsoft.Agents.AI.GithubCopilot.csproj
new file mode 100644
index 0000000000..f4f79c27bd
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.GithubCopilot/Microsoft.Agents.AI.GithubCopilot.csproj
@@ -0,0 +1,30 @@
+
+
+
+ preview
+
+ $(TargetFrameworksCore)
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Microsoft Agent Framework GitHub Copilot
+ Provides Microsoft Agent Framework support for GitHub Copilot SDK.
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/GithubCopilotAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/GithubCopilotAgentTests.cs
new file mode 100644
index 0000000000..2ae499c479
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/GithubCopilotAgentTests.cs
@@ -0,0 +1,257 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using GitHub.Copilot.SDK;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.GithubCopilot.IntegrationTests;
+
+public class GithubCopilotAgentTests
+{
+ private const string SkipReason = "Integration tests require GitHub Copilot CLI installed. For local execution only.";
+
+ private static Task OnPermissionRequestAsync(PermissionRequest request, PermissionInvocation invocation)
+ => Task.FromResult(new PermissionRequestResult { Kind = "approved" });
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithSimplePrompt_ReturnsResponseAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig: null);
+
+ // Act
+ AgentResponse response = await agent.RunAsync("What is 2 + 2? Answer with just the number.");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Messages);
+ Assert.Contains("4", response.Text);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunStreamingAsync_WithSimplePrompt_ReturnsUpdatesAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig: null);
+
+ // Act
+ List updates = [];
+ await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is 2 + 2? Answer with just the number."))
+ {
+ updates.Add(update);
+ }
+
+ // Assert
+ Assert.NotEmpty(updates);
+ string fullText = string.Join("", updates.Select(u => u.Text));
+ Assert.Contains("4", fullText);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithFunctionTool_InvokesToolAsync()
+ {
+ // Arrange
+ bool toolInvoked = false;
+
+ AIFunction weatherTool = AIFunctionFactory.Create((string location) =>
+ {
+ toolInvoked = true;
+ return $"The weather in {location} is sunny with a high of 25C.";
+ }, "GetWeather", "Get the weather for a given location.");
+
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ await using GithubCopilotAgent agent = new(
+ client,
+ tools: [weatherTool],
+ instructions: "You are a helpful weather agent. Use the GetWeather tool to answer weather questions.");
+
+ // Act
+ AgentResponse response = await agent.RunAsync("What's the weather like in Seattle?");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Messages);
+ Assert.True(toolInvoked);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithSession_MaintainsContextAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ await using GithubCopilotAgent agent = new(
+ client,
+ instructions: "You are a helpful assistant. Keep your answers short.");
+
+ AgentSession session = await agent.GetNewSessionAsync();
+
+ // Act - First turn
+ AgentResponse response1 = await agent.RunAsync("My name is Alice.", session);
+ Assert.NotNull(response1);
+
+ // Act - Second turn using same session
+ AgentResponse response2 = await agent.RunAsync("What is my name?", session);
+
+ // Assert
+ Assert.NotNull(response2);
+ Assert.Contains("Alice", response2.Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithSessionResume_ContinuesConversationAsync()
+ {
+ // Arrange - First agent instance starts a conversation
+ string? sessionId;
+
+ await using CopilotClient client1 = new(new CopilotClientOptions());
+ await client1.StartAsync();
+
+ await using GithubCopilotAgent agent1 = new(
+ client1,
+ instructions: "You are a helpful assistant. Keep your answers short.");
+
+ AgentSession session1 = await agent1.GetNewSessionAsync();
+ await agent1.RunAsync("Remember this number: 42.", session1);
+
+ sessionId = ((GithubCopilotAgentSession)session1).SessionId;
+ Assert.NotNull(sessionId);
+
+ // Act - Second agent instance resumes the session
+ await using CopilotClient client2 = new(new CopilotClientOptions());
+ await client2.StartAsync();
+
+ await using GithubCopilotAgent agent2 = new(
+ client2,
+ instructions: "You are a helpful assistant. Keep your answers short.");
+
+ AgentSession session2 = await agent2.GetNewSessionAsync(sessionId);
+ AgentResponse response = await agent2.RunAsync("What number did I ask you to remember?", session2);
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.Contains("42", response.Text);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithShellPermissions_ExecutesCommandAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ SessionConfig sessionConfig = new()
+ {
+ OnPermissionRequest = OnPermissionRequestAsync,
+ };
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig);
+
+ // Act
+ AgentResponse response = await agent.RunAsync("Run a shell command to print 'hello world'");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Messages);
+ Assert.Contains("hello", response.Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithUrlPermissions_FetchesContentAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ SessionConfig sessionConfig = new()
+ {
+ OnPermissionRequest = OnPermissionRequestAsync,
+ };
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig);
+
+ // Act
+ AgentResponse response = await agent.RunAsync(
+ "Fetch https://learn.microsoft.com/agent-framework/tutorials/quick-start and summarize its contents in one sentence");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.Contains("Agent Framework", response.Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithLocalMcpServer_UsesServerToolsAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ SessionConfig sessionConfig = new()
+ {
+ OnPermissionRequest = OnPermissionRequestAsync,
+ McpServers = new Dictionary
+ {
+ ["filesystem"] = new McpLocalServerConfig
+ {
+ Type = "stdio",
+ Command = "npx",
+ Args = ["-y", "@modelcontextprotocol/server-filesystem", "."],
+ Tools = ["*"],
+ },
+ },
+ };
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig);
+
+ // Act
+ AgentResponse response = await agent.RunAsync("List the files in the current directory");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.NotEmpty(response.Messages);
+ Assert.NotEmpty(response.Text);
+ }
+
+ [Fact(Skip = SkipReason)]
+ public async Task RunAsync_WithRemoteMcpServer_UsesServerToolsAsync()
+ {
+ // Arrange
+ await using CopilotClient client = new(new CopilotClientOptions());
+ await client.StartAsync();
+
+ SessionConfig sessionConfig = new()
+ {
+ OnPermissionRequest = OnPermissionRequestAsync,
+ McpServers = new Dictionary
+ {
+ ["microsoft-learn"] = new McpRemoteServerConfig
+ {
+ Type = "http",
+ Url = "https://learn.microsoft.com/api/mcp",
+ Tools = ["*"],
+ },
+ },
+ };
+
+ await using GithubCopilotAgent agent = new(client, sessionConfig);
+
+ // Act
+ AgentResponse response = await agent.RunAsync("Search Microsoft Learn for 'Azure Functions' and summarize the top result");
+
+ // Assert
+ Assert.NotNull(response);
+ Assert.Contains("Azure Functions", response.Text, StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests.csproj
new file mode 100644
index 0000000000..b51b09f284
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests.csproj
@@ -0,0 +1,12 @@
+
+
+
+
+ $(TargetFrameworksCore)
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/CopilotClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/CopilotClientExtensionsTests.cs
new file mode 100644
index 0000000000..5c3483f4b2
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/CopilotClientExtensionsTests.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using GitHub.Copilot.SDK;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.GithubCopilot.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class CopilotClientExtensionsTests
+{
+ [Fact]
+ public void AsAIAgent_WithAllParameters_ReturnsGithubCopilotAgentWithSpecifiedProperties()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+
+ const string TestId = "test-agent-id";
+ const string TestName = "Test Agent";
+ const string TestDescription = "This is a test agent description";
+
+ // Act
+ var agent = copilotClient.AsAIAgent(ownsClient: false, id: TestId, name: TestName, description: TestDescription, tools: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ Assert.Equal(TestId, agent.Id);
+ Assert.Equal(TestName, agent.Name);
+ Assert.Equal(TestDescription, agent.Description);
+ }
+
+ [Fact]
+ public void AsAIAgent_WithMinimalParameters_ReturnsGithubCopilotAgent()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+
+ // Act
+ var agent = copilotClient.AsAIAgent(ownsClient: false, tools: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ [Fact]
+ public void AsAIAgent_WithNullClient_ThrowsArgumentNullException()
+ {
+ // Arrange
+ CopilotClient? copilotClient = null;
+
+ // Act & Assert
+ Assert.Throws(() => copilotClient!.AsAIAgent(sessionConfig: null));
+ }
+
+ [Fact]
+ public void AsAIAgent_WithOwnsClient_ReturnsAgentThatOwnsClient()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+
+ // Act
+ var agent = copilotClient.AsAIAgent(ownsClient: true, tools: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+
+ [Fact]
+ public void AsAIAgent_WithTools_ReturnsAgentWithTools()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ List tools = [AIFunctionFactory.Create(() => "test", "TestFunc", "Test function")];
+
+ // Act
+ var agent = copilotClient.AsAIAgent(tools: tools);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/GithubCopilotAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/GithubCopilotAgentTests.cs
new file mode 100644
index 0000000000..90b84fb3bd
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/GithubCopilotAgentTests.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using GitHub.Copilot.SDK;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.GithubCopilot.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class GithubCopilotAgentTests
+{
+ [Fact]
+ public void Constructor_WithCopilotClient_InitializesPropertiesCorrectly()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ const string TestId = "test-id";
+ const string TestName = "test-name";
+ const string TestDescription = "test-description";
+
+ // Act
+ var agent = new GithubCopilotAgent(copilotClient, ownsClient: false, id: TestId, name: TestName, description: TestDescription, tools: null);
+
+ // Assert
+ Assert.Equal(TestId, agent.Id);
+ Assert.Equal(TestName, agent.Name);
+ Assert.Equal(TestDescription, agent.Description);
+ }
+
+ [Fact]
+ public void Constructor_WithNullCopilotClient_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.Throws(() => new GithubCopilotAgent(copilotClient: null!, sessionConfig: null));
+ }
+
+ [Fact]
+ public void Constructor_WithDefaultParameters_UsesBaseProperties()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+
+ // Act
+ var agent = new GithubCopilotAgent(copilotClient, ownsClient: false, tools: null);
+
+ // Assert
+ Assert.NotNull(agent.Id);
+ Assert.NotEmpty(agent.Id);
+ Assert.Equal("GitHub Copilot Agent", agent.Name);
+ Assert.Equal("An AI agent powered by GitHub Copilot", agent.Description);
+ }
+
+ [Fact]
+ public async Task GetNewSessionAsync_ReturnsGithubCopilotAgentSessionAsync()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ var agent = new GithubCopilotAgent(copilotClient, ownsClient: false, tools: null);
+
+ // Act
+ var session = await agent.GetNewSessionAsync();
+
+ // Assert
+ Assert.NotNull(session);
+ Assert.IsType(session);
+ }
+
+ [Fact]
+ public async Task GetNewSessionAsync_WithSessionId_ReturnsSessionWithSessionIdAsync()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ var agent = new GithubCopilotAgent(copilotClient, ownsClient: false, tools: null);
+ const string TestSessionId = "test-session-id";
+
+ // Act
+ var session = await agent.GetNewSessionAsync(TestSessionId);
+
+ // Assert
+ Assert.NotNull(session);
+ var typedSession = Assert.IsType(session);
+ Assert.Equal(TestSessionId, typedSession.SessionId);
+ }
+
+ [Fact]
+ public void Constructor_WithTools_InitializesCorrectly()
+ {
+ // Arrange
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ List tools = [AIFunctionFactory.Create(() => "test", "TestFunc", "Test function")];
+
+ // Act
+ var agent = new GithubCopilotAgent(copilotClient, tools: tools);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Id);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/Microsoft.Agents.AI.GithubCopilot.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/Microsoft.Agents.AI.GithubCopilot.UnitTests.csproj
new file mode 100644
index 0000000000..ef8527302e
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/Microsoft.Agents.AI.GithubCopilot.UnitTests.csproj
@@ -0,0 +1,12 @@
+
+
+
+
+ $(TargetFrameworksCore)
+
+
+
+
+
+
+