refactor: simplify workflow request interpretation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-24 21:29:35 +01:00
co-authored by Copilot
parent 38ebe5bdfa
commit 8b2e7cd2c0
4 changed files with 71 additions and 18 deletions
@@ -0,0 +1,5 @@
using System.Diagnostics.CodeAnalysis;
[assembly: Experimental(
"MEAI001",
UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}")]
@@ -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<FunctionCallContent>(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<McpServerToolCallContent>(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<CodeInterpreterToolCallContent>(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<ImageGenerationToolCallContent>())
{
toolName = "image generation";
toolCallId = null;
@@ -0,0 +1,5 @@
using System.Diagnostics.CodeAnalysis;
[assembly: Experimental(
"MEAI001",
UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}")]
@@ -31,6 +31,25 @@ public sealed class WorkflowRequestInfoInterpreterTests
Assert.Equal("view", toolNamesByCallId["call-1"]);
}
[Fact]
public void TryCreateActivityFromRequest_MapsMcpToolCalls()
{
ConcurrentDictionary<string, string> 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(