Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 22 additions & 10 deletions dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,7 @@ public override async Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> m
{
_ = Throw.IfNull(messages);

thread ??= this.GetNewThread();
if (thread is not A2AAgentThread typedThread)
{
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
}
A2AAgentThread typedThread = this.GetA2AThread(thread, options);

this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name);

Expand Down Expand Up @@ -142,11 +138,7 @@ public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync
{
_ = Throw.IfNull(messages);

thread ??= this.GetNewThread();
if (thread is not A2AAgentThread typedThread)
{
throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
}
A2AAgentThread typedThread = this.GetA2AThread(thread, options);

this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name);

Expand Down Expand Up @@ -217,6 +209,26 @@ public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync
/// <inheritdoc/>
public override string? Description => this._description ?? base.Description;

private A2AAgentThread GetA2AThread(AgentThread? thread, AgentRunOptions? options)
{
// Aligning with other agent implementations that support background responses, where
// a thread is required for background responses to prevent inconsistent experience
// for callers if they forget to provide the thread for initial or follow-up runs.
if (options?.AllowBackgroundResponses is true && thread is null)
{
throw new InvalidOperationException("A thread must be provided when AllowBackgroundResponses is enabled.");
}

thread ??= this.GetNewThread();

if (thread is not A2AAgentThread typedThread)
{
throw new InvalidOperationException($"The provided thread type {thread.GetType()} is not compatible with the agent. Only A2A agent created threads are supported.");
}

return typedThread;
}

private static void UpdateThread(A2AAgentThread? thread, string? contextId, string? taskId = null)
{
if (thread is null)
Expand Down
68 changes: 68 additions & 0 deletions dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -799,12 +799,80 @@ public async Task RunStreamingAsync_WithTaskArtifactUpdateEvent_YieldsResponseUp
Assert.Equal(TaskId, a2aThread.TaskId);
}

[Fact]
public async Task RunAsync_WithAllowBackgroundResponsesAndNoThread_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
var inputMessages = new List<ChatMessage>
{
new(ChatRole.User, "Test message")
};

var options = new AgentRunOptions { AllowBackgroundResponses = true };

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => this._agent.RunAsync(inputMessages, null, options));
}

[Fact]
public async Task RunStreamingAsync_WithAllowBackgroundResponsesAndNoThread_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
var inputMessages = new List<ChatMessage>
{
new(ChatRole.User, "Test message")
};

var options = new AgentRunOptions { AllowBackgroundResponses = true };

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options))
{
// Just iterate through to trigger the exception
}
});
}

[Fact]
public async Task RunAsync_WithInvalidThreadType_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
// Create a thread from a different agent type
var invalidThread = new CustomAgentThread();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => this._agent.RunAsync(invalidThread));
}

[Fact]
public async Task RunStreamingAsync_WithInvalidThreadType_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
var inputMessages = new List<ChatMessage>
{
new(ChatRole.User, "Test message")
};

// Create a thread from a different agent type
var invalidThread = new CustomAgentThread();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(async () => await this._agent.RunStreamingAsync(inputMessages, invalidThread).ToListAsync());
}

public void Dispose()
{
this._handler.Dispose();
this._httpClient.Dispose();
}

/// <summary>
/// Custom agent thread class for testing invalid thread type scenario.
/// </summary>
private sealed class CustomAgentThread : AgentThread;

internal sealed class A2AClientHttpMessageHandlerStub : HttpMessageHandler
{
public JsonRpcRequest? CapturedJsonRpcRequest { get; set; }
Expand Down
Loading