Skip to content

.NET: Fix duplicate messageId in consecutive TOOL_CALL_RESULT SSE eve…#4035

Open
kallebelins wants to merge 1 commit intomicrosoft:mainfrom
kallebelins:copilot/fix-agui-duplicate-messageid-tool-call-result-3962
Open

.NET: Fix duplicate messageId in consecutive TOOL_CALL_RESULT SSE eve…#4035
kallebelins wants to merge 1 commit intomicrosoft:mainfrom
kallebelins:copilot/fix-agui-duplicate-messageid-tool-call-result-3962

Conversation

@kallebelins
Copy link

When multiple FunctionResultContent items share the same ChatResponseUpdate, the AsAGUIEventStreamAsync method now generates a deterministic unique messageId by combining the original MessageId with the CallId (e.g. msg1_call_1, msg1_call_2), preserving traceability while satisfying the AG-UI protocol uniqueness constraint. Fixes #3962

Motivation and Context

The AG-UI protocol specification requires every SSE event of type TOOL_CALL_RESULT to carry a unique messageId. However, in Microsoft.Extensions.AI, when an LLM returns multiple tool call results in a single response, all FunctionResultContent items are grouped into a single ChatResponseUpdate that shares the same MessageId. This means the AsAGUIEventStreamAsync method in ChatResponseUpdateAGUIExtensions.cs was emitting consecutive TOOL_CALL_RESULT events with identical messageId values, violating the AG-UI spec and causing client-side issues (e.g., in @ag-ui/core clients that key events by messageId).

Description

Root cause: In the AsAGUIEventStreamAsync method, the FunctionResultContentToolCallResultEvent mapping was directly assigning chatResponse.MessageId to each event's MessageId. When a single ChatResponseUpdate contained 2+ FunctionResultContent items (which is the normal case when the LLM executes multiple server-side tools in parallel), all resulting SSE events had the same messageId.

Fix (1 line change in source): Instead of using chatResponse.MessageId directly, the messageId is now composed deterministically as:

{chatResponse.MessageId!}_{functionResultContent.CallId}

This approach:

  • Satisfies AG-UI uniqueness — since CallId is unique per tool call, the composed messageId is unique per TOOL_CALL_RESULT event
  • Preserves traceability — the original MessageId is still embedded, enabling correlation back to the source ChatResponseUpdate
  • Is deterministic — the same inputs always produce the same messageId, supporting idempotent replays
  • Uses the null-forgiving operator (!) on chatResponse.MessageId! for consistency with other MessageId usages in this file (lines 362, 375, 464)

Tests added (4 new test cases):

Test Scenario
ConsecutiveToolCallResults_HaveDistinctMessageIds 2 FunctionResultContent in a single ChatResponseUpdate → asserts distinct messageId on each TOOL_CALL_RESULT event
ToolCallResultsFromSeparateUpdates_HaveDistinctMessageIds 2 FunctionResultContent in separate ChatResponseUpdate objects sharing the same MessageId → asserts distinct messageId
ThreeConsecutiveToolCallResults_AllHaveUniqueMessageIds 3 FunctionResultContent in one update → asserts all messageId values are unique via HashSet
SingleToolCallResult_HasValidMessageId 1 FunctionResultContent → asserts messageId is non-null and non-empty (regression guard)

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — the generated messageId format changes from msg1 to msg1_call_1, but this is a bug fix (the previous value was invalid per the AG-UI spec)

…nts (microsoft#3962)

When multiple FunctionResultContent items share the same ChatResponseUpdate, the AsAGUIEventStreamAsync method now generates a deterministic unique messageId by combining the original MessageId with the CallId (e.g. msg1_call_1, msg1_call_2), preserving traceability while satisfying the AG-UI protocol uniqueness constraint. Fixes microsoft#3962
Copilot AI review requested due to automatic review settings February 18, 2026 14:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@alliscode
Copy link
Member

Hi @kallebelins thanks for the PR. Could you fix the formatting issues so we can get this in?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: [Bug]: [AG-UI] MapAGUI reuses the same messageId for consecutive TOOL_CALL_RESULT SSE events

3 participants

Comments