Skip to content

.NET: Support reasoning events in AGUI#4953

Merged
westey-m merged 15 commits intomicrosoft:mainfrom
jeffinsibycoremont:feature/support-reasoning
May 7, 2026
Merged

.NET: Support reasoning events in AGUI#4953
westey-m merged 15 commits intomicrosoft:mainfrom
jeffinsibycoremont:feature/support-reasoning

Conversation

@jeffinsibycoremont
Copy link
Copy Markdown

@jeffinsibycoremont jeffinsibycoremont commented Mar 27, 2026

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)

  • ReasoningStartEvent
  • ReasoningMessageStartEvent
  • ReasoningMessageContentEvent
  • ReasoningMessageEndEvent
  • ReasoningEndEvent
  • ReasoningMessageChunkEvent
  • ReasoningEncryptedValueEvent

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 AGUIReasoningMessage handles role: "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, because AGUIMessageJsonConverter had no case for the "reasoning" role discriminator. The message is converted to TextReasoningContent preserving both visible text and the encrypted thinking token (ProtectedData).

Path Direction Method Converts
Events (SSE streaming) Server → Client (produce) AsAGUIEventStreamAsync TextReasoningContentREASONING_* events
Events (SSE streaming) Client → Server (consume) AsChatResponseUpdatesAsync REASONING_* events → TextReasoningContent
Messages (POST body) Server ← Client (receive) AsChatMessages AGUIReasoningMessageTextReasoningContent
Messages (POST body) Client → Server (send) AsAGUIMessages TextReasoningContentAGUIReasoningMessage

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? If yes, add "[BREAKING]" prefix to the title of the PR.

@github-actions github-actions Bot changed the title Support reasoning .NET: Support reasoning Mar 27, 2026
@jeffinsibycoremont jeffinsibycoremont marked this pull request as draft March 27, 2026 17:35
@jeffinsibycoremont jeffinsibycoremont force-pushed the feature/support-reasoning branch 2 times, most recently from 232e036 to 8056b71 Compare March 27, 2026 18:11
@jeffinsibycoremont
Copy link
Copy Markdown
Author

@jeffinsibycoremont please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree company="Coremont"

@jeffinsibycoremont jeffinsibycoremont force-pushed the feature/support-reasoning branch from 8056b71 to 8aff11b Compare March 27, 2026 18:30
@jeffinsibycoremont jeffinsibycoremont marked this pull request as ready for review March 27, 2026 18:31
@jeffinsibycoremont jeffinsibycoremont changed the title .NET: Support reasoning .NET: Support reasoning events in AGUI Mar 27, 2026
@jeffinsibycoremont jeffinsibycoremont force-pushed the feature/support-reasoning branch 2 times, most recently from a7c0134 to 01a8291 Compare March 30, 2026 17:01
@jeffinsibycoremont
Copy link
Copy Markdown
Author

jeffinsibycoremont commented Mar 30, 2026

MEAI uses a MessageId in ChatResponseUpdate. ChatResponseUpdates contents can include TextReasoningContent. The MessageId emitted for reasoning events should be a 'unique identifier for the reasoning message' as per https://docs.ag-ui.com/concepts/reasoning and others such as the typescript ag-ui sdk depend on the IDs being unique https://github.com/ag-ui-protocol/ag-ui/blob/bd5f93f29b21ad150d671acb3e22db4c5b1dbd74/sdks/typescript/packages/client/src/apply/default.ts#L920.

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.

image

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 reasoning-${chatResponse.MessageId}.

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.

image

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
…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.
@jeffinsibycoremont jeffinsibycoremont force-pushed the feature/support-reasoning branch from a47f3d9 to 2191383 Compare April 1, 2026 12:56
…soningContent to AGUIReasoningMessage for c# client
@JeffinSiby
Copy link
Copy Markdown

@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

@westey-m
Copy link
Copy Markdown
Contributor

westey-m commented Apr 9, 2026

@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.
I've also asked Copilot to review. Feel free to disagree with it's comments if they don't make sense.

Copy link
Copy Markdown
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.

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: AsAGUIEventStreamAsync emits reasoning lifecycle + encrypted values; AsChatResponseUpdatesAsync consumes both lifecycle + chunk shorthand into TextReasoningContent.
  • Adds AGUIReasoningMessage and updates polymorphic message conversion/serialization to support role: "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 westey-m self-assigned this Apr 10, 2026
@jeffinsibycoremont
Copy link
Copy Markdown
Author

@westey-m no worries at all and thank you! I've pushed the dotnet format changes. Let me know if anything else is needed.

Copy link
Copy Markdown
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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ReasoningMessageStartEvent.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
auto-merge was automatically disabled May 7, 2026 08:07

Head branch was pushed to by a user without write access

@jeffinsibycoremont
Copy link
Copy Markdown
Author

@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?

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

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants