diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
index ad8435842b..144a560f7f 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
@@ -458,8 +458,9 @@ internal static class ChatResponseUpdateAGUIExtensions
// This ensures all AGUI events have a valid messageId regardless of agent type.
if (string.IsNullOrWhiteSpace(chatResponse.MessageId))
{
- streamingMessageId ??= Guid.NewGuid().ToString("N");
- chatResponse.MessageId = streamingMessageId;
+ chatResponse.MessageId = ContainsToolResult(chatResponse)
+ ? Guid.NewGuid().ToString("N")
+ : (streamingMessageId ??= Guid.NewGuid().ToString("N"));
}
if (chatResponse is { Contents.Count: > 0 } &&
@@ -725,4 +726,17 @@ internal static class ChatResponseUpdateAGUIExtensions
_ => JsonSerializer.Serialize(functionResultContent.Result, options.GetTypeInfo(functionResultContent.Result.GetType())),
};
}
+
+ private static bool ContainsToolResult(ChatResponseUpdate chatResponse)
+ {
+ foreach (AIContent content in chatResponse.Contents)
+ {
+ if (content is FunctionResultContent)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIStreamingMessageIdTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIStreamingMessageIdTests.cs
index 5c55408ff8..502e23d81c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIStreamingMessageIdTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIStreamingMessageIdTests.cs
@@ -149,6 +149,93 @@ public sealed class AGUIStreamingMessageIdTests
"ParentMessageId should have a generated fallback for empty provider MessageId");
}
+ ///
+ /// Tool results are separate tool-role messages, so their fallback IDs must not
+ /// collide with the assistant message that requested the tool call.
+ ///
+ [Fact]
+ public async Task ToolResults_NullMessageId_GeneratesDistinctMessageIdAsync()
+ {
+ FunctionCallContent functionCall = new("call_abc123", "GetWeather")
+ {
+ Arguments = new Dictionary { ["location"] = "San Francisco" }
+ };
+
+ List providerUpdates =
+ [
+ new ChatResponseUpdate(ChatRole.Assistant, "Checking the weather"),
+ new ChatResponseUpdate
+ {
+ Role = ChatRole.Assistant,
+ Contents = [functionCall]
+ },
+ new ChatResponseUpdate(ChatRole.Tool, [new FunctionResultContent("call_abc123", "72F and sunny")])
+ ];
+
+ List aguiEvents = [];
+ await foreach (BaseEvent evt in providerUpdates.ToAsyncEnumerableAsync()
+ .AsAGUIEventStreamAsync("thread-1", "run-1", AGUIJsonSerializerContext.Default.Options))
+ {
+ aguiEvents.Add(evt);
+ }
+
+ TextMessageStartEvent textStart = Assert.Single(aguiEvents.OfType());
+ ToolCallStartEvent toolCallStart = Assert.Single(aguiEvents.OfType());
+ ToolCallResultEvent toolCallResult = Assert.Single(aguiEvents.OfType());
+
+ Assert.Equal(textStart.MessageId, toolCallStart.ParentMessageId);
+ Assert.Equal("call_abc123", toolCallResult.ToolCallId);
+ Assert.False(string.IsNullOrEmpty(toolCallResult.MessageId));
+ Assert.NotEqual(textStart.MessageId, toolCallResult.MessageId);
+ }
+
+ [Fact]
+ public async Task ToolResults_WithTextContent_GeneratesDistinctMessageIdAsync()
+ {
+ FunctionCallContent functionCall = new("call_abc123", "GetWeather")
+ {
+ Arguments = new Dictionary { ["location"] = "San Francisco" }
+ };
+
+ List providerUpdates =
+ [
+ new ChatResponseUpdate(ChatRole.Assistant, "Checking the weather"),
+ new ChatResponseUpdate
+ {
+ Role = ChatRole.Assistant,
+ Contents = [functionCall]
+ },
+ new ChatResponseUpdate
+ {
+ Role = ChatRole.Tool,
+ Contents =
+ [
+ new TextContent("Tool says: "),
+ new FunctionResultContent("call_abc123", "72F and sunny")
+ ]
+ }
+ ];
+
+ List aguiEvents = [];
+ await foreach (BaseEvent evt in providerUpdates.ToAsyncEnumerableAsync()
+ .AsAGUIEventStreamAsync("thread-1", "run-1", AGUIJsonSerializerContext.Default.Options))
+ {
+ aguiEvents.Add(evt);
+ }
+
+ TextMessageStartEvent[] textStarts = aguiEvents.OfType().ToArray();
+ TextMessageContentEvent toolText = Assert.Single(
+ aguiEvents.OfType(),
+ content => content.Delta == "Tool says: ");
+ ToolCallStartEvent toolCallStart = Assert.Single(aguiEvents.OfType());
+ ToolCallResultEvent toolCallResult = Assert.Single(aguiEvents.OfType());
+
+ Assert.Equal(textStarts[0].MessageId, toolCallStart.ParentMessageId);
+ Assert.NotEqual(textStarts[0].MessageId, toolCallResult.MessageId);
+ Assert.Equal(toolCallResult.MessageId, toolText.MessageId);
+ Assert.Equal(textStarts[^1].MessageId, toolCallResult.MessageId);
+ }
+
///
/// When a provider properly sets MessageId (e.g., OpenAI), the AGUI pipeline
/// produces valid events with correct messageId values.