From 8b2e7cd2c012aeaad5bb1c023d7f7fd2ea917b3f Mon Sep 17 00:00:00 2001 From: David Kaya Date: Tue, 24 Mar 2026 21:29:35 +0100 Subject: [PATCH] refactor: simplify workflow request interpretation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Eryx.AgentHost/ExperimentalOptIns.cs | 5 ++ .../WorkflowRequestInfoInterpreter.cs | 52 ++++++++++++------- .../ExperimentalOptIns.cs | 5 ++ .../WorkflowRequestInfoInterpreterTests.cs | 27 ++++++++++ 4 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 sidecar/src/Eryx.AgentHost/ExperimentalOptIns.cs create mode 100644 sidecar/tests/Eryx.AgentHost.Tests/ExperimentalOptIns.cs diff --git a/sidecar/src/Eryx.AgentHost/ExperimentalOptIns.cs b/sidecar/src/Eryx.AgentHost/ExperimentalOptIns.cs new file mode 100644 index 0000000..0e33b40 --- /dev/null +++ b/sidecar/src/Eryx.AgentHost/ExperimentalOptIns.cs @@ -0,0 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: Experimental( + "MEAI001", + UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}")] diff --git a/sidecar/src/Eryx.AgentHost/Services/WorkflowRequestInfoInterpreter.cs b/sidecar/src/Eryx.AgentHost/Services/WorkflowRequestInfoInterpreter.cs index e034a6d..90e4bee 100644 --- a/sidecar/src/Eryx.AgentHost/Services/WorkflowRequestInfoInterpreter.cs +++ b/sidecar/src/Eryx.AgentHost/Services/WorkflowRequestInfoInterpreter.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using Eryx.AgentHost.Contracts; using Microsoft.Agents.AI.Workflows; +using Microsoft.Extensions.AI; namespace Eryx.AgentHost.Services; @@ -8,14 +9,6 @@ internal static class WorkflowRequestInfoInterpreter { private static readonly Type? HandoffTargetType = LoadType( "Microsoft.Agents.AI.Workflows.Specialized.HandoffTarget, Microsoft.Agents.AI.Workflows"); - private static readonly Type? FunctionCallContentType = LoadType( - "Microsoft.Extensions.AI.FunctionCallContent, Microsoft.Extensions.AI.Abstractions"); - private static readonly Type? McpServerToolCallContentType = LoadType( - "Microsoft.Extensions.AI.McpServerToolCallContent, Microsoft.Extensions.AI.Abstractions"); - private static readonly Type? CodeInterpreterToolCallContentType = LoadType( - "Microsoft.Extensions.AI.CodeInterpreterToolCallContent, Microsoft.Extensions.AI.Abstractions"); - private static readonly Type? ImageGenerationToolCallContentType = LoadType( - "Microsoft.Extensions.AI.ImageGenerationToolCallContent, Microsoft.Extensions.AI.Abstractions"); public static AgentActivityEventDto? TryCreateActivityFromRequest( RunTurnCommandDto command, @@ -85,30 +78,53 @@ internal static class WorkflowRequestInfoInterpreter out string toolName, out string? toolCallId) { - if (TryReadPortableValue(requestInfo.Request.Data, FunctionCallContentType, out object? functionCall)) + if (TryGetStableToolRequestInfo(requestInfo.Request.Data, out toolName, out toolCallId)) { - toolName = GetStringProperty(functionCall, "Name") ?? "function"; - toolCallId = NormalizeOptionalString(GetStringProperty(functionCall, "CallId")); return true; } - if (TryReadPortableValue(requestInfo.Request.Data, McpServerToolCallContentType, out object? mcpToolCall)) + return TryGetEvaluationToolRequestInfo(requestInfo.Request.Data, out toolName, out toolCallId); + } + + private static bool TryGetStableToolRequestInfo( + PortableValue requestData, + out string toolName, + out string? toolCallId) + { + if (requestData.Is(out FunctionCallContent? functionCall)) { - toolName = GetStringProperty(mcpToolCall, "ToolName") - ?? GetStringProperty(mcpToolCall, "ServerName") + toolName = NormalizeOptionalString(functionCall.Name) ?? "function"; + toolCallId = NormalizeOptionalString(functionCall.CallId); + return true; + } + + toolName = string.Empty; + toolCallId = null; + return false; + } + + private static bool TryGetEvaluationToolRequestInfo( + PortableValue requestData, + out string toolName, + out string? toolCallId) + { + if (requestData.Is(out McpServerToolCallContent? mcpToolCall)) + { + toolName = NormalizeOptionalString(mcpToolCall.ToolName) + ?? NormalizeOptionalString(mcpToolCall.ServerName) ?? string.Empty; - toolCallId = NormalizeOptionalString(GetStringProperty(mcpToolCall, "CallId")); + toolCallId = NormalizeOptionalString(mcpToolCall.CallId); return !string.IsNullOrWhiteSpace(toolName); } - if (TryReadPortableValue(requestInfo.Request.Data, CodeInterpreterToolCallContentType, out object? codeInterpreterToolCall)) + if (requestData.Is(out CodeInterpreterToolCallContent? codeInterpreterToolCall)) { toolName = "code interpreter"; - toolCallId = NormalizeOptionalString(GetStringProperty(codeInterpreterToolCall, "CallId")); + toolCallId = NormalizeOptionalString(codeInterpreterToolCall.CallId); return true; } - if (TryReadPortableValue(requestInfo.Request.Data, ImageGenerationToolCallContentType, out _)) + if (requestData.Is()) { toolName = "image generation"; toolCallId = null; diff --git a/sidecar/tests/Eryx.AgentHost.Tests/ExperimentalOptIns.cs b/sidecar/tests/Eryx.AgentHost.Tests/ExperimentalOptIns.cs new file mode 100644 index 0000000..0e33b40 --- /dev/null +++ b/sidecar/tests/Eryx.AgentHost.Tests/ExperimentalOptIns.cs @@ -0,0 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: Experimental( + "MEAI001", + UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}")] diff --git a/sidecar/tests/Eryx.AgentHost.Tests/WorkflowRequestInfoInterpreterTests.cs b/sidecar/tests/Eryx.AgentHost.Tests/WorkflowRequestInfoInterpreterTests.cs index d3e9162..60e4885 100644 --- a/sidecar/tests/Eryx.AgentHost.Tests/WorkflowRequestInfoInterpreterTests.cs +++ b/sidecar/tests/Eryx.AgentHost.Tests/WorkflowRequestInfoInterpreterTests.cs @@ -31,6 +31,25 @@ public sealed class WorkflowRequestInfoInterpreterTests Assert.Equal("view", toolNamesByCallId["call-1"]); } + [Fact] + public void TryCreateActivityFromRequest_MapsMcpToolCalls() + { + ConcurrentDictionary toolNamesByCallId = new(StringComparer.Ordinal); + RequestInfoEvent requestInfo = CreateRequestInfoEvent( + CreateMcpToolCall("call-1", "git.status", "Git MCP")); + + AgentActivityEventDto? activity = WorkflowRequestInfoInterpreter.TryCreateActivityFromRequest( + CreateSingleAgentCommand(), + requestInfo, + new AgentIdentity("agent-1", "Primary"), + toolNamesByCallId); + + Assert.NotNull(activity); + Assert.Equal("tool-calling", activity.ActivityType); + Assert.Equal("git.status", activity.ToolName); + Assert.Equal("git.status", toolNamesByCallId["call-1"]); + } + [Fact] public void TryCreateActivityFromRequest_MapsCodeInterpreterCallsToSyntheticToolName() { @@ -159,6 +178,14 @@ public sealed class WorkflowRequestInfoInterpreterTests return instance; } + private static object CreateMcpToolCall(string callId, string toolName, string serverName) + { + Type type = Type.GetType( + "Microsoft.Extensions.AI.McpServerToolCallContent, Microsoft.Extensions.AI.Abstractions", + throwOnError: true)!; + return Activator.CreateInstance(type, callId, toolName, serverName)!; + } + private static object CreateImageGenerationToolCall() { Type type = Type.GetType(