Skip to content

.NET: [Bug]: AIContent with only RawRepresentation serializes as empty objects in shared JSON paths #4633

@cgillum

Description

@cgillum

Description

We first observed this when testing Durable Agents with the GitHubCopilot agent type. Upon further analysis, however, this appears to be a broader .NET serialization bug.

When a ChatMessage contains a plain AIContent instance whose only useful data is in RawRepresentation, shared JSON serialization paths can serialize that content as an empty object {}.

This is the same underlying behavior that likely surfaces in #4481 for Durable Agents, but it also reproduces outside Durable Agents.

Simplified example

I reproduced the same behavior through ChatClientAgentContinuationToken, which serializes inputMessages using AgentJsonUtilities.DefaultOptions:

  • dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs:42-47
  • dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs

A ChatMessage containing:

new ChatMessage(ChatRole.Assistant,
[
    new AIContent
    {
        RawRepresentation = new { kind = "toolCall", name = "read_file", callId = "abc123" }
    }
])

round-trips to JSON like:

{"role":"assistant","contents":[{}]}

I also noticed that workflows use a similar shared ChatMessage serialization path in WorkflowsJsonUtilities:

  • dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs:36-40

So the underlying issue seems to be that opaque/base AIContent loses debuggable structure when serialized through shared JSON utilities, and the information is lost when deserializing the saved conversation state.

GitHub Copilot agent context

This is especially relevant for GitHub Copilot agents, which appear to rely on this pattern today.

In GitHubCopilotAgent, some assistant-side events are converted into plain AIContent instances with only RawRepresentation populated instead of being projected to richer content types:

  • dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs:349-356
  • dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs:418-426

Specifically:

  • AssistantMessageEvent is wrapped as new AIContent { RawRepresentation = assistantMessage }
  • SessionEvent is wrapped as new AIContent { RawRepresentation = sessionEvent }

Expected Behavior

One of the following:

  1. First party agent implementations should not use the generic AIContent types.
  2. If an AIContent instance cannot be projected to a richer known subtype, it should still serialize with some useful metadata instead of {}.

Examples of acceptable behavior:

  • serialize a lightweight envelope with a discriminator plus metadata such as raw type / name / callId
  • preserve a small subset of RawRepresentation for debuggability
  • convert known opaque content to a more explicit content type before serialization

Actual Behavior

Shared JSON serialization paths can emit contents: [{}], which loses all useful structure and makes state, logs, continuation tokens, and other persisted artifacts impossible to interpret.

Code Sample

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

ResponseContinuationToken inner = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 });
ChatClientAgentContinuationToken token = new(inner)
{
    InputMessages =
    [
        new ChatMessage(ChatRole.Assistant,
        [
            new AIContent
            {
                RawRepresentation = new { kind = "toolCall", name = "read_file", callId = "abc123" }
            }
        ])
    ]
};

ChatClientAgentContinuationToken roundTrip =
    ChatClientAgentContinuationToken.FromToken(ResponseContinuationToken.FromBytes(token.ToBytes()));

Observed serialized shape inside the token:

{"type":"chatClientAgentContinuationToken","innerToken":"\"AQID\"","inputMessages":"[{\"role\":\"assistant\",\"contents\":[{}]}]"}

Error Messages / Stack Traces

No exception is thrown. The issue is silent data loss / loss of debuggable structure during serialization.

Package Versions

  • Microsoft.Agents.AI.DurableTask: 1.0.0-preview.260225.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions