// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using A2A; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.A2A.UnitTests; /// /// Unit tests for the class. /// public sealed class A2AAgentTests : IDisposable { private readonly HttpClient _httpClient; private readonly A2AClientHttpMessageHandlerStub _handler; private readonly A2AClient _a2aClient; private readonly A2AAgent _agent; public A2AAgentTests() { this._handler = new A2AClientHttpMessageHandlerStub(); this._httpClient = new HttpClient(this._handler, false); this._a2aClient = new A2AClient(new Uri("http://test-endpoint"), this._httpClient); this._agent = new A2AAgent(this._a2aClient); } [Fact] public void Constructor_WithAllParameters_InitializesPropertiesCorrectly() { // Arrange const string TestId = "test-id"; const string TestName = "test-name"; const string TestDescription = "test-description"; // Act var agent = new A2AAgent(this._a2aClient, TestId, TestName, TestDescription); // Assert Assert.Equal(TestId, agent.Id); Assert.Equal(TestName, agent.Name); Assert.Equal(TestDescription, agent.Description); } [Fact] public void Constructor_WithNullA2AClient_ThrowsArgumentNullException() => // Act & Assert Assert.Throws(() => new A2AAgent(null!)); [Fact] public void Constructor_WithIA2AClient_InitializesCorrectly() { // Arrange IA2AClient ia2aClient = this._a2aClient; // Act var agent = new A2AAgent(ia2aClient, "ia2a-id", "IA2A Agent", "An agent from IA2AClient"); // Assert Assert.Equal("ia2a-id", agent.Id); Assert.Equal("IA2A Agent", agent.Name); Assert.Equal("An agent from IA2AClient", agent.Description); } [Fact] public void Constructor_WithDefaultParameters_UsesBaseProperties() { // Act var agent = new A2AAgent(this._a2aClient); // Assert Assert.NotNull(agent.Id); Assert.NotEmpty(agent.Id); Assert.Null(agent.Name); Assert.Null(agent.Description); } [Fact] public void Constructor_WithOptions_InitializesPropertiesCorrectly() { // Arrange var options = new A2AAgentOptions { Id = "options-id", Name = "options-name", Description = "options-description" }; // Act var agent = new A2AAgent(this._a2aClient, options); // Assert Assert.Equal("options-id", agent.Id); Assert.Equal("options-name", agent.Name); Assert.Equal("options-description", agent.Description); } [Fact] public void Constructor_WithOptions_IsolatesAgentFromOptionsMutation() { // Arrange var options = new A2AAgentOptions { Id = "original-id", Name = "Original Name", Description = "Original Description" }; var agent = new A2AAgent(this._a2aClient, options); // Act - mutate options after agent construction options.Id = "mutated-id"; options.Name = "Mutated Name"; options.Description = "Mutated Description"; // Assert - agent should retain original values Assert.Equal("original-id", agent.Id); Assert.Equal("Original Name", agent.Name); Assert.Equal("Original Description", agent.Description); } [Fact] public void Constructor_WithNullOptions_ThrowsArgumentNullException() => // Act & Assert Assert.Throws(() => new A2AAgent(this._a2aClient, options: null!)); [Fact] public void Constructor_WithEmptyOptions_UsesBaseProperties() { // Act var agent = new A2AAgent(this._a2aClient, new A2AAgentOptions()); // Assert Assert.NotNull(agent.Id); Assert.NotEmpty(agent.Id); Assert.Null(agent.Name); Assert.Null(agent.Description); } [Fact] public void Constructor_WithOptions_NullA2AClient_ThrowsArgumentNullException() => // Act & Assert Assert.Throws(() => new A2AAgent(null!, new A2AAgentOptions())); [Fact] public async Task RunAsync_AllowsNonUserRoleMessagesAsync() { // Arrange var inputMessages = new List { new(ChatRole.System, "I am a system message"), new(ChatRole.Assistant, "I am an assistant message"), new(ChatRole.User, "Valid user message") }; // Act & Assert await this._agent.RunAsync(inputMessages); } [Fact] public async Task RunAsync_WithValidUserMessage_RunsSuccessfullyAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [ new Part { Text = "Hello! How can I help you today?" } ] } }; var inputMessages = new List { new(ChatRole.User, "Hello, world!") }; // Act var result = await this._agent.RunAsync(inputMessages); // Assert input message sent to A2AClient var inputMessage = this._handler.CapturedSendMessageRequest?.Message; Assert.NotNull(inputMessage); Assert.Single(inputMessage.Parts); Assert.Equal(Role.User, inputMessage.Role); Assert.Equal("Hello, world!", inputMessage.Parts[0].Text); // Assert response from A2AClient is converted correctly Assert.NotNull(result); Assert.Equal(this._agent.Id, result.AgentId); Assert.Equal("response-123", result.ResponseId); Assert.NotNull(result.RawRepresentation); Assert.IsType(result.RawRepresentation); Assert.Equal("response-123", ((Message)result.RawRepresentation).MessageId); Assert.Single(result.Messages); Assert.Equal(ChatRole.Assistant, result.Messages[0].Role); Assert.Equal("Hello! How can I help you today?", result.Messages[0].Text); Assert.Equal(ChatFinishReason.Stop, result.FinishReason); } [Fact] public async Task RunAsync_WithNewSession_UpdatesSessionConversationIdAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [ new Part { Text = "Response" } ], ContextId = "new-context-id" } }; var inputMessages = new List { new(ChatRole.User, "Test message") }; var session = await this._agent.CreateSessionAsync(); // Act await this._agent.RunAsync(inputMessages, session); // Assert Assert.IsType(session); var a2aSession = (A2AAgentSession)session; Assert.Equal("new-context-id", a2aSession.ContextId); } [Fact] public async Task RunAsync_WithExistingSession_SetConversationIdToMessageAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var session = await this._agent.CreateSessionAsync(); var a2aSession = (A2AAgentSession)session; a2aSession.ContextId = "existing-context-id"; // Act await this._agent.RunAsync(inputMessages, session); // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.NotNull(message); Assert.Equal("existing-context-id", message.ContextId); } [Fact] public async Task RunAsync_WithSessionHavingDifferentContextId_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [ new Part { Text = "Response" } ], ContextId = "different-context" } }; var session = await this._agent.CreateSessionAsync(); var a2aSession = (A2AAgentSession)session; a2aSession.ContextId = "existing-context-id"; // Act & Assert await Assert.ThrowsAsync(() => this._agent.RunAsync(inputMessages, session)); } [Fact] public async Task RunStreamingAsync_WithValidUserMessage_YieldsAgentResponseUpdatesAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Hello, streaming!") }; this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-1", Role = Role.Agent, Parts = [new Part { Text = "Hello" }], ContextId = "stream-context" } }; // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync(inputMessages)) { updates.Add(update); } // Assert Assert.Single(updates); // Assert input message sent to A2AClient var inputMessage = this._handler.CapturedSendMessageRequest?.Message; Assert.NotNull(inputMessage); Assert.Single(inputMessage.Parts); Assert.Equal(Role.User, inputMessage.Role); Assert.Equal("Hello, streaming!", inputMessage.Parts[0].Text); // Assert response from A2AClient is converted correctly Assert.Equal(ChatRole.Assistant, updates[0].Role); Assert.Equal("Hello", updates[0].Text); Assert.Equal("stream-1", updates[0].MessageId); Assert.Equal(this._agent.Id, updates[0].AgentId); Assert.Equal("stream-1", updates[0].ResponseId); Assert.Equal(ChatFinishReason.Stop, updates[0].FinishReason); Assert.IsType(updates[0].RawRepresentation); Assert.Equal("stream-1", ((Message)updates[0].RawRepresentation!).MessageId); } [Fact] public async Task RunStreamingAsync_WithSession_UpdatesSessionConversationIdAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test streaming") }; this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-1", Role = Role.Agent, Parts = [new Part { Text = "Response" }], ContextId = "new-stream-context" } }; var session = await this._agent.CreateSessionAsync(); // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, session)) { // Just iterate through to trigger the logic } // Assert var a2aSession = (A2AAgentSession)session; Assert.Equal("new-stream-context", a2aSession.ContextId); } [Fact] public async Task RunStreamingAsync_WithExistingSession_SetConversationIdToMessageAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test streaming") }; this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message() }; var session = await this._agent.CreateSessionAsync(); var a2aSession = (A2AAgentSession)session; a2aSession.ContextId = "existing-context-id"; // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, session)) { // Just iterate through to trigger the logic } // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.NotNull(message); Assert.Equal("existing-context-id", message.ContextId); } [Fact] public async Task RunStreamingAsync_WithSessionHavingDifferentContextId_ThrowsInvalidOperationExceptionAsync() { // Arrange var session = await this._agent.CreateSessionAsync(); var a2aSession = (A2AAgentSession)session; a2aSession.ContextId = "existing-context-id"; var inputMessages = new List { new(ChatRole.User, "Test streaming") }; this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-1", Role = Role.Agent, Parts = [new Part { Text = "Response" }], ContextId = "different-context" } }; // Act await Assert.ThrowsAsync(async () => { await foreach (var update in this._agent.RunStreamingAsync(inputMessages, session)) { } }); } [Fact] public async Task RunStreamingAsync_AllowsNonUserRoleMessagesAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-1", Role = Role.Agent, Parts = [new Part { Text = "Response" }], ContextId = "new-stream-context" } }; var inputMessages = new List { new(ChatRole.System, "I am a system message"), new(ChatRole.Assistant, "I am an assistant message"), new(ChatRole.User, "Valid user message") }; // Act & Assert await foreach (var _ in this._agent.RunStreamingAsync(inputMessages)) { // Just iterate through to trigger the logic } } [Fact] public async Task RunAsync_WithHostedFileContent_ConvertsToFilePartAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, [ new TextContent("Check this file:"), new UriContent("https://example.com/file.pdf", "application/pdf") ]) }; // Act await this._agent.RunAsync(inputMessages); // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.NotNull(message); Assert.Equal(2, message.Parts.Count); Assert.Equal(PartContentCase.Text, message.Parts[0].ContentCase); Assert.Equal("Check this file:", message.Parts[0].Text); Assert.Equal(PartContentCase.Url, message.Parts[1].ContentCase); Assert.Equal("https://example.com/file.pdf", message.Parts[1].Url); } [Fact] public async Task RunAsync_WithContinuationTokenAndMessages_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") }; // Act & Assert await Assert.ThrowsAsync(() => this._agent.RunAsync(inputMessages, null, options)); } [Fact] public async Task RunAsync_WithContinuationToken_CallsGetTaskAsyncAsync() { // Arrange this._handler.AgentTaskToReturn = new AgentTask { Id = "task-123", ContextId = "context-123", Status = new() { State = TaskState.Submitted } }; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") }; // Act await this._agent.RunAsync([], options: options); // Assert Assert.Equal("GetTask", this._handler.CapturedJsonRpcRequest?.Method); Assert.Equal("task-123", this._handler.CapturedGetTaskRequest?.Id); } [Fact] public async Task RunAsync_WithTaskInSessionAndMessage_AddTaskAsReferencesToMessageAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Response to task" }] } }; var session = (A2AAgentSession)await this._agent.CreateSessionAsync(); session.TaskId = "task-123"; var inputMessage = new ChatMessage(ChatRole.User, "Please make the background transparent"); // Act await this._agent.RunAsync(inputMessage, session); // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.Null(message?.TaskId); Assert.NotNull(message?.ReferenceTaskIds); Assert.Contains("task-123", message.ReferenceTaskIds); } [Fact] public async Task RunAsync_WithInputRequiredTaskState_SetsTaskIdOnMessageAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-456", Role = Role.Agent, Parts = [new Part { Text = "Booking confirmed" }] } }; var session = (A2AAgentSession)await this._agent.CreateSessionAsync(); session.TaskId = "task-123"; session.TaskState = TaskState.InputRequired; var inputMessage = new ChatMessage(ChatRole.User, [new TextContent("New York to London")]); // Act await this._agent.RunAsync(inputMessage, session); // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.Equal("task-123", message?.TaskId); Assert.Null(message?.ReferenceTaskIds); } [Fact] public async Task RunAsync_WithAgentTask_UpdatesSessionTaskIdAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Task = new AgentTask { Id = "task-456", ContextId = "context-789", Status = new() { State = TaskState.Submitted } } }; var session = await this._agent.CreateSessionAsync(); // Act await this._agent.RunAsync("Start a task", session); // Assert var a2aSession = (A2AAgentSession)session; Assert.Equal("task-456", a2aSession.TaskId); } [Fact] public async Task RunAsync_WithAgentTaskResponse_ReturnsTaskResponseCorrectlyAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Task = new AgentTask { Id = "task-789", ContextId = "context-456", Status = new() { State = TaskState.Submitted }, Metadata = new Dictionary { { "key1", JsonSerializer.SerializeToElement("value1") }, { "count", JsonSerializer.SerializeToElement(42) } } } }; var session = await this._agent.CreateSessionAsync(); // Act var result = await this._agent.RunAsync("Start a long-running task", session); // Assert - verify task is converted correctly Assert.NotNull(result); Assert.Equal(this._agent.Id, result.AgentId); Assert.Equal("task-789", result.ResponseId); Assert.Null(result.FinishReason); Assert.IsType(result.RawRepresentation); Assert.Equal("task-789", ((AgentTask)result.RawRepresentation).Id); // Assert - verify continuation token is set for submitted task Assert.NotNull(result.ContinuationToken); Assert.IsType(result.ContinuationToken); Assert.Equal("task-789", ((A2AContinuationToken)result.ContinuationToken).TaskId); // Assert - verify session is updated with context and task IDs var a2aSession = (A2AAgentSession)session; Assert.Equal("context-456", a2aSession.ContextId); Assert.Equal("task-789", a2aSession.TaskId); // Assert - verify metadata is preserved Assert.NotNull(result.AdditionalProperties); Assert.NotNull(result.AdditionalProperties["key1"]); Assert.Equal("value1", ((JsonElement)result.AdditionalProperties["key1"]!).GetString()); Assert.NotNull(result.AdditionalProperties["count"]); Assert.Equal(42, ((JsonElement)result.AdditionalProperties["count"]!).GetInt32()); } [Theory] [InlineData(TaskState.Submitted)] [InlineData(TaskState.Working)] [InlineData(TaskState.Completed)] [InlineData(TaskState.Failed)] [InlineData(TaskState.Canceled)] [InlineData(TaskState.InputRequired)] public async Task RunAsync_WithVariousTaskStates_ReturnsCorrectTokenAsync(TaskState taskState) { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Task = new AgentTask { Id = "task-123", ContextId = "context-123", Status = new() { State = taskState } } }; // Act var result = await this._agent.RunAsync("Test message"); // Assert if (taskState is TaskState.Submitted or TaskState.Working) { Assert.NotNull(result.ContinuationToken); } else { Assert.Null(result.ContinuationToken); } if (taskState is TaskState.Completed) { Assert.Equal(ChatFinishReason.Stop, result.FinishReason); } else { Assert.Null(result.FinishReason); } } [Fact] public async Task RunStreamingAsync_WithContinuationTokenAndMessages_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") }; // Act & Assert await Assert.ThrowsAsync(async () => { await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options)) { // Just iterate through to trigger the exception } }); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_UsesSubscribeToTaskMethodAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Continuation response" }] } }; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-456") }; // Act await foreach (var _ in this._agent.RunStreamingAsync([], null, options)) { // Just iterate through to trigger the logic } // Assert - verify SubscribeToTask was called (not SendStreamingMessage) Assert.Single(this._handler.CapturedJsonRpcRequests); Assert.Equal("SubscribeToTask", this._handler.CapturedJsonRpcRequests[0].Method); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_PassesCorrectTaskIdAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Continuation response" }] } }; const string ExpectedTaskId = "my-task-789"; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(ExpectedTaskId) }; // Act await foreach (var _ in this._agent.RunStreamingAsync([], null, options)) { // Just iterate through to trigger the logic } // Assert - verify the task ID was passed correctly Assert.NotEmpty(this._handler.CapturedJsonRpcRequests); var subscribeRequest = this._handler.CapturedJsonRpcRequests[0]; var subscribeParams = subscribeRequest.Params?.Deserialize(A2AJsonUtilities.DefaultOptions); Assert.NotNull(subscribeParams); Assert.Equal(ExpectedTaskId, subscribeParams.Id); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_WhenSubscribeFailsWithUnsupportedOperation_FallsBackToGetTaskAsync() { // Arrange const string TaskId = "completed-task-123"; const string ContextId = "ctx-completed"; this._handler.StreamingErrorCodeToReturn = A2AErrorCode.UnsupportedOperation; this._handler.AgentTaskToReturn = new AgentTask { Id = TaskId, ContextId = ContextId, Status = new() { State = TaskState.Completed }, Artifacts = [ new() { ArtifactId = "art-1", Parts = [new Part { Text = "Final result" }] } ] }; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(TaskId) }; // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync([], null, options)) { updates.Add(update); } // Assert - should yield one update from GetTaskAsync fallback Assert.Single(updates); var update0 = updates[0]; Assert.Equal(TaskId, update0.ResponseId); Assert.Equal(ChatFinishReason.Stop, update0.FinishReason); Assert.IsType(update0.RawRepresentation); Assert.Equal(TaskId, ((AgentTask)update0.RawRepresentation!).Id); // Assert - both SubscribeToTask and GetTask were called Assert.Equal(2, this._handler.CapturedJsonRpcRequests.Count); Assert.Equal("SubscribeToTask", this._handler.CapturedJsonRpcRequests[0].Method); Assert.Equal("GetTask", this._handler.CapturedJsonRpcRequests[1].Method); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_WhenSubscribeFailsWithUnsupportedOperation_UpdatesSessionAsync() { // Arrange const string TaskId = "completed-task-456"; const string ContextId = "ctx-completed-456"; this._handler.StreamingErrorCodeToReturn = A2AErrorCode.UnsupportedOperation; this._handler.AgentTaskToReturn = new AgentTask { Id = TaskId, ContextId = ContextId, Status = new() { State = TaskState.Completed } }; var session = await this._agent.CreateSessionAsync(); var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(TaskId) }; // Act await foreach (var _ in this._agent.RunStreamingAsync([], session, options)) { // Just iterate through to trigger the logic } // Assert - session should be updated with the task state from GetTaskAsync var a2aSession = (A2AAgentSession)session; Assert.Equal(ContextId, a2aSession.ContextId); Assert.Equal(TaskId, a2aSession.TaskId); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_WhenSubscribeFailsWithNonUnsupportedError_PropagatesWithoutFallbackAsync() { // Arrange const string TaskId = "error-task-123"; this._handler.StreamingErrorCodeToReturn = A2AErrorCode.TaskNotFound; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(TaskId) }; // Act & Assert - the A2AException should propagate directly without fallback to GetTask var exception = await Assert.ThrowsAsync(async () => { await foreach (var _ in this._agent.RunStreamingAsync([], null, options)) { } }); Assert.Equal(A2AErrorCode.TaskNotFound, exception.ErrorCode); // Assert - only SubscribeToTask was called, no fallback to GetTask Assert.Single(this._handler.CapturedJsonRpcRequests); Assert.Equal("SubscribeToTask", this._handler.CapturedJsonRpcRequests[0].Method); } [Fact] public async Task RunStreamingAsync_WithContinuationToken_WhenSubscribeAndGetTaskBothFail_PropagatesExceptionAsync() { // Arrange const string TaskId = "failed-task-789"; this._handler.StreamingErrorCodeToReturn = A2AErrorCode.UnsupportedOperation; this._handler.GetTaskErrorCodeToReturn = A2AErrorCode.TaskNotFound; var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(TaskId) }; // Act & Assert - the A2AException from GetTaskAsync should propagate to the caller var exception = await Assert.ThrowsAsync(async () => { await foreach (var _ in this._agent.RunStreamingAsync([], null, options)) { } }); Assert.Equal(A2AErrorCode.TaskNotFound, exception.ErrorCode); // Assert - both SubscribeToTask and GetTask were called Assert.Equal(2, this._handler.CapturedJsonRpcRequests.Count); Assert.Equal("SubscribeToTask", this._handler.CapturedJsonRpcRequests[0].Method); Assert.Equal("GetTask", this._handler.CapturedJsonRpcRequests[1].Method); } [Fact] public async Task RunStreamingAsync_WithTaskInSessionAndMessage_AddTaskAsReferencesToMessageAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Response to task" }] } }; var session = (A2AAgentSession)await this._agent.CreateSessionAsync(); session.TaskId = "task-123"; // Act await foreach (var _ in this._agent.RunStreamingAsync("Please make the background transparent", session)) { // Just iterate through to trigger the logic } // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.Null(message?.TaskId); Assert.NotNull(message?.ReferenceTaskIds); Assert.Contains("task-123", message.ReferenceTaskIds); } [Fact] public async Task RunStreamingAsync_WithInputRequiredTaskState_SetsTaskIdOnMessageAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "response-456", Role = Role.Agent, Parts = [new Part { Text = "Booking confirmed" }] } }; var session = (A2AAgentSession)await this._agent.CreateSessionAsync(); session.TaskId = "task-123"; session.TaskState = TaskState.InputRequired; var inputMessage = new ChatMessage(ChatRole.User, [new TextContent("New York to London")]); // Act await foreach (var _ in this._agent.RunStreamingAsync([inputMessage], session)) { // Just iterate through to trigger the logic } // Assert var message = this._handler.CapturedSendMessageRequest?.Message; Assert.Equal("task-123", message?.TaskId); Assert.Null(message?.ReferenceTaskIds); } [Fact] public async Task RunStreamingAsync_WithAgentTask_UpdatesSessionTaskIdAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Task = new AgentTask { Id = "task-456", ContextId = "context-789", Status = new() { State = TaskState.Submitted } } }; var session = await this._agent.CreateSessionAsync(); // Act await foreach (var _ in this._agent.RunStreamingAsync("Start a task", session)) { // Just iterate through to trigger the logic } // Assert var a2aSession = (A2AAgentSession)session; Assert.Equal("task-456", a2aSession.TaskId); } [Fact] public async Task RunStreamingAsync_WithAgentMessage_YieldsResponseUpdateAsync() { // Arrange const string MessageId = "msg-123"; const string ContextId = "ctx-456"; const string MessageText = "Hello from agent!"; this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = MessageId, Role = Role.Agent, ContextId = ContextId, Parts = [ new Part { Text = MessageText } ] } }; // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync("Test message")) { updates.Add(update); } // Assert - one update should be yielded Assert.Single(updates); var update0 = updates[0]; Assert.Equal(ChatRole.Assistant, update0.Role); Assert.Equal(MessageId, update0.MessageId); Assert.Equal(MessageId, update0.ResponseId); Assert.Equal(this._agent.Id, update0.AgentId); Assert.Equal(MessageText, update0.Text); Assert.Equal(ChatFinishReason.Stop, update0.FinishReason); Assert.IsType(update0.RawRepresentation); Assert.Equal(MessageId, ((Message)update0.RawRepresentation!).MessageId); } [Fact] public async Task RunStreamingAsync_WithAgentTask_YieldsResponseUpdateAsync() { // Arrange const string TaskId = "task-789"; const string ContextId = "ctx-012"; this._handler.StreamingResponseToReturn = new StreamResponse { Task = new AgentTask { Id = TaskId, ContextId = ContextId, Status = new() { State = TaskState.Submitted }, Artifacts = [ new() { ArtifactId = "art-123", Parts = [new Part { Text = "Task artifact content" }] } ] } }; var session = await this._agent.CreateSessionAsync(); // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync("Start long-running task", session)) { updates.Add(update); } // Assert - one update should be yielded from artifact Assert.Single(updates); var update0 = updates[0]; Assert.Equal(ChatRole.Assistant, update0.Role); Assert.Equal(TaskId, update0.ResponseId); Assert.Equal(this._agent.Id, update0.AgentId); Assert.Null(update0.FinishReason); Assert.IsType(update0.RawRepresentation); Assert.Equal(TaskId, ((AgentTask)update0.RawRepresentation!).Id); // Assert - session should be updated with context and task IDs var a2aSession = (A2AAgentSession)session; Assert.Equal(ContextId, a2aSession.ContextId); Assert.Equal(TaskId, a2aSession.TaskId); } [Fact] public async Task RunStreamingAsync_WithTaskStatusUpdateEvent_YieldsResponseUpdateAsync() { // Arrange const string TaskId = "task-status-123"; const string ContextId = "ctx-status-456"; this._handler.StreamingResponseToReturn = new StreamResponse { StatusUpdate = new TaskStatusUpdateEvent { TaskId = TaskId, ContextId = ContextId, Status = new() { State = TaskState.Working } } }; var session = await this._agent.CreateSessionAsync(); // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync("Check task status", session)) { updates.Add(update); } // Assert - one update should be yielded Assert.Single(updates); var update0 = updates[0]; Assert.Equal(ChatRole.Assistant, update0.Role); Assert.Equal(TaskId, update0.ResponseId); Assert.Equal(this._agent.Id, update0.AgentId); Assert.Null(update0.FinishReason); Assert.IsType(update0.RawRepresentation); // Assert - session should be updated with context and task IDs var a2aSession = (A2AAgentSession)session; Assert.Equal(ContextId, a2aSession.ContextId); Assert.Equal(TaskId, a2aSession.TaskId); } [Fact] public async Task RunStreamingAsync_WithInputRequiredStatusUpdate_YieldsStatusContentsAsync() { // Arrange const string TaskId = "task-input-123"; const string ContextId = "ctx-input-456"; this._handler.StreamingResponseToReturn = new StreamResponse { StatusUpdate = new TaskStatusUpdateEvent { TaskId = TaskId, ContextId = ContextId, Status = new() { State = TaskState.InputRequired, Message = new Message { Parts = [Part.FromText("Where would you like to fly?")] } } } }; var session = await this._agent.CreateSessionAsync(); // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync("I'd like to book a flight.", session)) { updates.Add(update); } // Assert Assert.Single(updates); var update0 = updates[0]; Assert.Equal(TaskId, update0.ResponseId); Assert.Null(update0.FinishReason); var textContent = Assert.Single(update0.Contents.OfType()); Assert.Equal("Where would you like to fly?", textContent.Text); } [Fact] public async Task RunStreamingAsync_WithTaskArtifactUpdateEvent_YieldsResponseUpdateAsync() { // Arrange const string TaskId = "task-artifact-123"; const string ContextId = "ctx-artifact-456"; const string ArtifactContent = "Task artifact data"; this._handler.StreamingResponseToReturn = new StreamResponse { ArtifactUpdate = new TaskArtifactUpdateEvent { TaskId = TaskId, ContextId = ContextId, Artifact = new() { ArtifactId = "artifact-789", Parts = [new Part { Text = ArtifactContent }] } } }; var session = await this._agent.CreateSessionAsync(); // Act var updates = new List(); await foreach (var update in this._agent.RunStreamingAsync("Process artifact", session)) { updates.Add(update); } // Assert - one update should be yielded Assert.Single(updates); var update0 = updates[0]; Assert.Equal(ChatRole.Assistant, update0.Role); Assert.Equal(TaskId, update0.ResponseId); Assert.Equal(this._agent.Id, update0.AgentId); Assert.Null(update0.FinishReason); Assert.IsType(update0.RawRepresentation); // Assert - artifact content should be in the update Assert.NotEmpty(update0.Contents); Assert.Equal(ArtifactContent, update0.Text); // Assert - session should be updated with context and task IDs var a2aSession = (A2AAgentSession)session; Assert.Equal(ContextId, a2aSession.ContextId); Assert.Equal(TaskId, a2aSession.TaskId); } [Fact] public async Task RunAsync_WithAllowBackgroundResponsesAndNoSession_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { AllowBackgroundResponses = true }; // Act & Assert await Assert.ThrowsAsync(() => this._agent.RunAsync(inputMessages, null, options)); } [Fact] public async Task RunStreamingAsync_WithAllowBackgroundResponsesAndNoSession_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { AllowBackgroundResponses = true }; // Act & Assert await Assert.ThrowsAsync(async () => { await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options)) { // Just iterate through to trigger the exception } }); } [Fact] public async Task RunAsync_WithAgentMessageResponseMetadata_ReturnsMetadataAsAdditionalPropertiesAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Response with metadata" }], Metadata = new Dictionary { { "responseKey1", JsonSerializer.SerializeToElement("responseValue1") }, { "responseCount", JsonSerializer.SerializeToElement(99) } } } }; var inputMessages = new List { new(ChatRole.User, "Test message") }; // Act var result = await this._agent.RunAsync(inputMessages); // Assert Assert.NotNull(result.AdditionalProperties); Assert.NotNull(result.AdditionalProperties["responseKey1"]); Assert.Equal("responseValue1", ((JsonElement)result.AdditionalProperties["responseKey1"]!).GetString()); Assert.NotNull(result.AdditionalProperties["responseCount"]); Assert.Equal(99, ((JsonElement)result.AdditionalProperties["responseCount"]!).GetInt32()); } [Fact] public async Task RunAsync_WithAdditionalProperties_PropagatesThemAsMetadataToSendMessageRequestAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Response" }] } }; var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { AdditionalProperties = new() { { "key1", "value1" }, { "key2", 42 }, { "key3", true } } }; // Act await this._agent.RunAsync(inputMessages, null, options); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Metadata); Assert.Equal("value1", this._handler.CapturedSendMessageRequest.Metadata["key1"].GetString()); Assert.Equal(42, this._handler.CapturedSendMessageRequest.Metadata["key2"].GetInt32()); Assert.True(this._handler.CapturedSendMessageRequest.Metadata["key3"].GetBoolean()); } [Fact] public async Task RunAsync_WithNullAdditionalProperties_DoesNotSetMetadataAsync() { // Arrange this._handler.ResponseToReturn = new SendMessageResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Response" }] } }; var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { AdditionalProperties = null }; // Act await this._agent.RunAsync(inputMessages, null, options); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.Null(this._handler.CapturedSendMessageRequest.Metadata); } [Fact] public async Task RunStreamingAsync_WithAdditionalProperties_PropagatesThemAsMetadataToSendMessageRequestAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-123", Role = Role.Agent, Parts = [new Part { Text = "Streaming response" }] } }; var inputMessages = new List { new(ChatRole.User, "Test streaming message") }; var options = new AgentRunOptions { AdditionalProperties = new() { { "streamKey1", "streamValue1" }, { "streamKey2", 100 }, { "streamKey3", false } } }; // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options)) { } // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Metadata); Assert.Equal("streamValue1", this._handler.CapturedSendMessageRequest.Metadata["streamKey1"].GetString()); Assert.Equal(100, this._handler.CapturedSendMessageRequest.Metadata["streamKey2"].GetInt32()); Assert.False(this._handler.CapturedSendMessageRequest.Metadata["streamKey3"].GetBoolean()); } [Fact] public async Task RunStreamingAsync_WithNullAdditionalProperties_DoesNotSetMetadataAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "stream-123", Role = Role.Agent, Parts = [new Part { Text = "Streaming response" }] } }; var inputMessages = new List { new(ChatRole.User, "Test streaming message") }; var options = new AgentRunOptions { AdditionalProperties = null }; // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options)) { } // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.Null(this._handler.CapturedSendMessageRequest.Metadata); } [Fact] public async Task RunAsync_WithDefaultOptions_SetsBlockingToTrueAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; // Act await this._agent.RunAsync(inputMessages); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration); Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately); } [Fact] public async Task RunAsync_WithAllowBackgroundResponsesTrue_SetsReturnImmediatelyToTrueAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var session = await this._agent.CreateSessionAsync(); var options = new AgentRunOptions { AllowBackgroundResponses = true }; // Act await this._agent.RunAsync(inputMessages, session, options); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration); Assert.True(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately); } [Fact] public async Task RunAsync_WithAllowBackgroundResponsesFalse_SetsReturnImmediatelyToFalseAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; var options = new AgentRunOptions { AllowBackgroundResponses = false }; // Act await this._agent.RunAsync(inputMessages, null, options); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration); Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately); } [Fact] public async Task RunAsync_WithNullOptions_SetsReturnImmediatelyToFalseAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; // Act await this._agent.RunAsync(inputMessages, null, null); // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration); Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately); } [Fact] public async Task RunStreamingAsync_SendMessageRequest_DoesNotSetReturnImmediatelyConfigurationAsync() { // Arrange this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message { MessageId = "response-123", Role = Role.Agent, Parts = [new Part { Text = "Streaming response" }] } }; var inputMessages = new List { new(ChatRole.User, "Test message") }; // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages)) { // Just iterate through to trigger the logic } // Assert Assert.NotNull(this._handler.CapturedSendMessageRequest); Assert.Null(this._handler.CapturedSendMessageRequest.Configuration); } [Fact] public async Task RunAsync_WithInvalidSessionType_ThrowsInvalidOperationExceptionAsync() { // Arrange // Create a session from a different agent type var invalidSession = new CustomAgentSession(); // Act & Assert await Assert.ThrowsAsync(() => this._agent.RunAsync(invalidSession)); } [Fact] public async Task RunStreamingAsync_WithInvalidSessionType_ThrowsInvalidOperationExceptionAsync() { // Arrange var inputMessages = new List { new(ChatRole.User, "Test message") }; // Create a session from a different agent type var invalidSession = new CustomAgentSession(); // Act & Assert await Assert.ThrowsAsync(async () => await this._agent.RunStreamingAsync(inputMessages, invalidSession).ToListAsync()); } #region GetService Method Tests /// /// Verify that GetService returns IA2AClient when requested. /// [Fact] public void GetService_RequestingIA2AClient_ReturnsA2AClient() { // Arrange & Act var result = this._agent.GetService(typeof(IA2AClient)); // Assert Assert.NotNull(result); Assert.Same(this._a2aClient, result); } /// /// Verify that GetService returns null when requesting the concrete A2AClient type /// since the agent now exposes IA2AClient instead. /// [Fact] public void GetService_RequestingConcreteA2AClient_ReturnsNull() { // Arrange & Act var result = this._agent.GetService(typeof(A2AClient)); // Assert Assert.Null(result); } /// /// Verify that GetService returns AIAgentMetadata when requested. /// [Fact] public void GetService_RequestingAIAgentMetadata_ReturnsMetadata() { // Arrange & Act var result = this._agent.GetService(typeof(AIAgentMetadata)); // Assert Assert.NotNull(result); Assert.IsType(result); var metadata = (AIAgentMetadata)result; Assert.Equal("a2a", metadata.ProviderName); } /// /// Verify that GetService returns null for unknown service types. /// [Fact] public void GetService_RequestingUnknownServiceType_ReturnsNull() { // Arrange & Act var result = this._agent.GetService(typeof(string)); // Assert Assert.Null(result); } /// /// Verify that GetService with serviceKey parameter returns null for unknown service types. /// [Fact] public void GetService_WithServiceKey_ReturnsNull() { // Arrange & Act var result = this._agent.GetService(typeof(string), "test-key"); // Assert Assert.Null(result); } /// /// Verify that GetService calls base.GetService() first and returns the agent itself when requesting A2AAgent type. /// [Fact] public void GetService_RequestingA2AAgentType_ReturnsBaseImplementation() { // Arrange & Act var result = this._agent.GetService(typeof(A2AAgent)); // Assert Assert.NotNull(result); Assert.Same(this._agent, result); } /// /// Verify that GetService calls base.GetService() first and returns the agent itself when requesting AIAgent type. /// [Fact] public void GetService_RequestingAIAgentType_ReturnsBaseImplementation() { // Arrange & Act var result = this._agent.GetService(typeof(AIAgent)); // Assert Assert.NotNull(result); Assert.Same(this._agent, result); } /// /// Verify that GetService calls base.GetService() first but continues to derived logic when base returns null. /// [Fact] public void GetService_RequestingIA2AClientWithServiceKey_CallsBaseFirstThenDerivedLogic() { // Arrange & Act - Request IA2AClient with a service key (base.GetService will return null due to serviceKey) var result = this._agent.GetService(typeof(IA2AClient), "some-key"); // Assert Assert.NotNull(result); Assert.Same(this._a2aClient, result); } /// /// Verify that GetService returns consistent AIAgentMetadata across multiple calls. /// [Fact] public void GetService_RequestingAIAgentMetadata_ReturnsConsistentMetadata() { // Arrange & Act var result1 = this._agent.GetService(typeof(AIAgentMetadata)); var result2 = this._agent.GetService(typeof(AIAgentMetadata)); // Assert Assert.NotNull(result1); Assert.NotNull(result2); Assert.Same(result1, result2); // Should return the same instance Assert.IsType(result1); var metadata = (AIAgentMetadata)result1; Assert.Equal("a2a", metadata.ProviderName); } /// /// Verify that CreateSessionAsync with contextId creates a session with the correct context ID. /// [Fact] public async Task CreateSessionAsync_WithContextId_CreatesSessionWithContextIdAsync() { // Arrange const string ContextId = "test-context-123"; // Act var session = await this._agent.CreateSessionAsync(ContextId); // Assert Assert.NotNull(session); Assert.IsType(session); var typedSession = (A2AAgentSession)session; Assert.Equal(ContextId, typedSession.ContextId); Assert.Null(typedSession.TaskId); } /// /// Verify that CreateSessionAsync with contextId and taskId creates a session with both IDs set correctly. /// [Fact] public async Task CreateSessionAsync_WithContextIdAndTaskId_CreatesSessionWithBothIdsAsync() { // Arrange const string ContextId = "test-context-456"; const string TaskId = "test-task-789"; // Act var session = await this._agent.CreateSessionAsync(ContextId, TaskId); // Assert Assert.NotNull(session); Assert.IsType(session); var typedSession = (A2AAgentSession)session; Assert.Equal(ContextId, typedSession.ContextId); Assert.Equal(TaskId, typedSession.TaskId); } /// /// Verify that CreateSessionAsync throws when contextId is null, empty, or whitespace. /// [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("\t")] [InlineData("\r\n")] public async Task CreateSessionAsync_WithInvalidContextId_ThrowsArgumentExceptionAsync(string? contextId) { // Act & Assert await Assert.ThrowsAnyAsync(async () => await this._agent.CreateSessionAsync(contextId!)); } /// /// Verify that CreateSessionAsync with both parameters throws when contextId is null, empty, or whitespace. /// [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("\t")] [InlineData("\r\n")] public async Task CreateSessionAsync_WithInvalidContextIdAndValidTaskId_ThrowsArgumentExceptionAsync(string? contextId) { // Arrange const string TaskId = "valid-task-id"; // Act & Assert await Assert.ThrowsAnyAsync(async () => await this._agent.CreateSessionAsync(contextId!, TaskId)); } /// /// Verify that CreateSessionAsync with both parameters throws when taskId is null, empty, or whitespace. /// [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("\t")] [InlineData("\r\n")] public async Task CreateSessionAsync_WithValidContextIdAndInvalidTaskId_ThrowsArgumentExceptionAsync(string? taskId) { // Arrange const string ContextId = "valid-context-id"; // Act & Assert await Assert.ThrowsAnyAsync(async () => await this._agent.CreateSessionAsync(ContextId, taskId!)); } #endregion public void Dispose() { this._a2aClient.Dispose(); this._handler.Dispose(); this._httpClient.Dispose(); } /// /// Custom agent session class for testing invalid session type scenario. /// private sealed class CustomAgentSession : AgentSession; internal sealed class A2AClientHttpMessageHandlerStub : HttpMessageHandler { public JsonRpcRequest? CapturedJsonRpcRequest { get; set; } public List CapturedJsonRpcRequests { get; } = []; public SendMessageRequest? CapturedSendMessageRequest { get; set; } public GetTaskRequest? CapturedGetTaskRequest { get; set; } public SendMessageResponse? ResponseToReturn { get; set; } public AgentTask? AgentTaskToReturn { get; set; } public StreamResponse? StreamingResponseToReturn { get; set; } /// /// When set, streaming requests for SubscribeToTask will return a JSON-RPC error /// with this error code. Used to simulate UnsupportedOperation errors. /// public A2AErrorCode? StreamingErrorCodeToReturn { get; set; } /// /// Error message to include when is set. /// public string StreamingErrorMessage { get; set; } = "Task is in a terminal state and cannot be subscribed to."; /// /// When set, GetTask requests will return a JSON-RPC error with this error code. /// Used to simulate failures in the GetTaskAsync fallback path. /// public A2AErrorCode? GetTaskErrorCodeToReturn { get; set; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Capture the request content #pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods; overload doesn't exist downlevel var content = await request.Content!.ReadAsStringAsync(); #pragma warning restore CA2016 this.CapturedJsonRpcRequest = JsonSerializer.Deserialize(content); if (this.CapturedJsonRpcRequest is not null) { this.CapturedJsonRpcRequests.Add(this.CapturedJsonRpcRequest); } try { this.CapturedSendMessageRequest = this.CapturedJsonRpcRequest?.Params?.Deserialize(A2AJsonUtilities.DefaultOptions); } catch { /* Ignore deserialization errors for non-SendMessageRequest requests */ } try { this.CapturedGetTaskRequest = this.CapturedJsonRpcRequest?.Params?.Deserialize(A2AJsonUtilities.DefaultOptions); } catch { /* Ignore deserialization errors for non-GetTaskRequest requests */ } // Return a JSON-RPC error for GetTask when configured if (this.GetTaskErrorCodeToReturn is not null && this.CapturedJsonRpcRequest?.Method == "GetTask") { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Error = new JsonRpcError { Code = (int)this.GetTaskErrorCodeToReturn.Value, Message = "Simulated GetTask error." } }; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json") }; } // Return the pre-configured AgentTask response (for tasks/get) if (this.AgentTaskToReturn is not null && this.CapturedJsonRpcRequest?.Method == "GetTask") { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Result = JsonSerializer.SerializeToNode(this.AgentTaskToReturn, A2AJsonUtilities.DefaultOptions) }; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json") }; } // Return the pre-configured non-streaming response if (this.ResponseToReturn is not null) { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Result = JsonSerializer.SerializeToNode(this.ResponseToReturn, A2AJsonUtilities.DefaultOptions) }; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json") }; } // Return a streaming JSON-RPC error (e.g., UnsupportedOperation for SubscribeToTask) else if (this.StreamingErrorCodeToReturn is not null && this.CapturedJsonRpcRequest?.Method is "SubscribeToTask") { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Error = new JsonRpcError { Code = (int)this.StreamingErrorCodeToReturn.Value, Message = this.StreamingErrorMessage } }; var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) { await writer.WriteAsync($"data: {JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions)}\n\n"); #pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods; overload doesn't exist downlevel await writer.FlushAsync(); #pragma warning restore CA2016 } stream.Position = 0; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) { Headers = { { "Content-Type", "text/event-stream" } } } }; } // Return the pre-configured streaming response else if (this.StreamingResponseToReturn is not null) { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Result = JsonSerializer.SerializeToNode(this.StreamingResponseToReturn, A2AJsonUtilities.DefaultOptions) }; var stream = new MemoryStream(); using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) { await writer.WriteAsync($"data: {JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions)}\n\n"); #pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods; overload doesn't exist downlevel await writer.FlushAsync(); #pragma warning restore CA2016 } stream.Position = 0; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) { Headers = { { "Content-Type", "text/event-stream" } } } }; } else { var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Result = JsonSerializer.SerializeToNode(new SendMessageResponse { Message = new Message() }, A2AJsonUtilities.DefaultOptions) }; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json") }; } } } }