fix: restore final assistant responses

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-28 12:44:14 +01:00
co-authored by Copilot
parent f1fa52f9c3
commit e13e3b818a
2 changed files with 124 additions and 2 deletions
@@ -1,5 +1,6 @@
using System.Text;
using Aryx.AgentHost.Contracts;
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
namespace Aryx.AgentHost.Services;
@@ -85,7 +86,7 @@ internal static class WorkflowTranscriptProjector
assistantMessages.Count - messageIndex,
command.Pattern,
fallbackAgent);
string content = message.Text ?? matchedSegment?.Content ?? string.Empty;
string content = ResolveProjectedContent(message, matchedSegment);
if (string.IsNullOrWhiteSpace(content))
{
continue;
@@ -127,7 +128,9 @@ internal static class WorkflowTranscriptProjector
{
return new ChatMessageDto
{
Id = matchedSegment?.MessageId ?? $"{command.RequestId}-final-{fallbackOutputIndex}",
Id = matchedSegment?.MessageId
?? message.MessageId
?? $"{command.RequestId}-final-{fallbackOutputIndex}",
Role = message.Role == ChatRole.System ? "system" : "assistant",
AuthorName = ResolveProjectedAuthorName(
command.Pattern,
@@ -139,6 +142,35 @@ internal static class WorkflowTranscriptProjector
};
}
private static string ResolveProjectedContent(
ChatMessage message,
TranscriptSegment? matchedSegment)
{
return FirstNonBlank(
message.Text,
matchedSegment?.Content,
TryGetAssistantMessageContent(message))
?? string.Empty;
}
private static string? TryGetAssistantMessageContent(ChatMessage message)
{
if (TryGetAssistantMessageData(message.RawRepresentation, out AssistantMessageData? assistantMessageData))
{
return assistantMessageData?.Content;
}
foreach (AIContent content in message.Contents)
{
if (TryGetAssistantMessageData(content.RawRepresentation, out assistantMessageData))
{
return assistantMessageData?.Content;
}
}
return null;
}
private static ChatMessageDto CreateProjectedMessageFromSegment(
RunTurnCommandDto command,
TranscriptSegment segment,
@@ -360,11 +392,57 @@ internal static class WorkflowTranscriptProjector
return fallbackAgent.Value.AgentName;
}
if (fallbackAgent.HasValue
&& string.IsNullOrWhiteSpace(primaryIdentifier)
&& string.IsNullOrWhiteSpace(fallbackIdentifier))
{
return fallbackAgent.Value.AgentName;
}
if (pattern.Agents.Count == 1
&& string.IsNullOrWhiteSpace(primaryIdentifier)
&& string.IsNullOrWhiteSpace(fallbackIdentifier))
{
PatternAgentDefinitionDto singleAgent = pattern.Agents[0];
return AgentIdentityResolver.ResolveDisplayAuthorName(pattern, singleAgent.Id, singleAgent.Name);
}
return AgentIdentityResolver.ResolveDisplayAuthorName(
pattern,
primaryIdentifier,
fallbackIdentifier);
}
private static bool TryGetAssistantMessageData(
object? rawRepresentation,
out AssistantMessageData? assistantMessageData)
{
switch (rawRepresentation)
{
case AssistantMessageEvent assistantMessage when !string.IsNullOrWhiteSpace(assistantMessage.Data.Content):
assistantMessageData = assistantMessage.Data;
return true;
case AssistantMessageData data when !string.IsNullOrWhiteSpace(data.Content):
assistantMessageData = data;
return true;
default:
assistantMessageData = null;
return false;
}
}
private static string? FirstNonBlank(params string?[] values)
{
foreach (string? value in values)
{
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
}
return null;
}
}
internal sealed class StreamingTranscriptBuffer
@@ -160,6 +160,50 @@ public sealed class CopilotWorkflowRunnerTests
Assert.Equal("Hello", message.Content);
}
[Fact]
public void ProjectCompletedMessages_UsesFinalAssistantPayloadWhenStreamingTextIsMissing()
{
RunTurnCommandDto command = new()
{
RequestId = "turn-1",
SessionId = "session-1",
Pattern = new PatternDefinitionDto
{
Id = "pattern-single",
Name = "Single Agent",
Mode = "single",
Availability = "available",
Agents =
[
CreateAgent(id: "agent-single-primary", name: "Primary Agent"),
],
},
};
IReadOnlyList<ChatMessageDto> messages = WorkflowTranscriptProjector.ProjectCompletedMessages(
command,
[
new ChatMessage(ChatRole.Assistant, string.Empty)
{
MessageId = "msg-1",
RawRepresentation = new AssistantMessageEvent
{
Data = new AssistantMessageData
{
MessageId = "msg-1",
Content = "Hello from the final assistant payload.",
},
},
},
],
[]);
ChatMessageDto message = Assert.Single(messages);
Assert.Equal("msg-1", message.Id);
Assert.Equal("Primary Agent", message.AuthorName);
Assert.Equal("Hello from the final assistant payload.", message.Content);
}
[Fact]
public void ProjectCompletedMessages_PreservesSequentialConversationHistory()
{