.NET: Support reasoning events in AGUI#4953
Conversation
232e036 to
8056b71
Compare
@microsoft-github-policy-service agree company="Coremont" |
8056b71 to
8aff11b
Compare
a7c0134 to
01a8291
Compare
|
MEAI uses a As such 01a8291 creates a new GUID for reasoning content. Similarly, despite ReasoningStart and ReasoningEnd events being no-ops, the protocol suggests the mesage ID of these should be unique as well.
Note that the current implementation in this PR means there is no messageId link between a reasoning message and text message as the protocol does not explicitly specify any such relationship e.g. ReasoningMessage has a unique GUID instead of being This image from the docs suggests that RasoningStart/RasoningEnd be traced back to the related ReasoningMessage via the messageId, but Im wary of making such a link as there is nothing explicitly defined and could lead to a breaking change if we were to change this in the future.
Happy to update if I have misunderstood :) |
…they are part of the same logical model response. Create a new GUID for reasoning messages to be consistent with AGUI protocol and establish no link between reasoning and text messages
01a8291 to
47135b2
Compare
…sequent POST, any accumulated role: "reasoning" messages fail deserialization in AGUIMessageJsonConverter because the role wasn't handled - causing the request to fail. This adds AGUIReasoningMessage with Content and EncryptedValue properties, registers it in the JSON converter and serializer context, and converts it to TextReasoningContent (with ProtectedData) in AsChatMessages.
a47f3d9 to
2191383
Compare
…soningContent to AGUIReasoningMessage for c# client
|
@markwallace-microsoft @westey-m (tagging as I saw you merged some things in recently 🙂), I was wondering if you guys have any thoughts on this PR? It would be amazing to get this moving forward to support reasoning events, if you think the approach in the PR is tackling the issue in the right direction |
Thanks for the contribution @JeffinSiby. Definitely looks like something we should add. Added a couple of small comments. |
There was a problem hiding this comment.
Pull request overview
Adds first-class .NET support for AG-UI reasoning events and role: "reasoning" messages, wiring them into the existing AGUI <-> Microsoft.Extensions.AI conversions and JSON (AOT) serialization.
Changes:
- Introduces
REASONING_*event types, event models, and BaseEvent JSON converter support. - Extends streaming conversions:
AsAGUIEventStreamAsyncemits reasoning lifecycle + encrypted values;AsChatResponseUpdatesAsyncconsumes both lifecycle + chunk shorthand intoTextReasoningContent. - Adds
AGUIReasoningMessageand updates polymorphic message conversion/serialization to supportrole: "reasoning".
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/ChatResponseUpdateAGUIExtensionsTests.cs | Adds unit tests covering reasoning streaming in/out and invariants. |
| dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs | Adds serializer tests for reasoning messages/events and updates polymorphic array coverage. |
| dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs | Adds tests for reasoning message ↔ TextReasoningContent mapping. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningStartEvent.cs | New REASONING_START event model. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningMessageStartEvent.cs | New REASONING_MESSAGE_START event model. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningMessageContentEvent.cs | New REASONING_MESSAGE_CONTENT event model. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningMessageEndEvent.cs | New REASONING_MESSAGE_END event model. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningEndEvent.cs | New REASONING_END event model. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningMessageChunkEvent.cs | New chunk shorthand event model (REASONING_MESSAGE_CHUNK). |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningEncryptedValueEvent.cs | New encrypted payload event model (REASONING_ENCRYPTED_VALUE). |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs | Implements reasoning event emission/consumption and builder logic. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/BaseEventJsonConverter.cs | Adds read/write support for reasoning event discriminators. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIRoles.cs | Adds AGUIRoles.Reasoning. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIReasoningMessage.cs | Adds AGUIReasoningMessage with encryptedValue. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIMessageJsonConverter.cs | Adds polymorphic case for "reasoning" role. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs | Registers reasoning message + events for source-gen/AOT. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIEventTypes.cs | Adds REASONING_* event type constants. |
| dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIChatMessageExtensions.cs | Adds mapping between AGUIReasoningMessage and TextReasoningContent. |
|
@westey-m no worries at all and thank you! I've pushed the dotnet format changes. Let me know if anything else is needed. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Head branch was pushed to by a user without write access
|
@westey-m @rogerbarreto accidentally triggered copilot to review again resulting in the following commit to replace a hardcoded string 451eba1. Could I please get a re-review? |


Motivation and Context
Relates to the .NET support for reasoning mentioned in #2619 and parent issue #2558.
Description
7 new events based on agui spec https://docs.ag-ui.com/concepts/reasoning (note 'thinking' is decpriated and the convention now is 'reasoning' https://docs.ag-ui.com/concepts/reasoning#deprecated-events)
Outbound path (AsAGUIEventStreamAsync)
TextReasoningContent from the IChatClient pipeline is now converted into the explicit AG-UI reasoning lifecycle
(REASONING_START → REASONING_MESSAGE_START → REASONING_MESSAGE_CONTENT → REASONING_MESSAGE_END → REASONING_END).
ProtectedData is emitted as REASONING_ENCRYPTED_VALUE inline after the content delta. Content with only ProtectedData (no visible text) is not dropped - the block is opened and the encrypted value emitted without a content delta.
Inbound path (AsChatResponseUpdatesAsync)
A new ReasoningMessageBuilder (following the TextMessageBuilder pattern) handles both the explicit lifecycle form and the chunk shorthand form (REASONING_MESSAGE_CHUNK).
ReasoningStartEvent/ReasoningEndEvent are consumed as no-op bracket markers. The builder enforces the same invariants as TextMessageBuilder - overlapping starts and mismatched ends throw InvalidOperationException.
Request payload path (AsChatMessages)
A new
AGUIReasoningMessagehandlesrole: "reasoning"messages in the frontend's POST payload. Without this, multi-turn conversations with reasoning enabled would fail with 400 Bad Request on the second request, becauseAGUIMessageJsonConverterhad no case for the "reasoning" role discriminator. The message is converted toTextReasoningContentpreserving both visible text and the encrypted thinking token (ProtectedData).AsAGUIEventStreamAsyncTextReasoningContent→REASONING_*eventsAsChatResponseUpdatesAsyncREASONING_*events →TextReasoningContentAsChatMessagesAGUIReasoningMessage→TextReasoningContentAsAGUIMessagesTextReasoningContent→AGUIReasoningMessageContribution Checklist