mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-09 13:18:46 +02:00
refactor: decompose CopilotWorkflowRunner
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
new(ChatRole.Assistant, "Hi there."),
|
||||
];
|
||||
|
||||
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||
IReadOnlyList<ChatMessage> newMessages = WorkflowTranscriptProjector.SelectNewOutputMessages(
|
||||
outputMessages,
|
||||
inputMessages);
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
new(ChatRole.Assistant, "Hi there."),
|
||||
];
|
||||
|
||||
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||
IReadOnlyList<ChatMessage> newMessages = WorkflowTranscriptProjector.SelectNewOutputMessages(
|
||||
outputMessages,
|
||||
inputMessages);
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
new(ChatRole.Assistant, "Hi there."),
|
||||
];
|
||||
|
||||
IReadOnlyList<ChatMessage> newMessages = CopilotWorkflowRunner.SelectNewOutputMessages(
|
||||
IReadOnlyList<ChatMessage> newMessages = WorkflowTranscriptProjector.SelectNewOutputMessages(
|
||||
outputMessages,
|
||||
inputMessages);
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
},
|
||||
};
|
||||
|
||||
IReadOnlyList<ChatMessageDto> messages = CopilotWorkflowRunner.ProjectCompletedMessages(
|
||||
IReadOnlyList<ChatMessageDto> messages = WorkflowTranscriptProjector.ProjectCompletedMessages(
|
||||
command,
|
||||
[],
|
||||
[
|
||||
@@ -138,7 +138,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
},
|
||||
};
|
||||
|
||||
IReadOnlyList<ChatMessageDto> messages = CopilotWorkflowRunner.ProjectCompletedMessages(
|
||||
IReadOnlyList<ChatMessageDto> messages = WorkflowTranscriptProjector.ProjectCompletedMessages(
|
||||
command,
|
||||
[
|
||||
new ChatMessage(ChatRole.Assistant, "Hello")
|
||||
@@ -177,7 +177,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
},
|
||||
};
|
||||
|
||||
IReadOnlyList<ChatMessageDto> messages = CopilotWorkflowRunner.ProjectCompletedMessages(
|
||||
IReadOnlyList<ChatMessageDto> messages = WorkflowTranscriptProjector.ProjectCompletedMessages(
|
||||
command,
|
||||
[
|
||||
new ChatMessage(ChatRole.Assistant, "The button is in place.")
|
||||
@@ -214,7 +214,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
},
|
||||
};
|
||||
|
||||
IReadOnlyList<ChatMessageDto> messages = CopilotWorkflowRunner.ProjectCompletedMessages(
|
||||
IReadOnlyList<ChatMessageDto> messages = WorkflowTranscriptProjector.ProjectCompletedMessages(
|
||||
command,
|
||||
[
|
||||
new ChatMessage(ChatRole.Assistant, string.Empty)
|
||||
@@ -234,6 +234,52 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("Real content", message.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamingTranscriptBuffer_MergesUpdatesPerMessageId()
|
||||
{
|
||||
StreamingTranscriptBuffer buffer = new();
|
||||
|
||||
buffer.AppendDelta("msg-1", "Architect", "Hello");
|
||||
(string messageId, string authorName, string content) = buffer.AppendDelta("msg-1", "Architect", " world");
|
||||
|
||||
Assert.Equal("msg-1", messageId);
|
||||
Assert.Equal("Architect", authorName);
|
||||
Assert.Equal("Hello world", content);
|
||||
Assert.Collection(
|
||||
buffer.Snapshot(),
|
||||
segment =>
|
||||
{
|
||||
Assert.Equal("msg-1", segment.MessageId);
|
||||
Assert.Equal("Architect", segment.AuthorName);
|
||||
Assert.Equal("Hello world", segment.Content);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamingTranscriptBuffer_PreservesInsertionOrderAcrossMessages()
|
||||
{
|
||||
StreamingTranscriptBuffer buffer = new();
|
||||
|
||||
buffer.AppendDelta("msg-1", "Architect", "A");
|
||||
buffer.AppendDelta("msg-2", "Implementer", "B");
|
||||
buffer.AppendDelta("msg-1", "Architect", " plus");
|
||||
|
||||
Assert.Collection(
|
||||
buffer.Snapshot(),
|
||||
first =>
|
||||
{
|
||||
Assert.Equal("msg-1", first.MessageId);
|
||||
Assert.Equal("Architect", first.AuthorName);
|
||||
Assert.Equal("A plus", first.Content);
|
||||
},
|
||||
second =>
|
||||
{
|
||||
Assert.Equal("msg-2", second.MessageId);
|
||||
Assert.Equal("Implementer", second.AuthorName);
|
||||
Assert.Equal("B", second.Content);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiresToolCallApproval_HonorsAutoApprovedToolNames()
|
||||
{
|
||||
@@ -250,18 +296,18 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
AutoApprovedToolNames = ["lsp_ts_hover", "web_fetch"],
|
||||
};
|
||||
|
||||
Assert.False(CopilotWorkflowRunner.RequiresToolCallApproval(policy, "agent-1", "lsp_ts_hover"));
|
||||
Assert.False(CopilotWorkflowRunner.RequiresToolCallApproval(policy, "agent-1", "web_fetch"));
|
||||
Assert.True(CopilotWorkflowRunner.RequiresToolCallApproval(policy, "agent-1", "lsp_ts_definition"));
|
||||
Assert.True(CopilotWorkflowRunner.RequiresToolCallApproval(policy, "agent-1", null));
|
||||
Assert.False(CopilotWorkflowRunner.RequiresToolCallApproval(policy, "agent-2", "lsp_ts_definition"));
|
||||
Assert.False(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "lsp_ts_hover"));
|
||||
Assert.False(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "web_fetch"));
|
||||
Assert.True(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", "lsp_ts_definition"));
|
||||
Assert.True(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-1", null));
|
||||
Assert.False(CopilotApprovalCoordinator.RequiresToolCallApproval(policy, "agent-2", "lsp_ts_definition"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetApprovalToolName_ReadsMcpCustomAndHookRequests()
|
||||
{
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestMcp
|
||||
{
|
||||
Kind = "mcp",
|
||||
@@ -274,7 +320,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("git.status", mcpToolName);
|
||||
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestCustomTool
|
||||
{
|
||||
Kind = "custom tool",
|
||||
@@ -285,7 +331,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("lsp_ts_hover", customToolName);
|
||||
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestHook
|
||||
{
|
||||
Kind = "hook",
|
||||
@@ -297,7 +343,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("web_fetch", hookToolName);
|
||||
|
||||
Assert.False(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestShell
|
||||
{
|
||||
Kind = "shell",
|
||||
@@ -324,7 +370,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
};
|
||||
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestUrl
|
||||
{
|
||||
Kind = "url",
|
||||
@@ -337,7 +383,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("web_fetch", urlToolName);
|
||||
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestShell
|
||||
{
|
||||
Kind = "shell",
|
||||
@@ -355,7 +401,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("shell", shellToolName);
|
||||
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestRead
|
||||
{
|
||||
Kind = "read",
|
||||
@@ -372,7 +418,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
public void TryGetApprovalToolName_FallsBackToWebFetchForUncorrelatedUrlRequests()
|
||||
{
|
||||
Assert.True(
|
||||
CopilotWorkflowRunner.TryGetApprovalToolName(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
new PermissionRequestUrl
|
||||
{
|
||||
Kind = "url",
|
||||
@@ -387,7 +433,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
[Fact]
|
||||
public void BuildPermissionApprovalEvent_IncludesToolContextWhenKnown()
|
||||
{
|
||||
ApprovalRequestedEventDto approvalEvent = CopilotWorkflowRunner.BuildPermissionApprovalEvent(
|
||||
ApprovalRequestedEventDto approvalEvent = CopilotApprovalCoordinator.BuildPermissionApprovalEvent(
|
||||
new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
@@ -415,7 +461,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
[Fact]
|
||||
public void BuildPermissionApprovalEvent_IncludesRequestedUrlForUrlPermissions()
|
||||
{
|
||||
ApprovalRequestedEventDto approvalEvent = CopilotWorkflowRunner.BuildPermissionApprovalEvent(
|
||||
ApprovalRequestedEventDto approvalEvent = CopilotApprovalCoordinator.BuildPermissionApprovalEvent(
|
||||
new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
@@ -443,6 +489,98 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Contains("https://example.com/docs", approvalEvent.Detail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestApprovalAsync_RaisesApprovalAndCompletesAfterResolution()
|
||||
{
|
||||
CopilotApprovalCoordinator coordinator = new();
|
||||
ApprovalRequestedEventDto? observedApproval = null;
|
||||
RunTurnCommandDto command = CreateApprovalCommand();
|
||||
|
||||
Task<PermissionRequestResult> pending = coordinator.RequestApprovalAsync(
|
||||
command,
|
||||
command.Pattern.Agents[0],
|
||||
new PermissionRequestCustomTool
|
||||
{
|
||||
Kind = "custom tool",
|
||||
ToolName = "lsp_ts_definition",
|
||||
ToolDescription = "Go to definition",
|
||||
},
|
||||
new PermissionInvocation
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
approval =>
|
||||
{
|
||||
observedApproval = approval;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(pending.IsCompleted);
|
||||
Assert.NotNull(observedApproval);
|
||||
|
||||
await coordinator.ResolveApprovalAsync(
|
||||
new ResolveApprovalCommandDto
|
||||
{
|
||||
ApprovalId = observedApproval!.ApprovalId,
|
||||
Decision = "approved",
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
PermissionRequestResult result = await pending;
|
||||
Assert.Equal(PermissionRequestResultKind.Approved, result.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestApprovalAsync_AutoApprovesToolsThatDoNotRequireApproval()
|
||||
{
|
||||
CopilotApprovalCoordinator coordinator = new();
|
||||
bool sawApproval = false;
|
||||
RunTurnCommandDto command = CreateApprovalCommand();
|
||||
|
||||
PermissionRequestResult result = await coordinator.RequestApprovalAsync(
|
||||
command,
|
||||
command.Pattern.Agents[0],
|
||||
new PermissionRequestCustomTool
|
||||
{
|
||||
Kind = "custom tool",
|
||||
ToolName = "web_fetch",
|
||||
ToolDescription = "Fetch documentation",
|
||||
},
|
||||
new PermissionInvocation
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
approval =>
|
||||
{
|
||||
sawApproval = true;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(sawApproval);
|
||||
Assert.Equal(PermissionRequestResultKind.Approved, result.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveApprovalAsync_RejectsUnknownApprovalIds()
|
||||
{
|
||||
CopilotApprovalCoordinator coordinator = new();
|
||||
|
||||
InvalidOperationException error = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
coordinator.ResolveApprovalAsync(
|
||||
new ResolveApprovalCommandDto
|
||||
{
|
||||
ApprovalId = "approval-missing",
|
||||
Decision = "approved",
|
||||
},
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Contains("is not pending", error.Message);
|
||||
}
|
||||
|
||||
private static PatternAgentDefinitionDto CreateAgent(string id, string name)
|
||||
{
|
||||
return new PatternAgentDefinitionDto
|
||||
@@ -453,4 +591,36 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Instructions = "Help with the request.",
|
||||
};
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateApprovalCommand()
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
SessionId = "session-1",
|
||||
Pattern = new PatternDefinitionDto
|
||||
{
|
||||
Id = "pattern-1",
|
||||
Name = "Approval Pattern",
|
||||
Mode = "single",
|
||||
Availability = "available",
|
||||
ApprovalPolicy = new ApprovalPolicyDto
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
new ApprovalCheckpointRuleDto
|
||||
{
|
||||
Kind = "tool-call",
|
||||
AgentIds = ["agent-1"],
|
||||
},
|
||||
],
|
||||
AutoApprovedToolNames = ["web_fetch"],
|
||||
},
|
||||
Agents =
|
||||
[
|
||||
CreateAgent("agent-1", "Primary"),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Eryx.AgentHost.Contracts;
|
||||
using Eryx.AgentHost.Services;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Eryx.AgentHost.Tests;
|
||||
|
||||
public sealed class WorkflowRequestInfoInterpreterTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_ReturnsToolCallingActivityForFunctionCalls()
|
||||
{
|
||||
ConcurrentDictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal);
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>()));
|
||||
|
||||
AgentActivityEventDto? activity = WorkflowRequestInfoInterpreter.TryCreateActivityFromRequest(
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
toolNamesByCallId);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
Assert.Equal("agent-1", activity.AgentId);
|
||||
Assert.Equal("Primary", activity.AgentName);
|
||||
Assert.Equal("view", activity.ToolName);
|
||||
Assert.Equal("view", toolNamesByCallId["call-1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_MapsCodeInterpreterCallsToSyntheticToolName()
|
||||
{
|
||||
ConcurrentDictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal);
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(CreateCodeInterpreterToolCall("call-1"));
|
||||
|
||||
AgentActivityEventDto? activity = WorkflowRequestInfoInterpreter.TryCreateActivityFromRequest(
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
toolNamesByCallId);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
Assert.Equal("code interpreter", activity.ToolName);
|
||||
Assert.Equal("code interpreter", toolNamesByCallId["call-1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_MapsImageGenerationCallsWithoutTrackingCallId()
|
||||
{
|
||||
ConcurrentDictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal);
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(CreateImageGenerationToolCall());
|
||||
|
||||
AgentActivityEventDto? activity = WorkflowRequestInfoInterpreter.TryCreateActivityFromRequest(
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
toolNamesByCallId);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
Assert.Equal("image generation", activity.ToolName);
|
||||
Assert.Empty(toolNamesByCallId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_ReturnsHandoffActivityForKnownTargets()
|
||||
{
|
||||
ConcurrentDictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal);
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
CreateHandoffTarget("agent-handoff-ux", "UX Specialist"));
|
||||
|
||||
AgentActivityEventDto? activity = WorkflowRequestInfoInterpreter.TryCreateActivityFromRequest(
|
||||
CreateHandoffCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-handoff-triage", "Triage"),
|
||||
toolNamesByCallId);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("handoff", activity.ActivityType);
|
||||
Assert.Equal("agent-handoff-ux", activity.AgentId);
|
||||
Assert.Equal("UX Specialist", activity.AgentName);
|
||||
Assert.Equal("agent-handoff-triage", activity.SourceAgentId);
|
||||
Assert.Equal("Triage", activity.SourceAgentName);
|
||||
Assert.Null(activity.ToolName);
|
||||
Assert.Empty(toolNamesByCallId);
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateSingleAgentCommand()
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
SessionId = "session-1",
|
||||
Pattern = new PatternDefinitionDto
|
||||
{
|
||||
Id = "pattern-single",
|
||||
Name = "Single Agent",
|
||||
Mode = "single",
|
||||
Availability = "available",
|
||||
Agents =
|
||||
[
|
||||
CreateAgent("agent-1", "Primary"),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateHandoffCommand()
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
SessionId = "session-1",
|
||||
Pattern = new PatternDefinitionDto
|
||||
{
|
||||
Id = "pattern-handoff",
|
||||
Name = "Handoff Flow",
|
||||
Mode = "handoff",
|
||||
Availability = "available",
|
||||
Agents =
|
||||
[
|
||||
CreateAgent("agent-handoff-triage", "Triage"),
|
||||
CreateAgent("agent-handoff-ux", "UX Specialist"),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static PatternAgentDefinitionDto CreateAgent(string id, string name)
|
||||
{
|
||||
return new PatternAgentDefinitionDto
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Model = "gpt-5.4",
|
||||
Instructions = "Help with the request.",
|
||||
};
|
||||
}
|
||||
|
||||
private static RequestInfoEvent CreateRequestInfoEvent(object payload)
|
||||
{
|
||||
RequestPort port = RequestPort.Create<object, object>("test-port");
|
||||
ExternalRequest request = ExternalRequest.Create(port, payload, "request-1");
|
||||
return new RequestInfoEvent(request);
|
||||
}
|
||||
|
||||
private static object CreateCodeInterpreterToolCall(string callId)
|
||||
{
|
||||
Type type = Type.GetType(
|
||||
"Microsoft.Extensions.AI.CodeInterpreterToolCallContent, Microsoft.Extensions.AI.Abstractions",
|
||||
throwOnError: true)!;
|
||||
object instance = Activator.CreateInstance(type)!;
|
||||
type.GetProperty("CallId")!.SetValue(instance, callId);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static object CreateImageGenerationToolCall()
|
||||
{
|
||||
Type type = Type.GetType(
|
||||
"Microsoft.Extensions.AI.ImageGenerationToolCallContent, Microsoft.Extensions.AI.Abstractions",
|
||||
throwOnError: true)!;
|
||||
return Activator.CreateInstance(type)!;
|
||||
}
|
||||
|
||||
private static object CreateHandoffTarget(string id, string name)
|
||||
{
|
||||
Type type = Type.GetType(
|
||||
"Microsoft.Agents.AI.Workflows.Specialized.HandoffTarget, Microsoft.Agents.AI.Workflows",
|
||||
throwOnError: true)!;
|
||||
return Activator.CreateInstance(type, CreateChatClientAgent(id, name), "Handle the UX work.")!;
|
||||
}
|
||||
|
||||
private static ChatClientAgent CreateChatClientAgent(string id, string name)
|
||||
{
|
||||
return new ChatClientAgent(
|
||||
new StubChatClient(),
|
||||
id,
|
||||
name,
|
||||
"Stub agent for handoff tests.",
|
||||
[],
|
||||
null!,
|
||||
null!);
|
||||
}
|
||||
|
||||
private sealed class StubChatClient : IChatClient
|
||||
{
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options,
|
||||
[EnumeratorCancellation]
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user