Skip to content

.NET: Add support for file content messages in AG-UI #6951

Closed
ElderJames wants to merge 2 commits into
microsoft:mainfrom
ElderJames:feat/agui/filecontent
Closed

.NET: Add support for file content messages in AG-UI #6951
ElderJames wants to merge 2 commits into
microsoft:mainfrom
ElderJames:feat/agui/filecontent

Conversation

@ElderJames

Copy link
Copy Markdown

Motivation & Context

This commit is required to correctly support AG-UI message payloads that contain mixed structured content, not just plain-string content.

It solves the limitation where AG-UI messages were treated as single text payloads, which prevented reliable round-trip handling for multi-part content such as text + media/data blocks.

The change enables richer message flows and serialization compatibility for scenarios involving document/image/audio/video blocks, URI/data payloads, hosted file references, and mixed content in both directions (AGUIMessage ↔ ChatMessage).

Description & Review Guide

This change introduces first-class AG-UI content-block modeling and dual-direction serialization support for mixed message contents.

  • What are the major changes?

    • Added content-block data model and JSON converter:
      • New AGUIMessageContent, AGUIMessageContentBlock, and AGUIMessageContentSource in AGUIMessageContent.cs.
      • New AGUIMessageContentJsonConverter to serialize/deserialize content as either:
        • single string (legacy text path), or
        • array of content blocks (new structured path).
    • Updated AGUIMessage.Content type from string to AGUIMessageContent (AGUIMessage.cs).
    • Extended AGUIJsonSerializerContext registrations to include new content-block types for source-generation serializers.
    • Enhanced AGUIChatMessageExtensions with:
      • conversion from AGUIMessage to ChatMessage for block-based content,
      • conversion from ChatMessage to AGUIMessage producing content blocks,
      • explicit handling for TextContent, UriContent, DataContent, HostedFileContent, tool results/calls, and assistant message edge cases,
      • support for mapping media types and source modes (url, data, hosted file id).
    • Added/updated tests:
      • AGUIChatMessageExtensionsTests
      • AGUIJsonSerializerContextTests
      • coverage for mixed/blocked content conversion and JSON round-trip validation.
  • What is the impact of these changes?

    • Enables structured AG-UI payloads for mixed media/text content across request/response boundaries.
    • Improves interoperability and correctness of AG-UI serialization/deserialization in .NET.
    • Keeps behavior compatible when a message is pure text by preserving text-only optimization paths.
    • Expands test coverage for serialization and conversion paths, reducing regressions around content handling.
  • What do you want reviewers to focus on?

    • AGUIMessage.Content type migration impact: verify all call paths in AGUI conversion flow expect AGUIMessageContent and not raw strings.
    • Converter behavior in AGUIMessageContentJsonConverter (string vs block array parsing) for backward compatibility.
    • Mapping rules in AGUIChatMessageExtensions for:
      • assistant messages with tool calls + additional text content,
      • mixed content (TextContent + UriContent/DataContent),
      • media block type resolution (image/audio/video/document/file/binary).
    • Test expectations in both conversion and serializer-context tests to ensure both directions remain stable.

Related Issue

Fixes #6931

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI review requested due to automatic review settings July 7, 2026 09:51
@giles17 giles17 added the .NET Usage: [Issues, PRs], Target: .Net label Jul 7, 2026
@ElderJames

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 support in the .NET AG-UI implementation for messages whose content can be either a legacy string or a structured array of content blocks (e.g., mixed text + file/media blocks), enabling richer round-tripping between AGUIMessage and ChatMessage.

Changes:

  • Introduces AGUIMessageContent + block/source models and a JSON converter to support content as either a string or an array of blocks.
  • Updates AG-UI ↔ chat conversions to map block-based content to/from AIContent types (e.g., TextContent, UriContent, DataContent, HostedFileContent) and preserves tool-call batching behavior.
  • Extends source-generated JSON serializer registrations and adds unit tests + a sample HTTP request demonstrating block content.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIMessageContent.cs Adds the structured content + block/source model and string/block behavior.
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIMessageContentJsonConverter.cs Implements content polymorphic JSON (string vs block array).
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIMessage.cs Switches AGUIMessage.Content from string to AGUIMessageContent.
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs Registers new content types for source-generated serialization.
dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIChatMessageExtensions.cs Adds mixed-content conversion between AG-UI messages and ChatMessage contents.
dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs Adds serializer tests covering block-array content and adjusts round-trip assertions.
dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs Adds conversion tests for block-based content.
dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.http Adds an example request demonstrating file/data block + text in one message.

Comment on lines +481 to +499
if (string.Equals(sourceType, "url", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrWhiteSpace(sourceValue))
{
aiContent = new UriContent(sourceValue!, mediaType);
return true;
}

if (string.Equals(sourceType, "data", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrWhiteSpace(sourceData))
{
try
{
aiContent = new DataContent(Convert.FromBase64String(sourceData), mediaType);
return true;
}
catch (FormatException)
{
}
}
Comment on lines +51 to +55
public override string ToString() => this.IsText
? this.Text
: this.Blocks is null
? string.Empty
: string.Concat(this.Blocks.Where(block => string.Equals(block.Type, "text", StringComparison.OrdinalIgnoreCase)).Select(block => block.Text ?? string.Empty));
Comment on lines +293 to +299
[Fact]
public void AsChatMessages_WithContentBlocks_ConvertsToChatContents()
{
// Arrange
const string ImageUrl = "https://example.com/image.png";
AGUIMessageContent userContent = new(
new[]
@Kumima

Kumima commented Jul 7, 2026

Copy link
Copy Markdown

AG-UI hosting structure is under big change, see:#6653

@rogerbarreto rogerbarreto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ElderJames Thanks for the contribution! The direction is solid: AG-UI multimodal content maps cleanly onto the MEAI content types, and the bidirectional approach in AGUIChatMessageExtensions is the right place for it. A few things to align with the AG-UI spec and the existing Python adapter before this is ready.

Spec reference

Using the canonical ag-ui-protocol schema (sdks/python/ag_ui/core/types.py):

  • Only UserMessage.content is Union[str, List[InputContent]]. developer, system, assistant, tool, reasoning content is a plain string.
  • Canonical media parts (image/audio/video/document) carry only type, source, and optional metadata. The flat url/data/id/filename/mimeType fields exist only on the deprecated binary part.
  • source is {type:"data", value:<base64>, mimeType} (mimeType required) or {type:"url", value, mimeType?}.

Blocking

  1. DataContent emits base64 into the block url field. BuildMediaContentBlock always sets Url = value, and for the DataContent branch value is the base64 string. The result is a block with "url": "<base64...>", which is malformed. The correct payload is already in source.value, so url should not be set for data sources.

  2. HostedFileContent emits type:"document" with source: null. Per spec DocumentInputContent.source is required, so this is non-conformant. An id-only reference should use the deprecated binary shape (which allows id) or be resolved to a real source. The Python adapter maps a binary id to ag-ui://binary/{id}; matching that keeps the two languages consistent.

Please consider

  1. Content widened to all roles. AGUIMessage.Content becomes AGUIMessageContent for every role, but only user messages may carry block arrays. Text-only messages collapse back to a string so the common path is fine, but the outbound path can still emit arrays for non-user roles. Suggest constraining block-array emission to the user role.

  2. Canonical vs deprecated shape mixing. Emitting type:"image"/"document" with extra top-level url/mimeType/filename blends the canonical and deprecated-binary shapes. The Python SDK tolerates it (extra="allow"), but stricter SDKs (TypeScript zod, Rust) may reject it. Prefer source-only media parts.

  3. Align with the Python reference adapter. python/packages/ag-ui/.../_message_adapters.py already handles this end to end (source-type aliases uri/base64, mime_type vs mimeType, data: URI passthrough, binary-id handling). Reusing its semantics avoids cross-language drift.

Docs

ADR 0010-ag-ui-support.md states the original scope was core text streaming with no custom content types. This PR expands the content model, so a short ADR update noting the multimodal content-block addition would be good.

Round-trip tests for DataContent and HostedFileContent (not just the url/image case) would catch issues 1 and 2.

Nice to have

A small runnable end-to-end .NET sample showing a multimodal message (for example an image or document upload flowing through to the agent) would help users adopt this. Not a blocker, and note the Python package does not have one either, so this is a suggestion rather than a parity requirement.

@ElderJames

Copy link
Copy Markdown
Author

AG-UI hosting structure is under big change, see:#6653

So, does this Pull Request still need to be pushed forward? @Kumima @rogerbarreto

@rogerbarreto

rogerbarreto commented Jul 7, 2026

Copy link
Copy Markdown
Member

@ElderJames, I don't think so, as well pointed by @Kumima, I think the official package now is the source of support for AGUI, feel free to close this PR. Thanks!

We will move forward bringing the change proposed in the PR below into MAF.

@ElderJames

Copy link
Copy Markdown
Author

Ok, thanks.

@ElderJames ElderJames closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET Usage: [Issues, PRs], Target: .Net

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: [Feature]: Send file attachments and text together in one AG-UI message

5 participants