Catch agent errors and emit response.failed with real error message

Previously, unhandled exceptions from agent execution would bubble up
to the SDK orchestrator, which emits a generic 'An internal server
error occurred.' message — hiding the actual cause (e.g., 401 auth
failures, model not found, etc.).

Now AgentFrameworkResponseHandler catches non-cancellation exceptions
and emits a proper response.failed event containing the real error
message, making it visible to clients and in logs.

OperationCanceledException still propagates for proper cancellation
handling by the SDK.

Also bumps package version to 0.9.0-hosted.260403.2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
alliscode
2026-04-03 11:17:21 -07:00
Unverified
parent 21dc5311be
commit eb67379033
4 changed files with 35 additions and 9 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260402.1</PackageVersion>
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
<!-- Branch-specific override to distinguish from mainline packages -->
<PackageVersion>0.9.0-hosted.260403.1</PackageVersion>
<PackageVersion>0.9.0-hosted.260403.2</PackageVersion>
<GitTag>1.0.0</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<NoWarn>$(NoWarn);NU1903;NU1605</NoWarn>
<NoWarn>$(NoWarn);NU1903;NU1605;MAAIW001</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -95,6 +95,7 @@ public class AgentFrameworkResponseHandler : ResponseHandler
while (true)
{
bool shutdownDetected = false;
ResponseStreamEvent? failedEvent = null;
ResponseStreamEvent? evt = null;
try
{
@@ -109,6 +110,26 @@ public class AgentFrameworkResponseHandler : ResponseHandler
{
shutdownDetected = true;
}
catch (Exception ex) when (ex is not OperationCanceledException && !emittedTerminal)
{
// Catch agent execution errors and emit a proper failed event
// with the real error message instead of letting the SDK emit
// a generic "An internal server error occurred."
if (this._logger.IsEnabled(LogLevel.Error))
{
this._logger.LogError(ex, "Agent execution failed for response {ResponseId}.", context.ResponseId);
}
failedEvent = stream.EmitFailed(
ResponseErrorCode.ServerError,
ex.Message);
}
if (failedEvent is not null)
{
yield return failedEvent;
yield break;
}
if (shutdownDetected)
{
@@ -538,7 +538,7 @@ public class AgentFrameworkResponseHandlerTests
}
[Fact]
public async Task CreateAsync_AgentThrows_ExceptionPropagates()
public async Task CreateAsync_AgentThrows_EmitsFailedEventWithErrorMessage()
{
// Arrange
var agent = new ThrowingAgent(new InvalidOperationException("Agent crashed"));
@@ -561,13 +561,18 @@ public class AgentFrameworkResponseHandlerTests
mockContext.Setup(x => x.GetInputItemsAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(Array.Empty<OutputItem>());
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
// Act — collect all events
var events = new List<ResponseStreamEvent>();
await foreach (var evt in handler.CreateAsync(request, mockContext.Object, CancellationToken.None))
{
await foreach (var _ in handler.CreateAsync(request, mockContext.Object, CancellationToken.None))
{
}
});
events.Add(evt);
}
// Assert — should contain created, in_progress, and failed (with real error message)
Assert.Contains(events, e => e is ResponseCreatedEvent);
Assert.Contains(events, e => e is ResponseInProgressEvent);
var failedEvent = Assert.Single(events.OfType<ResponseFailedEvent>());
Assert.Contains("Agent crashed", failedEvent.Response.Error.Message);
}
[Fact]