diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostingExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostingExtensions.cs index 281d0694ac..e91531513d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostingExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostingExtensions.cs @@ -41,7 +41,7 @@ public static class WorkflowHostingExtensions { Dictionary parameters = new() { - { "data", request.Data} + { "data", request.Data } }; return new FunctionCallContent(request.RequestId, request.PortInfo.PortId, parameters); diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs index c1f81f0ecf..d015ecdcee 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs @@ -247,7 +247,7 @@ internal sealed class WorkflowSession : AgentSession hasMatchedResponseForStartExecutor |= string.Equals(responseExecutorId, this._workflow.StartExecutorId, StringComparison.Ordinal); } - AIContent normalizedResponseContent = NormalizeResponseContentForDelivery(content, pendingRequest); + object normalizedResponseContent = NormalizeResponseContentForDelivery(content, pendingRequest); externalResponses.Add((pendingRequest.CreateResponse(normalizedResponseContent), pendingRequest.RequestId)); (matchedContentIds ??= new(StringComparer.Ordinal)).Add(contentId); } @@ -303,14 +303,35 @@ internal sealed class WorkflowSession : AgentSession /// /// Rewrites workflow-facing response content back to the original agent-owned content ID. /// - private static AIContent NormalizeResponseContentForDelivery(AIContent content, ExternalRequest request) => content switch + private static object NormalizeResponseContentForDelivery(AIContent content, ExternalRequest request) { - FunctionResultContent functionResultContent when request.TryGetDataAs(out FunctionCallContent? functionCallContent) - => CloneFunctionResultContent(functionResultContent, functionCallContent.CallId), - ToolApprovalResponseContent toolApprovalResponseContent when request.TryGetDataAs(out ToolApprovalRequestContent? toolApprovalRequestContent) - => CloneToolApprovalResponseContent(toolApprovalResponseContent, toolApprovalRequestContent.RequestId), - _ => content, - }; + switch (content) + { + // If we got a FRC, and were expecting a FRC (because the request started out as a FCC, rather than getting converted to + // on at the WorkflowSession boundary), clone it and send it in. + case FunctionResultContent functionResultContent when request.TryGetDataAs(out FunctionCallContent? functionCallContent): + return CloneFunctionResultContent(functionResultContent, functionCallContent.CallId); + case FunctionResultContent functionResultContent when !request.PortInfo.ResponseType.IsMatchPolymorphic(typeof(FunctionResultContent)): + { + object? result = functionResultContent.Result; + if (result != null) + { + if (request.PortInfo.ResponseType.IsMatchPolymorphic(result.GetType()) || result is PortableValue) + { + return result; + } + + throw new InvalidOperationException($"Unexpected result type in FunctionResultContent {result.GetType()}; expecting {request.PortInfo.ResponseType}"); + } + + throw new NotSupportedException($"Null result is not supported when using RequestPort with non-AIContent-typed requests. {functionResultContent}"); + } + case ToolApprovalResponseContent toolApprovalResponseContent when request.TryGetDataAs(out ToolApprovalRequestContent? toolApprovalRequestContent): + return CloneToolApprovalResponseContent(toolApprovalResponseContent, toolApprovalRequestContent.RequestId); + default: + return content; + } + } /// /// Gets the workflow-facing request ID from response content types.