.NET: Require thread for background responses (#2498)

* require thread for backgraund responses

* address pr review feedback
This commit is contained in:
SergeyMenshykh
2025-11-27 16:26:52 +00:00
committed by GitHub
Unverified
parent 257e717608
commit e769256976
2 changed files with 90 additions and 10 deletions
+22 -10
View File
@@ -75,11 +75,7 @@ internal sealed class A2AAgent : AIAgent
{
_ = 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);
@@ -142,11 +138,7 @@ internal sealed class A2AAgent : AIAgent
{
_ = 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);
@@ -217,6 +209,26 @@ internal sealed class A2AAgent : AIAgent
/// <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)
@@ -799,12 +799,80 @@ public sealed class A2AAgentTests : IDisposable
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; }