mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-28 05:43:57 +02:00
fix: normalize runtime agent identities
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -61,6 +61,8 @@ The .NET sidecar emits `agent-activity` events when the workflow stream exposes
|
|||||||
|
|
||||||
In practice, `thinking` and `completed` are driven by executor lifecycle events, while `tool-calling` and `handoff` are emitted when request-info payloads expose recognizable tool-call or handoff targets.
|
In practice, `thinking` and `completed` are driven by executor lifecycle events, while `tool-calling` and `handoff` are emitted when request-info payloads expose recognizable tool-call or handoff targets.
|
||||||
|
|
||||||
|
One important runtime nuance is that MAF / Copilot executor identifiers are not always the raw configured agent IDs. Real streams can emit composite values such as `Architect_agent_concurrent_architect` or generic names such as `assistant`. The sidecar therefore normalizes these runtime identifiers back to the configured pattern agents before emitting `agent-activity` events or choosing the visible author name.
|
||||||
|
|
||||||
### .NET sidecar changes
|
### .NET sidecar changes
|
||||||
|
|
||||||
In the sidecar's turn-execution pipeline, emit a new JSON event type alongside the existing `turn-delta` and `turn-complete` events:
|
In the sidecar's turn-execution pipeline, emit a new JSON event type alongside the existing `turn-delta` and `turn-complete` events:
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Kopaya.AgentHost.Contracts;
|
||||||
|
|
||||||
|
namespace Kopaya.AgentHost.Services;
|
||||||
|
|
||||||
|
internal readonly record struct AgentIdentity(string AgentId, string AgentName);
|
||||||
|
|
||||||
|
internal static class AgentIdentityResolver
|
||||||
|
{
|
||||||
|
public static bool TryResolveKnownAgentIdentity(
|
||||||
|
PatternDefinitionDto pattern,
|
||||||
|
string? agentIdentifier,
|
||||||
|
out AgentIdentity agent)
|
||||||
|
{
|
||||||
|
agent = default;
|
||||||
|
if (string.IsNullOrWhiteSpace(agentIdentifier))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PatternAgentDefinitionDto? match = FindKnownAgent(pattern, agentIdentifier);
|
||||||
|
if (match is null
|
||||||
|
&& IsGenericAssistantIdentifier(agentIdentifier)
|
||||||
|
&& pattern.Agents.Count == 1)
|
||||||
|
{
|
||||||
|
match = pattern.Agents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent = ToAgentIdentity(match);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AgentIdentity ResolveAgentIdentity(
|
||||||
|
PatternDefinitionDto pattern,
|
||||||
|
string? agentId,
|
||||||
|
string? agentName)
|
||||||
|
{
|
||||||
|
PatternAgentDefinitionDto? match = FindKnownAgent(pattern, agentId)
|
||||||
|
?? FindKnownAgent(pattern, agentName);
|
||||||
|
|
||||||
|
if (match is null
|
||||||
|
&& pattern.Agents.Count == 1
|
||||||
|
&& (IsGenericAssistantIdentifier(agentId) || IsGenericAssistantIdentifier(agentName)))
|
||||||
|
{
|
||||||
|
match = pattern.Agents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match is not null)
|
||||||
|
{
|
||||||
|
return ToAgentIdentity(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
string resolvedAgentId = !string.IsNullOrWhiteSpace(agentId)
|
||||||
|
? agentId
|
||||||
|
: agentName ?? "agent";
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(agentName))
|
||||||
|
{
|
||||||
|
return new AgentIdentity(resolvedAgentId, agentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AgentIdentity(resolvedAgentId, resolvedAgentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ResolveDisplayAuthorName(
|
||||||
|
PatternDefinitionDto pattern,
|
||||||
|
string? primaryIdentifier,
|
||||||
|
string? fallbackIdentifier = null)
|
||||||
|
{
|
||||||
|
if (TryResolveKnownAgentIdentity(pattern, primaryIdentifier, out AgentIdentity primaryAgent))
|
||||||
|
{
|
||||||
|
return primaryAgent.AgentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryResolveKnownAgentIdentity(pattern, fallbackIdentifier, out AgentIdentity fallbackAgent))
|
||||||
|
{
|
||||||
|
return fallbackAgent.AgentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(primaryIdentifier))
|
||||||
|
{
|
||||||
|
return primaryIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(fallbackIdentifier))
|
||||||
|
{
|
||||||
|
return fallbackIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "assistant";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PatternAgentDefinitionDto? FindKnownAgent(PatternDefinitionDto pattern, string? candidate)
|
||||||
|
{
|
||||||
|
return pattern.Agents.FirstOrDefault(agent => MatchesAgent(agent, candidate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AgentIdentity ToAgentIdentity(PatternAgentDefinitionDto agent)
|
||||||
|
{
|
||||||
|
return new AgentIdentity(
|
||||||
|
agent.Id,
|
||||||
|
string.IsNullOrWhiteSpace(agent.Name) ? agent.Id : agent.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesAgent(PatternAgentDefinitionDto agent, string? candidate)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(candidate))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(agent.Id, candidate, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(agent.Name, candidate, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string normalizedCandidate = NormalizeComparisonKey(candidate);
|
||||||
|
string normalizedId = NormalizeComparisonKey(agent.Id);
|
||||||
|
string normalizedName = NormalizeComparisonKey(agent.Name);
|
||||||
|
|
||||||
|
if (normalizedCandidate.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedCandidate == normalizedId
|
||||||
|
|| normalizedCandidate == normalizedName)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedId.Length > 0
|
||||||
|
&& normalizedCandidate.EndsWith(normalizedId, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedId.Length > 0
|
||||||
|
&& normalizedName.Length > 0
|
||||||
|
&& normalizedCandidate.Contains(normalizedId, StringComparison.Ordinal)
|
||||||
|
&& normalizedCandidate.Contains(normalizedName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsGenericAssistantIdentifier(string? candidate)
|
||||||
|
{
|
||||||
|
return string.Equals(
|
||||||
|
NormalizeComparisonKey(candidate),
|
||||||
|
"assistant",
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeComparisonKey(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new(value.Length);
|
||||||
|
foreach (char character in value)
|
||||||
|
{
|
||||||
|
if (char.IsLetterOrDigit(character))
|
||||||
|
{
|
||||||
|
builder.Append(char.ToLowerInvariant(character));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,6 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
"Microsoft.Extensions.AI.ImageGenerationToolCallContent, Microsoft.Extensions.AI.Abstractions");
|
"Microsoft.Extensions.AI.ImageGenerationToolCallContent, Microsoft.Extensions.AI.Abstractions");
|
||||||
private readonly PatternValidator _patternValidator;
|
private readonly PatternValidator _patternValidator;
|
||||||
|
|
||||||
private readonly record struct AgentIdentity(string AgentId, string AgentName);
|
|
||||||
|
|
||||||
public CopilotWorkflowRunner(PatternValidator patternValidator)
|
public CopilotWorkflowRunner(PatternValidator patternValidator)
|
||||||
{
|
{
|
||||||
_patternValidator = patternValidator;
|
_patternValidator = patternValidator;
|
||||||
@@ -59,7 +57,10 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync(cancellationToken).ConfigureAwait(false))
|
await foreach (WorkflowEvent evt in run.WatchStreamAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
if (evt is ExecutorInvokedEvent invoked
|
if (evt is ExecutorInvokedEvent invoked
|
||||||
&& TryResolveKnownAgentIdentity(command.Pattern, invoked.ExecutorId, out AgentIdentity invokedAgent))
|
&& AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
command.Pattern,
|
||||||
|
invoked.ExecutorId,
|
||||||
|
out AgentIdentity invokedAgent))
|
||||||
{
|
{
|
||||||
activeAgent = invokedAgent;
|
activeAgent = invokedAgent;
|
||||||
await EmitThinkingIfNeeded(
|
await EmitThinkingIfNeeded(
|
||||||
@@ -82,16 +83,27 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
}
|
}
|
||||||
else if (evt is AgentResponseUpdateEvent update && !string.IsNullOrEmpty(update.Update.Text))
|
else if (evt is AgentResponseUpdateEvent update && !string.IsNullOrEmpty(update.Update.Text))
|
||||||
{
|
{
|
||||||
|
AgentIdentity? updateAgent = null;
|
||||||
|
string authorName = update.ExecutorId;
|
||||||
|
if (AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
command.Pattern,
|
||||||
|
update.ExecutorId,
|
||||||
|
out AgentIdentity resolvedUpdateAgent))
|
||||||
|
{
|
||||||
|
updateAgent = resolvedUpdateAgent;
|
||||||
|
authorName = resolvedUpdateAgent.AgentName;
|
||||||
|
}
|
||||||
|
|
||||||
string messageId = update.Update.MessageId ?? $"{command.RequestId}-delta-{fallbackMessageIndex++}";
|
string messageId = update.Update.MessageId ?? $"{command.RequestId}-delta-{fallbackMessageIndex++}";
|
||||||
StreamingSegment segment = GetOrCreateSegment(segments, messageId, update.ExecutorId);
|
StreamingSegment segment = GetOrCreateSegment(segments, messageId, authorName);
|
||||||
segment.Content.Append(update.Update.Text);
|
segment.Content.Append(update.Update.Text);
|
||||||
|
|
||||||
if (TryResolveKnownAgentIdentity(command.Pattern, update.ExecutorId, out AgentIdentity updateAgent))
|
if (updateAgent.HasValue)
|
||||||
{
|
{
|
||||||
activeAgent = updateAgent;
|
activeAgent = updateAgent.Value;
|
||||||
await EmitThinkingIfNeeded(
|
await EmitThinkingIfNeeded(
|
||||||
command,
|
command,
|
||||||
updateAgent,
|
updateAgent.Value,
|
||||||
startedAgents,
|
startedAgents,
|
||||||
onActivity).ConfigureAwait(false);
|
onActivity).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -102,12 +114,15 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
RequestId = command.RequestId,
|
RequestId = command.RequestId,
|
||||||
SessionId = command.SessionId,
|
SessionId = command.SessionId,
|
||||||
MessageId = messageId,
|
MessageId = messageId,
|
||||||
AuthorName = update.ExecutorId,
|
AuthorName = authorName,
|
||||||
ContentDelta = update.Update.Text,
|
ContentDelta = update.Update.Text,
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (evt is ExecutorCompletedEvent completed
|
else if (evt is ExecutorCompletedEvent completed
|
||||||
&& TryResolveKnownAgentIdentity(command.Pattern, completed.ExecutorId, out AgentIdentity completedAgent))
|
&& AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
command.Pattern,
|
||||||
|
completed.ExecutorId,
|
||||||
|
out AgentIdentity completedAgent))
|
||||||
{
|
{
|
||||||
if (completedAgents.Add(completedAgent.AgentId))
|
if (completedAgents.Add(completedAgent.AgentId))
|
||||||
{
|
{
|
||||||
@@ -207,7 +222,10 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
{
|
{
|
||||||
foreach (ChatMessageDto message in messages)
|
foreach (ChatMessageDto message in messages)
|
||||||
{
|
{
|
||||||
if (!TryResolveKnownAgentIdentity(command.Pattern, message.AuthorName, out AgentIdentity messageAgent))
|
if (!AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
command.Pattern,
|
||||||
|
message.AuthorName,
|
||||||
|
out AgentIdentity messageAgent))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -236,7 +254,7 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
}
|
}
|
||||||
|
|
||||||
object? target = handoffTarget?.GetType().GetProperty("Target")?.GetValue(handoffTarget);
|
object? target = handoffTarget?.GetType().GetProperty("Target")?.GetValue(handoffTarget);
|
||||||
agent = ResolveAgentIdentity(
|
agent = AgentIdentityResolver.ResolveAgentIdentity(
|
||||||
pattern,
|
pattern,
|
||||||
GetStringProperty(target, "Id"),
|
GetStringProperty(target, "Id"),
|
||||||
GetStringProperty(target, "Name"));
|
GetStringProperty(target, "Name"));
|
||||||
@@ -275,65 +293,6 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryResolveKnownAgentIdentity(
|
|
||||||
PatternDefinitionDto pattern,
|
|
||||||
string? agentIdentifier,
|
|
||||||
out AgentIdentity agent)
|
|
||||||
{
|
|
||||||
agent = default;
|
|
||||||
if (string.IsNullOrWhiteSpace(agentIdentifier))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PatternAgentDefinitionDto? match = pattern.Agents.FirstOrDefault(agent =>
|
|
||||||
MatchesAgent(agent, agentIdentifier));
|
|
||||||
|
|
||||||
if (match is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
agent = new AgentIdentity(
|
|
||||||
match.Id,
|
|
||||||
string.IsNullOrWhiteSpace(match.Name) ? match.Id : match.Name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AgentIdentity ResolveAgentIdentity(
|
|
||||||
PatternDefinitionDto pattern,
|
|
||||||
string? agentId,
|
|
||||||
string? agentName)
|
|
||||||
{
|
|
||||||
PatternAgentDefinitionDto? match = pattern.Agents.FirstOrDefault(agent =>
|
|
||||||
MatchesAgent(agent, agentId) || MatchesAgent(agent, agentName));
|
|
||||||
|
|
||||||
if (match is not null)
|
|
||||||
{
|
|
||||||
return new AgentIdentity(
|
|
||||||
match.Id,
|
|
||||||
string.IsNullOrWhiteSpace(match.Name) ? match.Id : match.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
string resolvedAgentId = !string.IsNullOrWhiteSpace(agentId)
|
|
||||||
? agentId
|
|
||||||
: agentName ?? "agent";
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(agentName))
|
|
||||||
{
|
|
||||||
return new AgentIdentity(resolvedAgentId, agentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AgentIdentity(resolvedAgentId, resolvedAgentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MatchesAgent(PatternAgentDefinitionDto agent, string? candidate)
|
|
||||||
{
|
|
||||||
return !string.IsNullOrWhiteSpace(candidate)
|
|
||||||
&& (string.Equals(agent.Id, candidate, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(agent.Name, candidate, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type? LoadType(string assemblyQualifiedName)
|
private static Type? LoadType(string assemblyQualifiedName)
|
||||||
{
|
{
|
||||||
return Type.GetType(assemblyQualifiedName, throwOnError: false);
|
return Type.GetType(assemblyQualifiedName, throwOnError: false);
|
||||||
@@ -386,7 +345,10 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
{
|
{
|
||||||
Id = segment?.MessageId ?? $"{command.RequestId}-final-{segmentIndex}",
|
Id = segment?.MessageId ?? $"{command.RequestId}-final-{segmentIndex}",
|
||||||
Role = message.Role == ChatRole.System ? "system" : "assistant",
|
Role = message.Role == ChatRole.System ? "system" : "assistant",
|
||||||
AuthorName = message.AuthorName ?? segment?.AuthorName ?? "assistant",
|
AuthorName = AgentIdentityResolver.ResolveDisplayAuthorName(
|
||||||
|
command.Pattern,
|
||||||
|
message.AuthorName,
|
||||||
|
segment?.AuthorName),
|
||||||
Content = message.Text ?? segment?.Content.ToString() ?? string.Empty,
|
Content = message.Text ?? segment?.Content.ToString() ?? string.Empty,
|
||||||
CreatedAt = DateTimeOffset.UtcNow.ToString("O"),
|
CreatedAt = DateTimeOffset.UtcNow.ToString("O"),
|
||||||
});
|
});
|
||||||
@@ -398,7 +360,7 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
|
|||||||
{
|
{
|
||||||
Id = segment.MessageId,
|
Id = segment.MessageId,
|
||||||
Role = "assistant",
|
Role = "assistant",
|
||||||
AuthorName = segment.AuthorName,
|
AuthorName = AgentIdentityResolver.ResolveDisplayAuthorName(command.Pattern, segment.AuthorName),
|
||||||
Content = segment.Content.ToString(),
|
Content = segment.Content.ToString(),
|
||||||
CreatedAt = DateTimeOffset.UtcNow.ToString("O"),
|
CreatedAt = DateTimeOffset.UtcNow.ToString("O"),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using Kopaya.AgentHost.Contracts;
|
||||||
|
using Kopaya.AgentHost.Services;
|
||||||
|
|
||||||
|
namespace Kopaya.AgentHost.Tests;
|
||||||
|
|
||||||
|
public sealed class AgentIdentityResolverTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TryResolveKnownAgentIdentity_MatchesRuntimeExecutorIdentifier()
|
||||||
|
{
|
||||||
|
PatternDefinitionDto pattern = CreatePattern(
|
||||||
|
[
|
||||||
|
CreateAgent(id: "agent-concurrent-architect", name: "Architect"),
|
||||||
|
CreateAgent(id: "agent-concurrent-product", name: "Product"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
bool resolved = AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
pattern,
|
||||||
|
"Architect_agent_concurrent_architect",
|
||||||
|
out AgentIdentity agent);
|
||||||
|
|
||||||
|
Assert.True(resolved);
|
||||||
|
Assert.Equal("agent-concurrent-architect", agent.AgentId);
|
||||||
|
Assert.Equal("Architect", agent.AgentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryResolveKnownAgentIdentity_MatchesSanitizedNameAndId()
|
||||||
|
{
|
||||||
|
PatternDefinitionDto pattern = CreatePattern(
|
||||||
|
[
|
||||||
|
CreateAgent(id: "agent-single-primary", name: "Primary Agent"),
|
||||||
|
],
|
||||||
|
mode: "single");
|
||||||
|
|
||||||
|
bool resolved = AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
pattern,
|
||||||
|
"Primary_Agent_agent_single_primary",
|
||||||
|
out AgentIdentity agent);
|
||||||
|
|
||||||
|
Assert.True(resolved);
|
||||||
|
Assert.Equal("agent-single-primary", agent.AgentId);
|
||||||
|
Assert.Equal("Primary Agent", agent.AgentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryResolveKnownAgentIdentity_MapsAssistantToSingleAgent()
|
||||||
|
{
|
||||||
|
PatternDefinitionDto pattern = CreatePattern(
|
||||||
|
[
|
||||||
|
CreateAgent(id: "agent-single-primary", name: "Primary Agent"),
|
||||||
|
],
|
||||||
|
mode: "single");
|
||||||
|
|
||||||
|
bool resolved = AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
pattern,
|
||||||
|
"assistant",
|
||||||
|
out AgentIdentity agent);
|
||||||
|
|
||||||
|
Assert.True(resolved);
|
||||||
|
Assert.Equal("agent-single-primary", agent.AgentId);
|
||||||
|
Assert.Equal("Primary Agent", agent.AgentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryResolveKnownAgentIdentity_DoesNotGuessAssistantForMultiAgentPattern()
|
||||||
|
{
|
||||||
|
PatternDefinitionDto pattern = CreatePattern(
|
||||||
|
[
|
||||||
|
CreateAgent(id: "agent-concurrent-architect", name: "Architect"),
|
||||||
|
CreateAgent(id: "agent-concurrent-product", name: "Product"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
bool resolved = AgentIdentityResolver.TryResolveKnownAgentIdentity(
|
||||||
|
pattern,
|
||||||
|
"assistant",
|
||||||
|
out _);
|
||||||
|
|
||||||
|
Assert.False(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveDisplayAuthorName_UsesCanonicalAgentName()
|
||||||
|
{
|
||||||
|
PatternDefinitionDto pattern = CreatePattern(
|
||||||
|
[
|
||||||
|
CreateAgent(id: "agent-concurrent-implementer", name: "Implementer"),
|
||||||
|
],
|
||||||
|
mode: "single");
|
||||||
|
|
||||||
|
string authorName = AgentIdentityResolver.ResolveDisplayAuthorName(
|
||||||
|
pattern,
|
||||||
|
"Implementer_agent_concurrent_implementer");
|
||||||
|
|
||||||
|
Assert.Equal("Implementer", authorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PatternDefinitionDto CreatePattern(
|
||||||
|
IReadOnlyList<PatternAgentDefinitionDto> agents,
|
||||||
|
string mode = "concurrent")
|
||||||
|
{
|
||||||
|
return new PatternDefinitionDto
|
||||||
|
{
|
||||||
|
Id = $"{mode}-pattern",
|
||||||
|
Name = "Pattern",
|
||||||
|
Mode = mode,
|
||||||
|
Availability = "available",
|
||||||
|
Agents = agents,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PatternAgentDefinitionDto CreateAgent(string id, string name)
|
||||||
|
{
|
||||||
|
return new PatternAgentDefinitionDto
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
Model = "gpt-5.4",
|
||||||
|
Instructions = "Help with the request.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user