chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using A2A;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="A2AAIContentExtensions"/> class.
/// </summary>
public sealed class A2AAIContentExtensionsTests
{
[Fact]
public void ToA2AParts_WithEmptyCollection_ReturnsNull()
{
// Arrange
var emptyContents = new List<AIContent>();
// Act
var result = emptyContents.ToParts();
// Assert
Assert.Null(result);
}
[Fact]
public void ToA2AParts_WithMultipleContents_ReturnsListWithAllParts()
{
// Arrange
var contents = new List<AIContent>
{
new TextContent("First text"),
new UriContent("https://example.com/file1.txt", "file/txt"),
new TextContent("Second text"),
};
// Act
var result = contents.ToParts();
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.Equal(PartContentCase.Text, result[0].ContentCase);
Assert.Equal("First text", result[0].Text);
Assert.Equal(PartContentCase.Url, result[1].ContentCase);
Assert.Equal("https://example.com/file1.txt", result[1].Url);
Assert.Equal(PartContentCase.Text, result[2].ContentCase);
Assert.Equal("Second text", result[2].Text);
}
[Fact]
public void ToA2AParts_WithMixedSupportedAndUnsupportedContent_IgnoresUnsupportedContent()
{
// Arrange
var contents = new List<AIContent>
{
new TextContent("First text"),
new MockAIContent(), // Unsupported - should be ignored
new UriContent("https://example.com/file.txt", "file/txt"),
new MockAIContent(), // Unsupported - should be ignored
new TextContent("Second text")
};
// Act
var result = contents.ToParts();
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.Equal(PartContentCase.Text, result[0].ContentCase);
Assert.Equal("First text", result[0].Text);
Assert.Equal(PartContentCase.Url, result[1].ContentCase);
Assert.Equal("https://example.com/file.txt", result[1].Url);
Assert.Equal(PartContentCase.Text, result[2].ContentCase);
Assert.Equal("Second text", result[2].Text);
}
// Mock class for testing unsupported scenarios
private sealed class MockAIContent : AIContent;
}
@@ -0,0 +1,287 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using A2A;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="A2AAgentCardExtensions"/> class.
/// </summary>
public sealed class A2AAgentCardExtensionsTests
{
private readonly AgentCard _agentCard;
public A2AAgentCardExtensionsTests()
{
this._agentCard = new AgentCard
{
Name = "Test Agent",
Description = "A test agent for unit testing",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
};
}
[Fact]
public void AsAIAgent_ReturnsAIAgent()
{
// Act
var agent = this._agentCard.AsAIAgent();
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("A test agent for unit testing", agent.Description);
}
[Fact]
public async Task RunIAgentAsync_SendsRequestToTheUrlSpecifiedInAgentCardAsync()
{
// Arrange
using var handler = new HttpMessageHandlerStub();
using var httpClient = new HttpClient(handler, false);
handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var agent = this._agentCard.AsAIAgent(httpClient: httpClient);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Single(handler.CapturedUris);
Assert.Equal(new Uri("http://test-endpoint/agent"), handler.CapturedUris[0]);
}
[Fact]
public async Task AsAIAgent_WithPreferredBindings_UsesMatchingInterfaceAsync()
{
// Arrange
var card = new AgentCard
{
Name = "Multi-Interface Agent",
Description = "An agent with multiple interfaces",
SupportedInterfaces =
[
new AgentInterface { Url = "http://first/agent", ProtocolBinding = ProtocolBindingNames.HttpJson },
new AgentInterface { Url = "http://second/agent", ProtocolBinding = ProtocolBindingNames.JsonRpc },
]
};
using var handler = new HttpMessageHandlerStub();
using var httpClient = new HttpClient(handler, false);
handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var options = new A2AClientOptions
{
PreferredBindings = [ProtocolBindingNames.JsonRpc]
};
var agent = card.AsAIAgent(httpClient, options: options);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Single(handler.CapturedUris);
Assert.Equal(new Uri("http://second/agent"), handler.CapturedUris[0]);
}
[Fact]
public void AsAIAgent_WithNullOptions_UsesDefaultBindingPreference()
{
// Arrange
var card = new AgentCard
{
Name = "Default Options Agent",
Description = "Tests default A2AClientOptions behavior",
SupportedInterfaces =
[
new AgentInterface { Url = "http://default/agent" },
]
};
// Act - null options should use defaults (HTTP+JSON first, JSON-RPC as fallback)
var agent = card.AsAIAgent(options: null);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("Default Options Agent", agent.Name);
}
[Fact]
public void AsAIAgent_WithNoMatchingBinding_ThrowsException()
{
// Arrange
var card = new AgentCard
{
Name = "Unmatched Binding Agent",
Description = "Agent with unsupported binding only",
SupportedInterfaces =
[
new AgentInterface { Url = "http://grpc/agent", ProtocolBinding = "GRPC" },
]
};
var options = new A2AClientOptions
{
PreferredBindings = [ProtocolBindingNames.JsonRpc]
};
// Act & Assert - factory should throw when no matching binding exists
Assert.ThrowsAny<Exception>(() => card.AsAIAgent(options: options));
}
[Fact]
public void AsAIAgent_WithNoSupportedInterfaces_ThrowsException()
{
// Arrange
var card = new AgentCard
{
Name = "No Interfaces Agent",
Description = "Agent with no supported interfaces",
};
// Act & Assert
Assert.ThrowsAny<Exception>(() => card.AsAIAgent());
}
[Fact]
public void AsAIAgent_WithAgentOptions_OverridesCardValues()
{
// Arrange
var card = new AgentCard
{
Name = "Card Agent",
Description = "Card description",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
};
var agentOptions = new A2AAgentOptions
{
Id = "custom-id",
Name = "Custom Agent",
Description = "Custom description"
};
// Act
var agent = card.AsAIAgent(agentOptions);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("custom-id", agent.Id);
Assert.Equal("Custom Agent", agent.Name);
Assert.Equal("Custom description", agent.Description);
}
[Fact]
public void AsAIAgent_WithAgentOptions_FallsBackToCardValues()
{
// Arrange
var card = new AgentCard
{
Name = "Card Agent",
Description = "Card description",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
};
var agentOptions = new A2AAgentOptions
{
Id = "custom-id"
};
// Act
var agent = card.AsAIAgent(agentOptions);
// Assert
Assert.NotNull(agent);
Assert.Equal("custom-id", agent.Id);
Assert.Equal("Card Agent", agent.Name);
Assert.Equal("Card description", agent.Description);
}
[Fact]
public void AsAIAgent_WithEmptyAgentOptions_UsesCardValues()
{
// Arrange
var card = new AgentCard
{
Name = "Card Agent",
Description = "Card description",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
};
// Act
var agent = card.AsAIAgent(new A2AAgentOptions());
// Assert
Assert.NotNull(agent);
Assert.Equal("Card Agent", agent.Name);
Assert.Equal("Card description", agent.Description);
}
internal sealed class HttpMessageHandlerStub : HttpMessageHandler
{
public Queue ResponsesToReturn { get; } = new();
public List<Uri> CapturedUris { get; } = [];
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.CapturedUris.Add(request.RequestUri!);
var response = this.ResponsesToReturn.Dequeue();
if (response is AgentCard agentCard)
{
var json = JsonSerializer.Serialize(agentCard);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
}
else if (response is Message message)
{
var sendMessageResponse = new SendMessageResponse { Message = message };
var jsonRpcResponse = new JsonRpcResponse
{
Id = "response-id",
Result = JsonSerializer.SerializeToNode(sendMessageResponse, A2AJsonUtilities.DefaultOptions)
};
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions), Encoding.UTF8, "application/json")
};
}
// Return empty agent card if none specified
var emptyCard = new AgentCard();
var emptyJson = JsonSerializer.Serialize(emptyCard);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(emptyJson, Encoding.UTF8, "application/json")
};
}
}
}
@@ -0,0 +1,245 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using A2A;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="A2AAgentTaskExtensions"/> class.
/// </summary>
public sealed class A2AAgentTaskExtensionsTests
{
[Fact]
public void ToChatMessages_WithNullAgentTask_ThrowsArgumentNullException()
{
// Arrange
AgentTask agentTask = null!;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => agentTask.ToChatMessages());
}
[Fact]
public void ToAIContents_WithNullAgentTask_ThrowsArgumentNullException()
{
// Arrange
AgentTask agentTask = null!;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => agentTask.ToAIContents());
}
[Fact]
public void ToChatMessages_WithEmptyArtifactsAndNoUserInputRequests_ReturnsNull()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = [],
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<ChatMessage>? result = agentTask.ToChatMessages();
// Assert
Assert.Null(result);
}
[Fact]
public void ToChatMessages_WithNullArtifactsAndNoUserInputRequests_ReturnsNull()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = null,
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<ChatMessage>? result = agentTask.ToChatMessages();
// Assert
Assert.Null(result);
}
[Fact]
public void ToAIContents_WithEmptyArtifactsAndNoUserInputRequests_ReturnsNull()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = [],
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<AIContent>? result = agentTask.ToAIContents();
// Assert
Assert.Null(result);
}
[Fact]
public void ToAIContents_WithNullArtifactsAndNoUserInputRequests_ReturnsNull()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = null,
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<AIContent>? result = agentTask.ToAIContents();
// Assert
Assert.Null(result);
}
[Fact]
public void ToChatMessages_WithValidArtifact_ReturnsChatMessages()
{
// Arrange
var artifact = new Artifact
{
Parts = [Part.FromText("response")],
};
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = [artifact],
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<ChatMessage>? result = agentTask.ToChatMessages();
// Assert
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.All(result, msg => Assert.Equal(ChatRole.Assistant, msg.Role));
Assert.Equal("response", result[0].Contents[0].ToString());
}
[Fact]
public void ToAIContents_WithMultipleArtifacts_FlattenAllContents()
{
// Arrange
var artifact1 = new Artifact
{
Parts = [Part.FromText("content1")],
};
var artifact2 = new Artifact
{
Parts =
[
Part.FromText("content2"),
Part.FromText("content3")
],
};
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = [artifact1, artifact2],
Status = new TaskStatus { State = TaskState.Completed },
};
// Act
IList<AIContent>? result = agentTask.ToAIContents();
// Assert
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.Equal(3, result.Count);
Assert.Equal("content1", result[0].ToString());
Assert.Equal("content2", result[1].ToString());
Assert.Equal("content3", result[2].ToString());
}
[Fact]
public void ToChatMessages_WithInputRequiredStatus_IncludesStatusContents()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = null,
Status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message { Parts = [Part.FromText("What is your destination?")] },
},
};
// Act
IList<ChatMessage>? result = agentTask.ToChatMessages();
// Assert
Assert.NotNull(result);
Assert.Single(result);
Assert.Equal(ChatRole.Assistant, result[0].Role);
var textContent = Assert.Single(result[0].Contents.OfType<TextContent>());
Assert.Equal("What is your destination?", textContent.Text);
}
[Fact]
public void ToAIContents_WithInputRequiredStatus_IncludesStatusContents()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = null,
Status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message { Parts = [Part.FromText("What is your destination?")] },
},
};
// Act
IList<AIContent>? result = agentTask.ToAIContents();
// Assert
Assert.NotNull(result);
var textContent = Assert.Single(result.OfType<TextContent>());
Assert.Equal("What is your destination?", textContent.Text);
}
[Fact]
public void ToChatMessages_WithArtifactsAndInputRequired_IncludesBoth()
{
// Arrange
var agentTask = new AgentTask
{
Id = "task1",
Artifacts = [new Artifact { Parts = [Part.FromText("partial result")] }],
Status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message { Parts = [Part.FromText("Need more info")] },
},
};
// Act
IList<ChatMessage>? result = agentTask.ToChatMessages();
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Count);
Assert.Equal("partial result", result[0].Text);
Assert.Single(result[1].Contents.OfType<TextContent>());
}
}
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Text.Json;
using A2A;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="A2AArtifactExtensions"/> class.
/// </summary>
public sealed class A2AArtifactExtensionsTests
{
[Fact]
public void ToChatMessage_WithMultiplePartsMetadataAndRawRepresentation_ReturnsCorrectChatMessage()
{
// Arrange
var artifact = new Artifact
{
ArtifactId = "artifact-comprehensive",
Name = "comprehensive-artifact",
Parts =
[
Part.FromText("First part"),
Part.FromText("Second part"),
Part.FromText("Third part")
],
Metadata = new Dictionary<string, JsonElement>
{
{ "key1", JsonSerializer.SerializeToElement("value1") },
{ "key2", JsonSerializer.SerializeToElement(42) }
}
};
// Act
var result = artifact.ToChatMessage();
// Assert - Verify multiple parts
Assert.NotNull(result);
Assert.Equal(ChatRole.Assistant, result.Role);
Assert.Equal(3, result.Contents.Count);
Assert.All(result.Contents, content => Assert.IsType<TextContent>(content));
Assert.Equal("First part", ((TextContent)result.Contents[0]).Text);
Assert.Equal("Second part", ((TextContent)result.Contents[1]).Text);
Assert.Equal("Third part", ((TextContent)result.Contents[2]).Text);
// Assert - Verify metadata conversion to AdditionalProperties
Assert.NotNull(result.AdditionalProperties);
Assert.Equal(2, result.AdditionalProperties.Count);
Assert.True(result.AdditionalProperties.ContainsKey("key1"));
Assert.True(result.AdditionalProperties.ContainsKey("key2"));
// Assert - Verify RawRepresentation is set to artifact
Assert.NotNull(result.RawRepresentation);
Assert.Same(artifact, result.RawRepresentation);
}
[Fact]
public void ToAIContents_WithMultipleParts_ReturnsCorrectList()
{
// Arrange
var artifact = new Artifact
{
ArtifactId = "artifact-ai-multi",
Name = "test",
Parts =
[
Part.FromText("Part 1"),
Part.FromText("Part 2"),
Part.FromText("Part 3")
],
Metadata = null
};
// Act
var result = artifact.ToAIContents();
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.All(result, content => Assert.IsType<TextContent>(content));
Assert.Equal("Part 1", ((TextContent)result[0]).Text);
Assert.Equal("Part 2", ((TextContent)result[1]).Text);
Assert.Equal("Part 3", ((TextContent)result[2]).Text);
}
[Fact]
public void ToAIContents_WithEmptyParts_ReturnsEmptyList()
{
// Arrange
var artifact = new Artifact
{
ArtifactId = "artifact-empty",
Name = "test",
Parts = [],
Metadata = null
};
// Act
var result = artifact.ToAIContents();
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
}
@@ -0,0 +1,221 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using A2A;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="A2ACardResolverExtensions"/> class.
/// </summary>
public sealed class A2ACardResolverExtensionsTests : IDisposable
{
private readonly HttpClient _httpClient;
private readonly HttpMessageHandlerStub _handler;
private readonly A2ACardResolver _resolver;
public A2ACardResolverExtensionsTests()
{
this._handler = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._handler, false);
this._resolver = new A2ACardResolver(new Uri("http://test-host"), httpClient: this._httpClient);
}
[Fact]
public async Task GetAIAgentAsync_WithValidAgentCard_ReturnsAIAgentAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Test Agent",
Description = "A test agent for unit testing",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
// Act
var agent = await this._resolver.GetAIAgentAsync();
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("A test agent for unit testing", agent.Description);
// Verify that there was only one request made to retrieve the agent card
Assert.Single(this._handler.CapturedUris);
Assert.StartsWith("http://test-host/", this._handler.CapturedUris[0].ToString());
}
[Fact]
public async Task RunIAgentAsync_WithUrlFromAgentCard_SendsRequestToTheUrlAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
this._handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var agent = await this._resolver.GetAIAgentAsync(httpClient: this._httpClient);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Equal(2, this._handler.CapturedUris.Count); // One for getting the card, one for sending the message to the agent
Assert.Equal(new Uri("http://test-endpoint/agent"), this._handler.CapturedUris[1]);
}
[Fact]
public async Task GetAIAgentAsync_WithOptions_PassesOptionsToFactoryAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Options Agent",
Description = "Agent with multiple interfaces",
SupportedInterfaces =
[
new AgentInterface { Url = "http://httpjson/agent", ProtocolBinding = ProtocolBindingNames.HttpJson },
new AgentInterface { Url = "http://jsonrpc/agent", ProtocolBinding = ProtocolBindingNames.JsonRpc },
]
});
this._handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var options = new A2AClientOptions
{
PreferredBindings = [ProtocolBindingNames.JsonRpc]
};
var agent = await this._resolver.GetAIAgentAsync(httpClient: this._httpClient, options: options);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Equal(2, this._handler.CapturedUris.Count);
Assert.Equal(new Uri("http://jsonrpc/agent"), this._handler.CapturedUris[1]);
}
[Fact]
public async Task GetAIAgentAsync_WithAgentOptions_OverridesCardValuesAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Card Agent",
Description = "Card description",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
var agentOptions = new A2AAgentOptions
{
Id = "custom-id",
Name = "Custom Agent",
Description = "Custom description"
};
// Act
var agent = await this._resolver.GetAIAgentAsync(agentOptions, httpClient: this._httpClient);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("custom-id", agent.Id);
Assert.Equal("Custom Agent", agent.Name);
Assert.Equal("Custom description", agent.Description);
}
[Fact]
public async Task GetAIAgentAsync_WithAgentOptions_FallsBackToCardValuesAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Card Agent",
Description = "Card description",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
var agentOptions = new A2AAgentOptions
{
Id = "custom-id"
};
// Act
var agent = await this._resolver.GetAIAgentAsync(agentOptions, httpClient: this._httpClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("custom-id", agent.Id);
Assert.Equal("Card Agent", agent.Name);
Assert.Equal("Card description", agent.Description);
}
public void Dispose()
{
this._handler.Dispose();
this._httpClient.Dispose();
}
internal sealed class HttpMessageHandlerStub : HttpMessageHandler
{
public Queue ResponsesToReturn { get; } = new();
public List<Uri> CapturedUris { get; } = [];
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.CapturedUris.Add(request.RequestUri!);
var response = this.ResponsesToReturn.Dequeue();
if (response is AgentCard agentCard)
{
var json = JsonSerializer.Serialize(agentCard);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
}
else if (response is Message message)
{
var sendMessageResponse = new SendMessageResponse { Message = message };
var jsonRpcResponse = new JsonRpcResponse
{
Id = "response-id",
Result = JsonSerializer.SerializeToNode(sendMessageResponse, A2AJsonUtilities.DefaultOptions)
};
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions), Encoding.UTF8, "application/json")
};
}
// Return empty agent card if none specified
var emptyCard = new AgentCard();
var emptyJson = JsonSerializer.Serialize(emptyCard);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(emptyJson, Encoding.UTF8, "application/json")
};
}
}
}
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using A2A;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the A2AClientExtensions class.
/// </summary>
public sealed class A2AClientExtensionsTests
{
[Fact]
public void AsAIAgent_WithAllParameters_ReturnsA2AAgentWithSpecifiedProperties()
{
// Arrange
var a2aClient = new A2AClient(new Uri("http://test-endpoint"));
const string TestId = "test-agent-id";
const string TestName = "Test Agent";
const string TestDescription = "This is a test agent description";
// Act
var agent = a2aClient.AsAIAgent(TestId, TestName, TestDescription);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal(TestId, agent.Id);
Assert.Equal(TestName, agent.Name);
Assert.Equal(TestDescription, agent.Description);
}
[Fact]
public void AsAIAgent_WithIA2AClient_ReturnsA2AAgentWithSpecifiedProperties()
{
// Arrange - use IA2AClient reference type to verify the extension method works with the interface
IA2AClient a2aClient = new A2AClient(new Uri("http://test-endpoint"));
const string TestId = "ia2a-agent-id";
const string TestName = "IA2A Agent";
const string TestDescription = "Agent created from IA2AClient";
// Act
var agent = a2aClient.AsAIAgent(TestId, TestName, TestDescription);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal(TestId, agent.Id);
Assert.Equal(TestName, agent.Name);
Assert.Equal(TestDescription, agent.Description);
}
[Fact]
public void AsAIAgent_WithIA2AClient_ExposesClientViaGetService()
{
// Arrange
IA2AClient a2aClient = new A2AClient(new Uri("http://test-endpoint"));
// Act
var agent = a2aClient.AsAIAgent();
// Assert
var service = agent.GetService(typeof(IA2AClient));
Assert.NotNull(service);
Assert.Same(a2aClient, service);
}
[Fact]
public void AsAIAgent_WithOptions_ReturnsA2AAgentWithSpecifiedProperties()
{
// Arrange
var a2aClient = new A2AClient(new Uri("http://test-endpoint"));
var options = new A2AAgentOptions
{
Id = "options-agent-id",
Name = "Options Agent",
Description = "Agent created with options"
};
// Act
var agent = a2aClient.AsAIAgent(options);
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.Equal("options-agent-id", agent.Id);
Assert.Equal("Options Agent", agent.Name);
Assert.Equal("Agent created with options", agent.Description);
}
[Fact]
public void AsAIAgent_WithEmptyOptions_ReturnsA2AAgentWithDefaultProperties()
{
// Arrange
var a2aClient = new A2AClient(new Uri("http://test-endpoint"));
// Act
var agent = a2aClient.AsAIAgent(new A2AAgentOptions());
// Assert
Assert.NotNull(agent);
Assert.IsType<A2AAgent>(agent);
Assert.NotNull(agent.Id);
Assert.NotEmpty(agent.Id);
Assert.Null(agent.Name);
Assert.Null(agent.Description);
}
}
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using A2A;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="AgentTaskStatusExtensions"/> class.
/// </summary>
public sealed class AgentTaskStatusExtensionsTests
{
[Fact]
public void GetUserInputRequests_WithNullMessage_ReturnsNull()
{
// Arrange
var status = new TaskStatus
{
State = TaskState.InputRequired,
Message = null,
};
// Act
IList<AIContent>? result = status.GetUserInputRequests();
// Assert
Assert.Null(result);
}
[Fact]
public void GetUserInputRequests_WithNotInputRequiredState_ReturnsNull()
{
// Arrange
var status = new TaskStatus
{
State = TaskState.Completed,
Message = new Message { Parts = [Part.FromText("Some text")] },
};
// Act
IList<AIContent>? result = status.GetUserInputRequests();
// Assert
Assert.Null(result);
}
[Fact]
public void GetUserInputRequests_WithInputRequiredStateAndMultipleRequests_ReturnsAIContentList()
{
// Arrange
var status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message
{
Parts =
[
Part.FromText("First request"),
Part.FromText("Second request"),
Part.FromText("Third request")
],
},
};
// Act
IList<AIContent>? result = status.GetUserInputRequests();
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.Equal("First request", Assert.IsType<TextContent>(result[0]).Text);
Assert.Equal("Second request", Assert.IsType<TextContent>(result[1]).Text);
Assert.Equal("Third request", Assert.IsType<TextContent>(result[2]).Text);
}
[Fact]
public void GetUserInputRequests_WithTextParts_SetsRawRepresentationAndAdditionalPropertiesCorrectly()
{
// Arrange
var textPart = Part.FromText("Input request");
textPart.Metadata = new Dictionary<string, System.Text.Json.JsonElement>
{
{ "key1", System.Text.Json.JsonSerializer.SerializeToElement("value1") },
{ "key2", System.Text.Json.JsonSerializer.SerializeToElement("value2") }
};
var status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message { Parts = [textPart] },
};
// Act
IList<AIContent>? result = status.GetUserInputRequests();
// Assert
Assert.NotNull(result);
var content = Assert.IsType<TextContent>(result[0]);
Assert.Equal(textPart, content.RawRepresentation);
Assert.NotNull(content.AdditionalProperties);
Assert.True(content.AdditionalProperties.ContainsKey("key1"));
Assert.True(content.AdditionalProperties.ContainsKey("key2"));
}
[Fact]
public void GetUserInputRequests_WithEmptyMessageParts_ReturnsNull()
{
// Arrange
var status = new TaskStatus
{
State = TaskState.InputRequired,
Message = new Message { Parts = [] },
};
// Act
IList<AIContent>? result = status.GetUserInputRequests();
// Assert
Assert.Null(result);
}
}
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using A2A;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.A2A.UnitTests;
/// <summary>
/// Unit tests for the <see cref="ChatMessageExtensions"/> class.
/// </summary>
public sealed class ChatMessageExtensionsTests
{
[Fact]
public void ToA2AMessage_WithMessageContainingMultipleContents_AddsAllContentsAsParts()
{
// Arrange
var contents = new List<AIContent>
{
new UriContent("https://example.com/report.pdf", "file/pdf"),
new TextContent("please summarize the file content"),
new TextContent("and send it to me over email")
};
var chatMessage = new ChatMessage(ChatRole.User, contents);
var messages = new List<ChatMessage> { chatMessage };
// Act
var a2aMessage = messages.ToA2AMessage();
// Assert
Assert.NotNull(a2aMessage);
Assert.NotNull(a2aMessage.MessageId);
Assert.NotEmpty(a2aMessage.MessageId);
Assert.Equal(Role.User, a2aMessage.Role);
Assert.NotNull(a2aMessage.Parts);
Assert.Equal(3, a2aMessage.Parts.Count);
Assert.Equal(PartContentCase.Url, a2aMessage.Parts[0].ContentCase);
Assert.Equal("https://example.com/report.pdf", a2aMessage.Parts[0].Url);
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[1].ContentCase);
Assert.Equal("please summarize the file content", a2aMessage.Parts[1].Text);
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[2].ContentCase);
Assert.Equal("and send it to me over email", a2aMessage.Parts[2].Text);
}
[Fact]
public void ToA2AMessage_WithMixedMessages_AddsAllContentsAsParts()
{
// Arrange
var firstMessage = new ChatMessage(ChatRole.User, [
new UriContent("https://example.com/report.pdf", "file/pdf"),
]);
var secondMessage = new ChatMessage(ChatRole.User, [
new TextContent("please summarize the file content")
]);
var thirdMessage = new ChatMessage(ChatRole.User, [
new TextContent("and send it to me over email")
]);
var messages = new List<ChatMessage> { firstMessage, secondMessage, thirdMessage };
// Act
var a2aMessage = messages.ToA2AMessage();
// Assert
Assert.NotNull(a2aMessage);
Assert.NotNull(a2aMessage.MessageId);
Assert.NotEmpty(a2aMessage.MessageId);
Assert.Equal(Role.User, a2aMessage.Role);
Assert.NotNull(a2aMessage.Parts);
Assert.Equal(3, a2aMessage.Parts.Count);
Assert.Equal(PartContentCase.Url, a2aMessage.Parts[0].ContentCase);
Assert.Equal("https://example.com/report.pdf", a2aMessage.Parts[0].Url);
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[1].ContentCase);
Assert.Equal("please summarize the file content", a2aMessage.Parts[1].Text);
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[2].ContentCase);
Assert.Equal("and send it to me over email", a2aMessage.Parts[2].Text);
}
}