From 56c3f8d82582f8d848dbc96e487a036075ba5696 Mon Sep 17 00:00:00 2001
From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Date: Fri, 24 Apr 2026 22:56:30 +0100
Subject: [PATCH 1/3] .NET: Bump OpenTelemetry packages to 1.15.3 (#5478)
* Bump OpenTelemetry packages to 1.15.3 to fix known vulnerabilities
Update OpenTelemetry packages from 1.15.0 to 1.15.3 in Directory.Packages.props
to resolve NU1902 warnings-as-errors for CVEs GHSA-g94r-2vxg-569j,
GHSA-mr8r-92fq-pj8p, and GHSA-q834-8qmm-v933.
Add explicit PackageReference for OpenTelemetry.Exporter.OpenTelemetryProtocol
in Foundry.Hosting and OpenTelemetry.Api + OpenTelemetry.Exporter.OpenTelemetryProtocol
in Hosted-Invocations-EchoAgent to override transitive 1.15.0 resolution in
projects with CentralPackageTransitivePinningEnabled=false.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump OpenTelemetry Extensions and Instrumentation packages to 1.15.x
Align the full OpenTelemetry package set to the 1.15.x family:
- OpenTelemetry.Extensions.Hosting: 1.14.0 -> 1.15.3
- OpenTelemetry.Instrumentation.AspNetCore: 1.14.0 -> 1.15.2
- OpenTelemetry.Instrumentation.Http: 1.14.0 -> 1.15.1
- OpenTelemetry.Instrumentation.Runtime: 1.14.0 -> 1.15.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
dotnet/Directory.Packages.props | 18 +++++++++---------
.../Hosted-Invocations-EchoAgent.csproj | 2 ++
.../Microsoft.Agents.AI.Foundry.Hosting.csproj | 1 +
3 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index fff7b6a7d4..c57af55ef9 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -56,15 +56,15 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj
index d925172007..a0b9e2e0d8 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/Hosted-Invocations-EchoAgent.csproj
@@ -13,6 +13,8 @@
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
index 99ecde93ca..af02c44aad 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
@@ -34,6 +34,7 @@
+
From 56fb634f0e921f1e0227aa843ce2551d26eaabd6 Mon Sep 17 00:00:00 2001
From: Shyju Krishnankutty
Date: Fri, 24 Apr 2026 17:55:28 -0700
Subject: [PATCH 2/3] .NET: Support returning durable workflow results from
HTTP trigger endpoint (#5321)
* Adding support for "wait for response" when invoking workflow http endpoint.
* update changelog.
* PR comment fixes.
* Address PR review feedback.
- Return 404 Not Found when no orchestration with the given ID exists
- Return 200 OK for failed workflows (the HTTP operation succeeded;
the workflow outcome is conveyed via the response body)
- Rename 'status' to 'workflowStatus' in WorkflowRunResponse to avoid
inconsistency with AgentRunSuccessResponse which uses integer status
- Add optional 'error' field (omitted from JSON when null) to
WorkflowRunResponse for failed workflow details
---
.../01_SequentialWorkflow/README.md | 47 +++++
.../01_SequentialWorkflow/demo.http | 22 +++
.../BuiltInFunctions.cs | 165 +++++++++++++++---
.../CHANGELOG.md | 1 +
.../WorkflowSamplesValidation.cs | 40 +++++
5 files changed, 254 insertions(+), 21 deletions(-)
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/README.md b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/README.md
index 384fd358a7..4f455b3dec 100644
--- a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/README.md
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/README.md
@@ -65,6 +65,53 @@ Workflow orchestration started for CancelOrder. Orchestration runId: abc123def45
>
> If not provided, a unique run ID is auto-generated.
+### Wait for the Workflow Result
+
+By default, the HTTP endpoint returns `202 Accepted` immediately with the run ID. If you want to wait for the workflow to complete and get the result in the response, add the `x-ms-wait-for-response: true` header:
+
+Bash (Linux/macOS/WSL):
+
+```bash
+curl -X POST http://localhost:7071/api/workflows/CancelOrder/run \
+ -H "Content-Type: text/plain" \
+ -H "x-ms-wait-for-response: true" \
+ -d "12345"
+```
+
+PowerShell:
+
+```powershell
+Invoke-RestMethod -Method Post `
+ -Uri http://localhost:7071/api/workflows/CancelOrder/run `
+ -ContentType text/plain `
+ -Headers @{ "x-ms-wait-for-response" = "true" } `
+ -Body "12345"
+```
+
+The response will contain the workflow result as plain text (200 OK):
+
+```text
+Cancellation email sent for order 12345 to jerry@example.com.
+```
+
+To get the result as JSON, also include the `Accept: application/json` header:
+
+```bash
+curl -X POST http://localhost:7071/api/workflows/CancelOrder/run \
+ -H "Content-Type: text/plain" \
+ -H "x-ms-wait-for-response: true" \
+ -H "Accept: application/json" \
+ -d "12345"
+```
+
+```json
+{
+ "runId": "abc123def456",
+ "workflowStatus": "Completed",
+ "result": "Cancellation email sent for order 12345 to jerry@example.com."
+}
+```
+
In the function app logs, you will see the sequential execution of each executor:
```text
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/demo.http b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/demo.http
index 8366216a6c..fb9793f449 100644
--- a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/demo.http
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/demo.http
@@ -7,6 +7,21 @@ Content-Type: text/plain
12345
+### Cancel an order and wait for the result
+POST {{authority}}/api/workflows/CancelOrder/run
+Content-Type: text/plain
+x-ms-wait-for-response: true
+
+12345
+
+### Cancel an order and wait for the result (JSON response)
+POST {{authority}}/api/workflows/CancelOrder/run
+Content-Type: text/plain
+Accept: application/json
+x-ms-wait-for-response: true
+
+12345
+
### Cancel an order with a custom run ID
POST {{authority}}/api/workflows/CancelOrder/run?runId=my-custom-id-123
Content-Type: text/plain
@@ -19,6 +34,13 @@ Content-Type: text/plain
12345
+### Get order status and wait for the result
+POST {{authority}}/api/workflows/OrderStatus/run
+Content-Type: text/plain
+x-ms-wait-for-response: true
+
+12345
+
### Batch cancel orders with a complex JSON input
POST {{authority}}/api/workflows/BatchCancelOrders/run
Content-Type: application/json
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
index e6c94347a1..376f2fa2ca 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
@@ -21,6 +21,8 @@ internal static class BuiltInFunctions
internal const string HttpPrefix = "http-";
internal const string McpToolPrefix = "mcptool-";
+ private const string WaitForResponseHeaderName = "x-ms-wait-for-response";
+
internal static readonly string RunAgentHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunAgentHttpAsync)}";
internal static readonly string RunAgentEntityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeAgentAsync)}";
internal static readonly string RunAgentMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunMcpToolAsync)}";
@@ -62,6 +64,11 @@ internal static class BuiltInFunctions
StartOrchestrationOptions? options = instanceId is not null ? new StartOrchestrationOptions(instanceId) : null;
string resolvedInstanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, orchestrationInput, options);
+ if (ShouldWaitForResponse(req, defaultValue: false))
+ {
+ return await WaitForWorkflowCompletionAsync(req, client, context, resolvedInstanceId);
+ }
+
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteStringAsync($"Workflow orchestration started for {workflowName}. Orchestration runId: {resolvedInstanceId}");
return response;
@@ -304,15 +311,7 @@ internal static class BuiltInFunctions
}
// Check if we should wait for response (default is true)
- bool waitForResponse = true;
- if (req.Headers.TryGetValues("x-ms-wait-for-response", out IEnumerable? waitForResponseValues))
- {
- string? waitForResponseValue = waitForResponseValues.FirstOrDefault();
- if (!string.IsNullOrEmpty(waitForResponseValue) && bool.TryParse(waitForResponseValue, out bool parsedValue))
- {
- waitForResponse = parsedValue;
- }
- }
+ bool waitForResponse = ShouldWaitForResponse(req, defaultValue: true);
AIAgent agentProxy = client.AsDurableAgentProxy(context, agentName);
@@ -428,6 +427,95 @@ internal static class BuiltInFunctions
return metadata.ReadOutputAs()?.Result;
}
+ ///
+ /// Waits for a workflow orchestration to complete and returns an appropriate HTTP response.
+ ///
+ private static async Task WaitForWorkflowCompletionAsync(
+ HttpRequestData req,
+ DurableTaskClient client,
+ FunctionContext context,
+ string instanceId)
+ {
+ bool acceptsJson = AcceptsJson(req);
+
+ OrchestrationMetadata? metadata = await client.WaitForInstanceCompletionAsync(
+ instanceId,
+ getInputsAndOutputs: true,
+ cancellation: context.CancellationToken);
+
+ if (metadata is null)
+ {
+ return await CreateErrorResponseAsync(req, context, HttpStatusCode.NotFound,
+ $"No workflow orchestration with ID '{instanceId}' was found.", acceptsJson);
+ }
+
+ if (metadata.RuntimeStatus is OrchestrationRuntimeStatus.Failed)
+ {
+ string errorMessage = metadata.FailureDetails?.ErrorMessage ?? "Unknown error";
+ HttpResponseData failedResponse = req.CreateResponse(HttpStatusCode.OK);
+
+ if (acceptsJson)
+ {
+ await failedResponse.WriteAsJsonAsync(
+ new WorkflowRunResponse(instanceId, metadata.RuntimeStatus.ToString(), Result: null, Error: errorMessage),
+ context.CancellationToken);
+ }
+ else
+ {
+ failedResponse.Headers.Add("Content-Type", "text/plain");
+ await failedResponse.WriteStringAsync(errorMessage, context.CancellationToken);
+ }
+
+ return failedResponse;
+ }
+
+ if (metadata.RuntimeStatus is not OrchestrationRuntimeStatus.Completed)
+ {
+ return await CreateErrorResponseAsync(req, context, HttpStatusCode.InternalServerError,
+ $"Workflow orchestration '{instanceId}' ended with unexpected status '{metadata.RuntimeStatus}'.", acceptsJson);
+ }
+
+ string? result = metadata.ReadOutputAs()?.Result;
+
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+
+ if (acceptsJson)
+ {
+ JsonElement? resultElement = null;
+ if (!string.IsNullOrEmpty(result))
+ {
+ try
+ {
+ using JsonDocument doc = JsonDocument.Parse(result);
+ resultElement = doc.RootElement.Clone();
+ }
+ catch (JsonException)
+ {
+ // Result is a plain string (not valid JSON) — serialize it as a JSON string element.
+ var buffer = new System.Buffers.ArrayBufferWriter();
+ using (var writer = new Utf8JsonWriter(buffer))
+ {
+ writer.WriteStringValue(result);
+ }
+
+ using JsonDocument fallbackDoc = JsonDocument.Parse(buffer.WrittenMemory);
+ resultElement = fallbackDoc.RootElement.Clone();
+ }
+ }
+
+ await response.WriteAsJsonAsync(
+ new WorkflowRunResponse(instanceId, metadata.RuntimeStatus.ToString(), resultElement),
+ context.CancellationToken);
+ }
+ else
+ {
+ response.Headers.Add("Content-Type", "text/plain");
+ await response.WriteStringAsync(result ?? string.Empty, context.CancellationToken);
+ }
+
+ return response;
+ }
+
///
/// Creates an error response with the specified status code and error message.
///
@@ -435,18 +523,18 @@ internal static class BuiltInFunctions
/// The function context.
/// The HTTP status code.
/// The error message.
+ /// Optional pre-computed value indicating whether the client accepts JSON. When , the value is determined from the request's Accept header.
/// The HTTP response data containing the error.
private static async Task CreateErrorResponseAsync(
HttpRequestData req,
FunctionContext context,
HttpStatusCode statusCode,
- string errorMessage)
+ string errorMessage,
+ bool? acceptsJson = null)
{
HttpResponseData response = req.CreateResponse(statusCode);
- bool acceptsJson = req.Headers.TryGetValues("Accept", out IEnumerable? acceptValues) &&
- acceptValues.Contains("application/json", StringComparer.OrdinalIgnoreCase);
- if (acceptsJson)
+ if (acceptsJson ?? AcceptsJson(req))
{
ErrorResponse errorResponse = new((int)statusCode, errorMessage);
await response.WriteAsJsonAsync(errorResponse, context.CancellationToken);
@@ -479,10 +567,7 @@ internal static class BuiltInFunctions
HttpResponseData response = req.CreateResponse(statusCode);
response.Headers.Add("x-ms-thread-id", sessionId);
- bool acceptsJson = req.Headers.TryGetValues("Accept", out IEnumerable? acceptValues) &&
- acceptValues.Contains("application/json", StringComparer.OrdinalIgnoreCase);
-
- if (acceptsJson)
+ if (AcceptsJson(req))
{
AgentRunSuccessResponse successResponse = new((int)statusCode, sessionId, agentResponse);
await response.WriteAsJsonAsync(successResponse, context.CancellationToken);
@@ -511,10 +596,7 @@ internal static class BuiltInFunctions
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
response.Headers.Add("x-ms-thread-id", sessionId);
- bool acceptsJson = req.Headers.TryGetValues("Accept", out IEnumerable? acceptValues) &&
- acceptValues.Contains("application/json", StringComparer.OrdinalIgnoreCase);
-
- if (acceptsJson)
+ if (AcceptsJson(req))
{
AgentRunAcceptedResponse acceptedResponse = new((int)HttpStatusCode.Accepted, sessionId);
await response.WriteAsJsonAsync(acceptedResponse, context.CancellationToken);
@@ -528,6 +610,34 @@ internal static class BuiltInFunctions
return response;
}
+ ///
+ /// Returns when the caller has requested waiting for the workflow/agent to complete,
+ /// as indicated by the x-ms-wait-for-response header. Falls back to
+ /// when the header is absent or not a valid boolean.
+ ///
+ private static bool ShouldWaitForResponse(HttpRequestData req, bool defaultValue)
+ {
+ if (req.Headers.TryGetValues(WaitForResponseHeaderName, out IEnumerable? values) &&
+ bool.TryParse(values.FirstOrDefault(), out bool parsed))
+ {
+ return parsed;
+ }
+
+ return defaultValue;
+ }
+
+ ///
+ /// Returns when the request accepts the application/json media type.
+ ///
+ private static bool AcceptsJson(HttpRequestData req)
+ {
+ return req.Headers.TryGetValues("Accept", out IEnumerable? acceptValues) &&
+ acceptValues
+ .SelectMany(v => v.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
+ .Select(v => v.Split(';', 2)[0].Trim())
+ .Contains("application/json", StringComparer.OrdinalIgnoreCase);
+ }
+
private static string GetAgentName(FunctionContext context)
{
// Check if the function name starts with the HttpPrefix
@@ -591,6 +701,19 @@ internal static class BuiltInFunctions
[property: JsonPropertyName("eventName")] string? EventName,
[property: JsonPropertyName("response")] JsonElement Response);
+ ///
+ /// Represents a workflow run response when waiting for completion.
+ ///
+ /// The orchestration run ID.
+ /// The orchestration runtime status (e.g., "Completed", "Failed").
+ /// The workflow result as a JSON element so POCOs serialize as nested objects rather than escaped strings.
+ /// An optional error message when the workflow has failed.
+ private sealed record WorkflowRunResponse(
+ [property: JsonPropertyName("runId")] string RunId,
+ [property: JsonPropertyName("workflowStatus")] string WorkflowStatus,
+ [property: JsonPropertyName("result")] JsonElement? Result,
+ [property: JsonPropertyName("error"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Error = null);
+
///
/// A service provider that combines the original service provider with an additional DurableTaskClient instance.
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md
index 2c188757d5..f8f59c89d1 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md
@@ -2,6 +2,7 @@
## [Unreleased]
+- Support returning workflow results from HTTP trigger endpoint ([#5321](https://github.com/microsoft/agent-framework/pull/5321))
- Added MCP tool trigger support for durable workflows ([#4768](https://github.com/microsoft/agent-framework/pull/4768))
- Added Azure Functions hosting support for durable workflows ([#4436](https://github.com/microsoft/agent-framework/pull/4436))
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
index a7f2f51156..2eba009c67 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Text;
+using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;
@@ -125,6 +126,45 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
},
message: "OrderStatus workflow completed",
timeout: s_orchestrationTimeout);
+
+ // Test the CancelOrder workflow with x-ms-wait-for-response header
+ this._outputHelper.WriteLine("Starting CancelOrder workflow with x-ms-wait-for-response: true...");
+
+ using HttpRequestMessage waitRequest = new(HttpMethod.Post, cancelOrderUri);
+ waitRequest.Content = new StringContent("55555", Encoding.UTF8, "text/plain");
+ waitRequest.Headers.Add("x-ms-wait-for-response", "true");
+ using HttpResponseMessage waitResponse = await s_sharedHttpClient.SendAsync(waitRequest);
+
+ Assert.True(waitResponse.IsSuccessStatusCode, $"CancelOrder wait-for-response request failed with status: {waitResponse.StatusCode}");
+ string waitResponseText = await waitResponse.Content.ReadAsStringAsync();
+ this._outputHelper.WriteLine($"CancelOrder wait-for-response result: {waitResponseText}");
+
+ // The response should contain the workflow result (not just "started for CancelOrder")
+ Assert.DoesNotContain("Workflow orchestration started", waitResponseText);
+ Assert.Contains("55555", waitResponseText);
+
+ // Test the wait-for-response with Accept: application/json header
+ this._outputHelper.WriteLine("Starting CancelOrder workflow with x-ms-wait-for-response and Accept: application/json...");
+
+ using HttpRequestMessage jsonWaitRequest = new(HttpMethod.Post, cancelOrderUri);
+ jsonWaitRequest.Content = new StringContent("77777", Encoding.UTF8, "text/plain");
+ jsonWaitRequest.Headers.Add("x-ms-wait-for-response", "true");
+ jsonWaitRequest.Headers.Add("Accept", "application/json");
+
+ using CancellationTokenSource jsonWaitCts = new(s_orchestrationTimeout);
+ using HttpResponseMessage jsonWaitResponse = await s_sharedHttpClient.SendAsync(jsonWaitRequest, jsonWaitCts.Token);
+
+ Assert.True(jsonWaitResponse.IsSuccessStatusCode, $"CancelOrder JSON wait-for-response request failed with status: {jsonWaitResponse.StatusCode}");
+ string jsonWaitResponseText = await jsonWaitResponse.Content.ReadAsStringAsync();
+ this._outputHelper.WriteLine($"CancelOrder JSON wait-for-response result: {jsonWaitResponseText}");
+
+ using JsonDocument jsonDoc = JsonDocument.Parse(jsonWaitResponseText);
+ JsonElement root = jsonDoc.RootElement;
+ Assert.True(root.TryGetProperty("runId", out _), "JSON response missing 'runId' property");
+ Assert.True(root.TryGetProperty("workflowStatus", out JsonElement statusEl), "JSON response missing 'workflowStatus' property");
+ Assert.Equal("Completed", statusEl.GetString());
+ Assert.True(root.TryGetProperty("result", out JsonElement resultEl), "JSON response missing 'result' property");
+ Assert.Contains("77777", resultEl.GetString());
});
}
From dad3652f46d38539b4c588b9732e9eb1b3c1e6f8 Mon Sep 17 00:00:00 2001
From: bahtyar <34988899+Bahtya@users.noreply.github.com>
Date: Mon, 27 Apr 2026 12:53:06 +0800
Subject: [PATCH 3/3] Python: fix: prevent inner_exception from being lost in
AgentFrameworkException (#5167)
* fix: prevent inner_exception from being lost in AgentFrameworkException
The __init__ method unconditionally called super().__init__() after
the conditional call with inner_exception, effectively overwriting the
exception args and losing the inner_exception reference.
Add else branch so super().__init__() is only called once with the
correct arguments.
Fixes #5155
Signed-off-by: bahtya
* test: add explicit tests for AgentFrameworkException inner_exception handling
- test_exception_with_inner_exception: verifies args include inner exception
- test_exception_without_inner_exception: verifies args only contain message
- test_exception_inner_exception_none_explicit: verifies explicit None
Covers both branches of the if/else in __init__.
* fix: export AgentFrameworkException from package
Bahtya
---------
Signed-off-by: bahtya
---
.../packages/core/agent_framework/__init__.py | 2 ++
.../core/agent_framework/exceptions.py | 3 +-
.../core/tests/core/test_exceptions.py | 29 +++++++++++++++++++
3 files changed, 33 insertions(+), 1 deletion(-)
create mode 100644 python/packages/core/tests/core/test_exceptions.py
diff --git a/python/packages/core/agent_framework/__init__.py b/python/packages/core/agent_framework/__init__.py
index 364f62eae1..a098159a50 100644
--- a/python/packages/core/agent_framework/__init__.py
+++ b/python/packages/core/agent_framework/__init__.py
@@ -247,6 +247,7 @@ from ._workflows._workflow_executor import (
WorkflowExecutor,
)
from .exceptions import (
+ AgentFrameworkException,
MiddlewareException,
UserInputRequiredException,
WorkflowCheckpointException,
@@ -276,6 +277,7 @@ __all__ = [
"USER_AGENT_TELEMETRY_DISABLED_ENV_VAR",
"Agent",
"AgentContext",
+ "AgentFrameworkException",
"AgentEvalConverter",
"AgentExecutor",
"AgentExecutorRequest",
diff --git a/python/packages/core/agent_framework/exceptions.py b/python/packages/core/agent_framework/exceptions.py
index 4f56c34b5c..d5a54c2102 100644
--- a/python/packages/core/agent_framework/exceptions.py
+++ b/python/packages/core/agent_framework/exceptions.py
@@ -34,7 +34,8 @@ class AgentFrameworkException(Exception):
logger.log(log_level, message, exc_info=inner_exception)
if inner_exception:
super().__init__(message, inner_exception, *args) # type: ignore
- super().__init__(message, *args) # type: ignore
+ else:
+ super().__init__(message, *args) # type: ignore
# region Agent Exceptions
diff --git a/python/packages/core/tests/core/test_exceptions.py b/python/packages/core/tests/core/test_exceptions.py
new file mode 100644
index 0000000000..47b3a53197
--- /dev/null
+++ b/python/packages/core/tests/core/test_exceptions.py
@@ -0,0 +1,29 @@
+# Copyright (c) Microsoft. All rights reserved.
+
+"""Tests for AgentFrameworkException inner_exception handling."""
+
+import pytest
+
+from agent_framework import AgentFrameworkException
+
+
+def test_exception_with_inner_exception():
+ """When inner_exception is provided, it should be set as the second arg."""
+ inner = ValueError("inner error")
+ exc = AgentFrameworkException("test message", inner_exception=inner)
+ assert exc.args[0] == "test message"
+ assert exc.args[1] is inner
+
+
+def test_exception_without_inner_exception():
+ """When inner_exception is None, args should only contain the message."""
+ exc = AgentFrameworkException("test message")
+ assert exc.args == ("test message",)
+ assert len(exc.args) == 1
+
+
+def test_exception_inner_exception_none_explicit():
+ """When inner_exception is explicitly None, args should only contain the message."""
+ exc = AgentFrameworkException("test message", inner_exception=None)
+ assert exc.args == ("test message",)
+ assert len(exc.args) == 1