diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs index fcdefcfd22..db3d299ee9 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs @@ -279,8 +279,8 @@ internal sealed class WorkflowSession : AgentSession { ExternalRequest externalRequest when externalRequest.TryGetDataAs(out FunctionCallContent? functionCallContent) => CloneFunctionCallContent(functionCallContent, externalRequest.RequestId), - ExternalRequest externalRequest when externalRequest.TryGetDataAs(out UserInputRequestContent? userInputRequestContent) - => CloneUserInputRequestContent(userInputRequestContent, externalRequest.RequestId), + ExternalRequest externalRequest when externalRequest.TryGetDataAs(out ToolApprovalRequestContent? toolApprovalRequestContent) + => CloneToolApprovalRequestContent(toolApprovalRequestContent, externalRequest.RequestId), ExternalRequest externalRequest => externalRequest.ToFunctionCall(), }; @@ -292,8 +292,8 @@ internal sealed class WorkflowSession : AgentSession { FunctionResultContent functionResultContent when request.TryGetDataAs(out FunctionCallContent? functionCallContent) => CloneFunctionResultContent(functionResultContent, functionCallContent.CallId), - UserInputResponseContent userInputResponseContent when request.TryGetDataAs(out UserInputRequestContent? userInputRequestContent) - => CloneUserInputResponseContent(userInputResponseContent, userInputRequestContent.Id), + ToolApprovalResponseContent toolApprovalResponseContent when request.TryGetDataAs(out ToolApprovalRequestContent? toolApprovalRequestContent) + => CloneToolApprovalResponseContent(toolApprovalResponseContent, toolApprovalRequestContent.RequestId), _ => content, }; @@ -303,7 +303,7 @@ internal sealed class WorkflowSession : AgentSession private static string? GetResponseContentId(AIContent content) => content switch { FunctionResultContent functionResultContent => functionResultContent.CallId, - UserInputResponseContent userInputResponseContent => userInputResponseContent.Id, + ToolApprovalResponseContent toolApprovalResponseContent => toolApprovalResponseContent.RequestId, _ => null }; @@ -509,39 +509,22 @@ internal sealed class WorkflowSession : AgentSession } /// - /// Clones a with a workflow-facing request ID. + /// Clones a with a workflow-facing request ID. /// - private static UserInputRequestContent CloneUserInputRequestContent(UserInputRequestContent content, string id) + private static ToolApprovalRequestContent CloneToolApprovalRequestContent(ToolApprovalRequestContent content, string id) { - UserInputRequestContent clone = content switch - { - FunctionApprovalRequestContent functionApprovalRequestContent => - new FunctionApprovalRequestContent(id, functionApprovalRequestContent.FunctionCall), - McpServerToolApprovalRequestContent mcpApprovalRequestContent => - new McpServerToolApprovalRequestContent(id, mcpApprovalRequestContent.ToolCall), - _ => throw new NotSupportedException( - $"Unsupported user input request content type '{content.GetType().Name}' for workflow request ID rewriting."), - }; - + ToolApprovalRequestContent clone = new(id, content.ToolCall); return CopyContentMetadata(content, clone); } /// - /// Clones a with an agent-owned request ID. + /// Clones a with an agent-owned request ID. /// - private static UserInputResponseContent CloneUserInputResponseContent(UserInputResponseContent content, string id) + private static ToolApprovalResponseContent CloneToolApprovalResponseContent(ToolApprovalResponseContent content, string id) { - UserInputResponseContent clone = content switch + ToolApprovalResponseContent clone = new(id, content.Approved, content.ToolCall) { - FunctionApprovalResponseContent functionApprovalResponseContent => - new FunctionApprovalResponseContent(id, functionApprovalResponseContent.Approved, functionApprovalResponseContent.FunctionCall) - { - Reason = functionApprovalResponseContent.Reason, - }, - McpServerToolApprovalResponseContent mcpApprovalResponseContent => - new McpServerToolApprovalResponseContent(id, mcpApprovalResponseContent.Approved), - _ => throw new NotSupportedException( - $"Unsupported user input response content type '{content.GetType().Name}' for workflow response ID rewriting."), + Reason = content.Reason, }; return CopyContentMetadata(content, clone); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs index 0d543acec3..1920a57cb1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs @@ -29,7 +29,7 @@ public sealed class ExpectedException : Exception } /// -/// A simple agent that emits a FunctionCallContent or UserInputRequestContent request. +/// A simple agent that emits a FunctionCallContent or ToolApprovalRequestContent request. /// Used to test that RequestInfoEvent handling preserves the original content type. /// internal sealed class RequestEmittingAgent : AIAgent @@ -44,7 +44,7 @@ internal sealed class RequestEmittingAgent : AIAgent /// /// When , the agent emits a text completion instead of re-emitting /// the request when the incoming messages contain a - /// or . This models realistic agent behaviour + /// or . This models realistic agent behaviour /// where the agent processes the tool result and produces a final answer. /// public RequestEmittingAgent(AIContent requestContent, bool completeOnResponse = false) @@ -73,7 +73,7 @@ internal sealed class RequestEmittingAgent : AIAgent protected override async IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { if (this._completeOnResponse && messages.Any(m => m.Contents.Any(c => - c is FunctionResultContent || c is UserInputResponseContent))) + c is FunctionResultContent || c is ToolApprovalResponseContent))) { yield return new AgentResponseUpdate(ChatRole.Assistant, [new TextContent("Request processed")]); } @@ -328,16 +328,16 @@ public class WorkflowHostSmokeTests } /// - /// Tests that when a workflow emits a RequestInfoEvent with UserInputRequestContent data, - /// the AgentResponseUpdate preserves the original UserInputRequestContent type. + /// Tests that when a workflow emits a RequestInfoEvent with ToolApprovalRequestContent data, + /// the AgentResponseUpdate preserves the original ToolApprovalRequestContent type. /// [Fact] - public async Task Test_AsAgent_UserInputRequestContentPreservedInRequestInfoAsync() + public async Task Test_AsAgent_ToolApprovalRequestContentPreservedInRequestInfoAsync() { // Arrange const string RequestId = "test-request-id"; McpServerToolCallContent mcpCall = new("call-id", "testToolName", "http://localhost"); - UserInputRequestContent originalContent = new McpServerToolApprovalRequestContent(RequestId, mcpCall); + ToolApprovalRequestContent originalContent = new(RequestId, mcpCall); RequestEmittingAgent requestAgent = new(originalContent); ExecutorBinding agentBinding = requestAgent.BindAsExecutor( new AIAgentHostOptions { InterceptUserInputRequests = false, EmitAgentUpdateEvents = true }); @@ -350,17 +350,17 @@ public class WorkflowHostSmokeTests // Assert AgentResponseUpdate? updateWithUserInput = updates.FirstOrDefault(u => - u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is UserInputRequestContent)); + u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is ToolApprovalRequestContent)); - updateWithUserInput.Should().NotBeNull("a UserInputRequestContent should be present in the response updates"); - UserInputRequestContent retrievedContent = updateWithUserInput!.Contents - .OfType() + updateWithUserInput.Should().NotBeNull("a ToolApprovalRequestContent should be present in the response updates"); + ToolApprovalRequestContent retrievedContent = updateWithUserInput!.Contents + .OfType() .Should().ContainSingle() .Which; - retrievedContent.Should().BeOfType(); - retrievedContent.Id.Should().NotBe(RequestId); - retrievedContent.Id.Should().EndWith($":{RequestId}"); + retrievedContent.Should().NotBeNull(); + retrievedContent.RequestId.Should().NotBe(RequestId); + retrievedContent.RequestId.Should().EndWith($":{RequestId}"); } /// @@ -414,41 +414,40 @@ public class WorkflowHostSmokeTests } /// - /// Tests the full roundtrip for UserInputRequestContent: workflow emits request, external caller responds. - /// Verifying inbound UserInputResponseContent conversion. + /// Tests the full roundtrip for ToolApprovalRequestContent: workflow emits request, external caller responds. + /// Verifying inbound ToolApprovalResponseContent conversion. /// [Fact] - public async Task Test_AsAgent_UserInputRoundtrip_ResponseIsProcessedAsync() + public async Task Test_AsAgent_ToolApprovalRoundtrip_ResponseIsProcessedAsync() { - // Arrange: Create an agent that emits a UserInputRequestContent request + // Arrange: Create an agent that emits a ToolApprovalRequestContent request const string RequestId = "roundtrip-request-id"; McpServerToolCallContent mcpCall = new("mcp-call-id", "testMcpTool", "http://localhost"); - McpServerToolApprovalRequestContent requestContent = new(RequestId, mcpCall); + ToolApprovalRequestContent requestContent = new(RequestId, mcpCall); RequestEmittingAgent requestAgent = new(requestContent, completeOnResponse: true); ExecutorBinding agentBinding = requestAgent.BindAsExecutor( new AIAgentHostOptions { InterceptUserInputRequests = false, EmitAgentUpdateEvents = true }); Workflow workflow = new WorkflowBuilder(agentBinding).Build(); AIAgent agent = workflow.AsAIAgent("WorkflowAgent"); - // Act 1: First call - should receive the UserInputRequestContent request + // Act 1: First call - should receive the ToolApprovalRequestContent request AgentSession session = await agent.CreateSessionAsync(); List firstCallUpdates = await agent.RunStreamingAsync( new ChatMessage(ChatRole.User, "Start"), session).ToListAsync(); - // Assert 1: We should have received a UserInputRequestContent + // Assert 1: We should have received a ToolApprovalRequestContent AgentResponseUpdate? updateWithRequest = firstCallUpdates.FirstOrDefault(u => - u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is UserInputRequestContent)); - updateWithRequest.Should().NotBeNull("a UserInputRequestContent should be present in the response updates"); + u.RawRepresentation is RequestInfoEvent && u.Contents.Any(c => c is ToolApprovalRequestContent)); + updateWithRequest.Should().NotBeNull("a ToolApprovalRequestContent should be present in the response updates"); - UserInputRequestContent receivedRequest = updateWithRequest!.Contents - .OfType() + ToolApprovalRequestContent receivedRequest = updateWithRequest!.Contents + .OfType() .First(); - receivedRequest.Id.Should().EndWith($":{RequestId}"); - receivedRequest.Should().BeOfType(); + receivedRequest.RequestId.Should().EndWith($":{RequestId}"); // Act 2: Send the response back - use CreateResponse to get the right response type - UserInputResponseContent responseContent = ((McpServerToolApprovalRequestContent)receivedRequest).CreateResponse(approved: true); + ToolApprovalResponseContent responseContent = receivedRequest.CreateResponse(approved: true); ChatMessage responseMessage = new(ChatRole.User, [responseContent]); // Act 2: Run the workflow again with the response and capture the updates @@ -458,8 +457,8 @@ public class WorkflowHostSmokeTests secondCallUpdates.Should().NotBeEmpty("handling the user input response should produce follow-up updates"); bool requestStillPresent = secondCallUpdates.Any(u => u.RawRepresentation is RequestInfoEvent - && u.Contents.OfType().Any(r => r.Id == receivedRequest.Id)); - requestStillPresent.Should().BeFalse("the original UserInputRequestContent should not be re-emitted after its response is processed"); + && u.Contents.OfType().Any(r => r.RequestId == receivedRequest.RequestId)); + requestStillPresent.Should().BeFalse("the original ToolApprovalRequestContent should not be re-emitted after its response is processed"); } ///