mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-29 07:58:47 +02:00
fix: classify unstreamed sub-agent messages as thinking
Sub-agent messages bypass the streaming path (turn-delta) entirely due to SDK batching behavior. They arrive only at turn-complete time via FinalizeCompletedMessages. Without classification, they appear as separate chat bubbles cluttering the transcript. Two-pronged fix: Sidecar: FinalizeCompletedMessages now tags messages from _reclassifiedMessageIds with MessageKind='thinking'. This covers messages that WERE streamed and reclassified during the turn. Added MessageKind property to ChatMessageDto. Main process: finalizeTurn detects unstreamed assistant messages (not in existing session.messages) and classifies them as thinking when a visible response was already streamed. Emits message-reclassified events so the renderer can update incrementally, though the primary path is the workspace:updated broadcast which already includes the correct messageKind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Aryx.AgentHost.Contracts;
|
||||
using Aryx.AgentHost.Services;
|
||||
using GitHub.Copilot.SDK;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Aryx.AgentHost.Tests;
|
||||
|
||||
@@ -601,6 +602,56 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
""");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FinalizeCompletedMessages_TagsReclassifiedMessagesAsThinking()
|
||||
{
|
||||
RunTurnCommandDto command = CreateCommand();
|
||||
CopilotTurnExecutionState state = new(command);
|
||||
|
||||
// Simulate assistant message with tool requests → triggers reclassification
|
||||
state.ObserveSessionEvent(
|
||||
command.Pattern.Agents[0],
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "assistant.message",
|
||||
"data": {
|
||||
"messageId": "msg-intermediate",
|
||||
"content": "Let me search...",
|
||||
"toolRequests": [
|
||||
{
|
||||
"toolCallId": "tool-call-1",
|
||||
"name": "grep",
|
||||
"arguments": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"timestamp": "2026-03-27T00:00:00Z"
|
||||
}
|
||||
"""));
|
||||
|
||||
state.DrainPendingEvents();
|
||||
|
||||
// Build completed messages with a reclassified and a non-reclassified message
|
||||
ChatMessage intermediateMsg = new(ChatRole.Assistant, "Let me search...");
|
||||
intermediateMsg.MessageId = "msg-intermediate";
|
||||
intermediateMsg.AuthorName = "Primary";
|
||||
|
||||
ChatMessage finalMsg = new(ChatRole.Assistant, "Here are the results.");
|
||||
finalMsg.MessageId = "msg-final";
|
||||
finalMsg.AuthorName = "Primary";
|
||||
|
||||
state.UpdateCompletedMessages([intermediateMsg, finalMsg], []);
|
||||
IReadOnlyList<ChatMessageDto> messages = state.FinalizeCompletedMessages();
|
||||
|
||||
ChatMessageDto intermediate = Assert.Single(messages, m => m.Id == "msg-intermediate");
|
||||
Assert.Equal("thinking", intermediate.MessageKind);
|
||||
|
||||
ChatMessageDto final_ = Assert.Single(messages, m => m.Id == "msg-final");
|
||||
Assert.Null(final_.MessageKind);
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateCommand()
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
|
||||
Reference in New Issue
Block a user