Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
<PackageVersion Include="JsonSchema.Net.Generation" Version="5.0.2" />
<PackageVersion Include="Markdig" Version="0.40.0" />
<PackageVersion Include="Microsoft.Agents.AI.Abstractions" Version="1.0.0-preview.251009.1" />
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.1.107-beta" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.13" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.14" />
Expand Down Expand Up @@ -101,7 +102,7 @@
<PackageVersion Include="System.Memory.Data" Version="8.0.1" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="8.0.6" />
<PackageVersion Include="System.Text.Json" Version="9.0.9" />
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="A2A" Version="0.3.1-preview" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
<PackageReference Include="Microsoft.Extensions.Configuration" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Http" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" VersionOverride="9.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" VersionOverride="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" VersionOverride="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="OpenTelemetry.Exporter.Console" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
Expand Down
29 changes: 29 additions & 0 deletions dotnet/src/Agents/A2A/A2AAgentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using MAAI = Microsoft.Agents.AI;

namespace Microsoft.SemanticKernel.Agents.A2A;

/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="A2AAgent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
public static class A2AAgentExtensions
{
/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="A2AAgent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
/// <param name="a2aAgent">The Semantic Kernel <see cref="A2AAgent"/> to expose as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.</param>
/// <returns>The Semantic Kernel Agent Framework <see cref="Agent"/> exposed as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/></returns>
[Experimental("SKEXP0110")]
public static MAAI.AIAgent AsAIAgent(this A2AAgent a2aAgent)
=> a2aAgent.AsAIAgent(
() => new A2AAgentThread(a2aAgent.Client),
(json, options) =>
{
var agentId = JsonSerializer.Deserialize<string>(json);
return agentId is null ? new A2AAgentThread(a2aAgent.Client) : new A2AAgentThread(a2aAgent.Client, agentId);
},
(thread, options) => JsonSerializer.SerializeToElement((thread as A2AAgentThread)?.Id));
}
128 changes: 128 additions & 0 deletions dotnet/src/Agents/Abstractions/AIAgent/AIAgentAdapter.cs
Copy link
Member

@rogerbarreto rogerbarreto Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. (Internal)

For the name of this class I would do something in the same lines we did for the ChatCompletionService vs ChatClient.

i.e: SKAgentAIAgent, see ChatCompletionServiceChatClient.cs

Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text.Json;
using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Threading;
using MAAI = Microsoft.Agents.AI;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.SemanticKernel.Agents;

/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="Agent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
[Experimental("SKEXP0110")]
internal sealed class AIAgentAdapter : MAAI.AIAgent
{
private readonly Func<AgentThread> _threadFactory;
private readonly Func<JsonElement, JsonSerializerOptions?, AgentThread> _threadDeserializationFactory;
private readonly Func<AgentThread, JsonSerializerOptions?, JsonElement> _threadSerializer;

/// <summary>
/// Initializes a new instance of the <see cref="AIAgentAdapter"/> class.
/// </summary>
/// <param name="semanticKernelAgent">The Semantic Kernel <see cref="Agent"/> to expose as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.</param>
/// <param name="threadFactory">A factory method to create the required <see cref="AgentThread"/> type to use with the agent.</param>
/// <param name="threadDeserializationFactory">A factory method to deserialize the required <see cref="AgentThread"/> type.</param>
/// <param name="threadSerializer">A method to serialize the <see cref="AgentThread"/> type.</param>
public AIAgentAdapter(
Agent semanticKernelAgent,
Func<AgentThread> threadFactory,
Func<JsonElement, JsonSerializerOptions?, AgentThread> threadDeserializationFactory,
Func<AgentThread, JsonSerializerOptions?, JsonElement> threadSerializer)
{
Throw.IfNull(semanticKernelAgent);
Throw.IfNull(threadFactory);
Throw.IfNull(threadDeserializationFactory);
Throw.IfNull(threadSerializer);

this.InnerAgent = semanticKernelAgent;
this._threadFactory = threadFactory;
this._threadDeserializationFactory = threadDeserializationFactory;
this._threadSerializer = threadSerializer;
}

/// <summary>
/// Gets the underlying Semantic Kernel Agent Framework <see cref="Agent"/>.
/// </summary>
public Agent InnerAgent { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fully sure if we need to expose the Agent, we could just recommend the GetService<Agent>() for this.

Suggested change
public Agent InnerAgent { get; }


/// <inheritdoc />
public override MAAI.AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
=> new AIAgentThreadAdapter(this._threadDeserializationFactory(serializedThread, jsonSerializerOptions), this._threadSerializer);

/// <inheritdoc />
public override MAAI.AgentThread GetNewThread() => new AIAgentThreadAdapter(this._threadFactory(), this._threadSerializer);

/// <inheritdoc />
public override async Task<MAAI.AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, MAAI.AgentThread? thread = null, MAAI.AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
thread ??= this.GetNewThread();
if (thread is not AIAgentThreadAdapter typedThread)
{
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
}

List<ChatMessage> responseMessages = [];
var invokeOptions = new AgentInvokeOptions()
{
OnIntermediateMessage = (msg) =>
{
responseMessages.Add(msg.ToChatMessage());
return Task.CompletedTask;
}
};

AgentResponseItem<ChatMessageContent>? lastResponseItem = null;
ChatMessage? lastResponseMessage = null;
await foreach (var responseItem in this.InnerAgent.InvokeAsync(messages.Select(x => x.ToChatMessageContent()).ToList(), typedThread.InnerThread, invokeOptions, cancellationToken).ConfigureAwait(false))
{
lastResponseItem = responseItem;
lastResponseMessage = responseItem.Message.ToChatMessage();
responseMessages.Add(lastResponseMessage);
}

return new MAAI.AgentRunResponse(responseMessages)
{
AgentId = this.InnerAgent.Id,
RawRepresentation = lastResponseItem,
AdditionalProperties = lastResponseMessage?.AdditionalProperties,
CreatedAt = lastResponseMessage?.CreatedAt,
};
}

/// <inheritdoc />
public override async IAsyncEnumerable<MAAI.AgentRunResponseUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
MAAI.AgentThread? thread = null,
MAAI.AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
thread ??= this.GetNewThread();
if (thread is not AIAgentThreadAdapter typedThread)
{
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
}

await foreach (var responseItem in this.InnerAgent.InvokeAsync(messages.Select(x => x.ToChatMessageContent()).ToList(), typedThread.InnerThread, cancellationToken: cancellationToken).ConfigureAwait(false))
{
var chatMessage = responseItem.Message.ToChatMessage();

yield return new MAAI.AgentRunResponseUpdate
{
AgentId = this.InnerAgent.Id,
RawRepresentation = responseItem,
AdditionalProperties = chatMessage.AdditionalProperties,
MessageId = chatMessage.MessageId,
Role = chatMessage.Role,
CreatedAt = chatMessage.CreatedAt,
Contents = chatMessage.Contents
};
}
}
}
38 changes: 38 additions & 0 deletions dotnet/src/Agents/Abstractions/AIAgent/AIAgentThreadAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using MAAI = Microsoft.Agents.AI;

namespace Microsoft.SemanticKernel.Agents;

[Experimental("SKEXP0110")]
internal sealed class AIAgentThreadAdapter : MAAI.AgentThread
{
private readonly Func<AgentThread, JsonSerializerOptions?, JsonElement> _threadSerializer;

internal AIAgentThreadAdapter(AgentThread thread, Func<AgentThread, JsonSerializerOptions?, JsonElement> threadSerializer)
{
Throw.IfNull(thread);
Throw.IfNull(threadSerializer);

this.InnerThread = thread;
this._threadSerializer = threadSerializer;
}

/// <summary>
/// Gets the underlying Semantic Kernel Agent Framework <see cref="AgentThread"/>.
/// </summary>
public AgentThread InnerThread { get; }
Comment on lines +24 to +27
Copy link
Member

@rogerbarreto rogerbarreto Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale here is a bit similar to where after the conversion we don't expose the converted type back as we assume the caller will have the reference already.

private readonly IChatCompletionService _chatCompletionService;

Point to the GetService usage.

Suggested change
/// <summary>
/// Gets the underlying Semantic Kernel Agent Framework <see cref="AgentThread"/>.
/// </summary>
public AgentThread InnerThread { get; }


/// <inheritdoc />
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> this._threadSerializer(this.InnerThread, jsonSerializerOptions);

public override object? GetService(Type serviceType, object? serviceKey = null)
=> base.GetService(serviceType, serviceKey)
?? (serviceType == typeof(AgentThread) && serviceKey is null
? this.InnerThread
: null);
}
65 changes: 65 additions & 0 deletions dotnet/src/Agents/Abstractions/AIAgent/ChatMessageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.ChatCompletion;
using MEAI = Microsoft.Extensions.AI;

namespace Microsoft.SemanticKernel.Agents;

#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

internal static class ChatMessageExtensions
{
/// <summary>Converts a <see cref="ChatMessage"/> to a <see cref="ChatMessageContent"/>.</summary>
internal static ChatMessageContent ToChatMessageContent(this ChatMessage message, ChatResponse? response = null)
{
ChatMessageContent result = new()
{
ModelId = response?.ModelId,
AuthorName = message.AuthorName,
InnerContent = response?.RawRepresentation ?? message.RawRepresentation,
Metadata = new AdditionalPropertiesDictionary(message.AdditionalProperties ?? []) { ["Usage"] = response?.Usage },
Role = new AuthorRole(message.Role.Value),
};

foreach (AIContent content in message.Contents)
{
KernelContent? resultContent = content switch
{
MEAI.TextContent tc => new TextContent(tc.Text),
DataContent dc when dc.HasTopLevelMediaType("image") => new ImageContent(dc.Uri),
UriContent uc when uc.HasTopLevelMediaType("image") => new ImageContent(uc.Uri),
DataContent dc when dc.HasTopLevelMediaType("audio") => new AudioContent(dc.Uri),
UriContent uc when uc.HasTopLevelMediaType("audio") => new AudioContent(uc.Uri),
DataContent dc => new BinaryContent(dc.Uri),
UriContent uc => new BinaryContent(uc.Uri),
MEAI.FunctionCallContent fcc => new FunctionCallContent(
functionName: fcc.Name,
id: fcc.CallId,
arguments: fcc.Arguments is not null ? new(fcc.Arguments) : null),
MEAI.FunctionResultContent frc => new FunctionResultContent(
functionName: GetFunctionCallContent(frc.CallId)?.Name,
callId: frc.CallId,
result: frc.Result),
_ => null
};

if (resultContent is not null)
{
resultContent.Metadata = content.AdditionalProperties;
resultContent.InnerContent = content.RawRepresentation;
resultContent.ModelId = response?.ModelId;
result.Items.Add(resultContent);
}
}

return result;

MEAI.FunctionCallContent? GetFunctionCallContent(string callId)
=> response?.Messages
.Select(m => m.Contents
.FirstOrDefault(c => c is MEAI.FunctionCallContent fcc && fcc.CallId == callId) as MEAI.FunctionCallContent)
.FirstOrDefault(fcc => fcc is not null);
}
}
34 changes: 34 additions & 0 deletions dotnet/src/Agents/Abstractions/AgentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using MAAI = Microsoft.Agents.AI;

namespace Microsoft.SemanticKernel.Agents;

/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="Agent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
public static class AgentExtensions
{
/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="Agent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
/// <param name="semanticKernelAgent">The Semantic Kernel <see cref="Agent"/> to expose as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.</param>
/// <param name="threadFactory">A factory method to create the required <see cref="AgentThread"/> type to use with the agent.</param>
/// <param name="threadDeserializationFactory">A factory method to deserialize the required <see cref="AgentThread"/> type.</param>
/// <param name="threadSerializer">A method to serialize the <see cref="AgentThread"/> type.</param>
/// <returns>The Semantic Kernel Agent Framework <see cref="Agent"/> exposed as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/></returns>
[Experimental("SKEXP0110")]
public static MAAI.AIAgent AsAIAgent(
this Agent semanticKernelAgent,
Func<AgentThread> threadFactory,
Func<JsonElement, JsonSerializerOptions?, AgentThread> threadDeserializationFactory,
Func<AgentThread, JsonSerializerOptions?, JsonElement> threadSerializer)
=> new AIAgentAdapter(
semanticKernelAgent,
threadFactory,
threadDeserializationFactory,
threadSerializer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

<ItemGroup>
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.Agents.AI.Abstractions" />
</ItemGroup>

<ItemGroup>
Expand Down
29 changes: 29 additions & 0 deletions dotnet/src/Agents/AzureAI/AzureAIAgentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using MAAI = Microsoft.Agents.AI;

namespace Microsoft.SemanticKernel.Agents.AzureAI;

/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="AzureAIAgent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
public static class AzureAIAgentExtensions
{
/// <summary>
/// Exposes a Semantic Kernel Agent Framework <see cref="AzureAIAgent"/> as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.
/// </summary>
/// <param name="azureAIAgent">The Semantic Kernel <see cref="AzureAIAgent"/> to expose as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/>.</param>
/// <returns>The Semantic Kernel Agent Framework <see cref="Agent"/> exposed as a Microsoft Agent Framework <see cref="MAAI.AIAgent"/></returns>
[Experimental("SKEXP0110")]
public static MAAI.AIAgent AsAIAgent(this AzureAIAgent azureAIAgent)
=> azureAIAgent.AsAIAgent(
() => new AzureAIAgentThread(azureAIAgent.Client),
(json, options) =>
{
var agentId = JsonSerializer.Deserialize<string>(json);
return agentId is null ? new AzureAIAgentThread(azureAIAgent.Client) : new AzureAIAgentThread(azureAIAgent.Client, agentId);
},
(thread, options) => JsonSerializer.SerializeToElement((thread as AzureAIAgentThread)?.Id));
}
Loading
Loading