Problem
Three interlocked bugs in the AGUI translation layer prevent parallel tool calls from rendering correctly in AG-UI protocol clients (@ag-ui/client, CopilotKit, etc.).
Bug 1: ParentMessageId collision on parallel tool calls
A single synthetic GUID (streamingMessageId) is reused as ParentMessageId on every ToolCallStartEvent across the entire SSE stream. OpenAI omits MessageId on FunctionCallContent chunks, so the fallback engages on every tool-call chunk — all parallel tool calls collapse into one assistant-message card in the frontend.
Bug 2: ToolCallResultEvent.MessageId collision
MEAI's FunctionInvokingChatClient packs all FunctionResultContent items from a single round trip into one ChatResponseUpdate with one synthetic MessageId. Reusing that ID on each emitted ToolCallResultEvent causes the frontend to overwrite results — only the first renders.
PR #5800 attempted to fix this with a ContainsToolResult branch, but it is gated by IsNullOrWhiteSpace(chatResponse.MessageId). Since MEAI prefills MessageId (non-empty), the branch is skipped entirely in the production path.
Bug 3: AsChatMessages doesn't coalesce consecutive assistant-tool-call messages
Once Bug 1 is fixed (empty ParentMessageId), the frontend produces a separate AGUIAssistantMessage per tool call. On multi-turn replay, these become consecutive assistant(tool_calls) messages without intervening tool messages — Azure OpenAI rejects with HTTP 400: "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'"
Why all three must be fixed together
Reproduction
Independently confirmed via console repro app feeding synthetic MEAI-shaped data through AsAGUIEventStreamAsync and AsChatMessages. All three bugs reproduce on current main (post-PR #5800).
Affected files
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIChatMessageExtensions.cs
Problem
Three interlocked bugs in the AGUI translation layer prevent parallel tool calls from rendering correctly in AG-UI protocol clients (@ag-ui/client, CopilotKit, etc.).
Bug 1: ParentMessageId collision on parallel tool calls
A single synthetic GUID (
streamingMessageId) is reused asParentMessageIdon everyToolCallStartEventacross the entire SSE stream. OpenAI omitsMessageIdonFunctionCallContentchunks, so the fallback engages on every tool-call chunk — all parallel tool calls collapse into one assistant-message card in the frontend.Bug 2: ToolCallResultEvent.MessageId collision
MEAI's
FunctionInvokingChatClientpacks allFunctionResultContentitems from a single round trip into oneChatResponseUpdatewith one syntheticMessageId. Reusing that ID on each emittedToolCallResultEventcauses the frontend to overwrite results — only the first renders.PR #5800 attempted to fix this with a
ContainsToolResultbranch, but it is gated byIsNullOrWhiteSpace(chatResponse.MessageId). Since MEAI prefillsMessageId(non-empty), the branch is skipped entirely in the production path.Bug 3: AsChatMessages doesn't coalesce consecutive assistant-tool-call messages
Once Bug 1 is fixed (empty
ParentMessageId), the frontend produces a separateAGUIAssistantMessageper tool call. On multi-turn replay, these become consecutiveassistant(tool_calls)messages without intervening tool messages — Azure OpenAI rejects with HTTP 400:"An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'"Why all three must be fixed together
Reproduction
Independently confirmed via console repro app feeding synthetic MEAI-shaped data through
AsAGUIEventStreamAsyncandAsChatMessages. All three bugs reproduce on current main (post-PR #5800).Affected files
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.csdotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIChatMessageExtensions.cs