Skip to content

ToolApprovalRequestContent.RequiresConfirmation causes MEAI001 in consumer AIContent source-generation contexts #7658

Description

@jeffhandley

ToolApprovalRequestContent.RequiresConfirmation causes MEAI001 in consumer AIContent source-generation contexts

Microsoft.Extensions.AI.Abstractions 10.8.0 introduced a source-generation regression for consumers that serialize AIContent.

ToolApprovalRequestContent is statically registered as a JsonDerivedType of AIContent and InputRequestContent. In 10.8.0, Add ToolApprovalRequestContent.RequiresConfirmation (#7549) added the serialized experimental member ToolApprovalRequestContent.RequiresConfirmation. As a result, a consumer need not use approvals—or reference RequiresConfirmation—to receive MEAI001: it is emitted from generated code whenever the consumer source-generates metadata for AIContent.

Minimal repro

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="10.8.0" />
</ItemGroup>
using Microsoft.Extensions.AI;
using System.Text.Json.Serialization;

[JsonSerializable(typeof(List<AIContent>))]
internal partial class JsonContext : JsonSerializerContext;

The build produces source-generated warnings:

warning MEAI001: 'Microsoft.Extensions.AI.ToolApprovalRequestContent.RequiresConfirmation'
is for evaluation purposes only and is subject to change or removal in future updates.

Consumers using source generation are forced to suppress MEAI001 at the project level as the only remedy. But that suppression does not remove the member from the generated contract: externally exposed JSON payloads still include the experimental requiresConfirmation field, despite the consumer never opting into the approval feature.

Expected behavior

Consumers that do not directly use experimental approval APIs should be able to source-generate AIContent metadata without receiving MEAI001.

Direct use of RequiresConfirmation should continue to require explicit experimental opt-in.

Fix serialization of [Experimental] AIContent-derived types (#6900) identified this same consumer source-generation boundary, but for experimental types: an experimental type cannot be a static JsonDerivedType member of AIContent, so it registered such types through the AIJsonUtilities.DefaultOptions runtime path instead of the static contract.

Stabilize CodeInterpreter and WebSearch content types (#7493) later removed that temporary runtime-registration pattern once the affected types became stable. The compile-time guard that remained (Microsoft.Extensions.AI.Stabilization.Tests, which treats MEAI001 as an error) only ever exercised individual stabilized types; it never source-generated a full AIContent consumer context. That is why an experimental member on an already-stable type was not caught.

How this slipped through

We had guards to prevent experimental types from leaking, but RequiresConfirmation is an experimental member on a stable, statically registered type. It has the same viral consequence: it leaks into every consumer-generated AIContent contract. This is not specific to polymorphism—any reachable experimental serialized member leaks the same way. UsageDetails' token counts and HostedFileContent.Purpose/Scope are non-polymorphic experimental members that already avoid it (see Proposed direction).

The regression symptom was visible in Add ToolApprovalRequestContent.RequiresConfirmation (#7549): it added NoWarn suppressions for MEAI001 to both Microsoft.Extensions.AI.Evaluation.Reporting and Microsoft.Extensions.AI.Evaluation.Reporting.Azure. The PR initially added the suppressions, removed them, and re-added them after RequiresConfirmation was marked experimental. Those project-level suppressions should have prompted investigation of why an experimental member was leaking into their serialization contracts.

Proposed direction

ToolApprovalRequestContent is a stable type; only its RequiresConfirmation member is experimental. So, unlike #6900's handling of experimental types, there is no need to remove the type from the static polymorphic contract. Instead, gate the experimental member at the member level—the pattern already used by UsageDetails' token-count properties (Add token-count details to UsageDetails (#7285)) and HostedFileContent.Purpose/Scope:

  • Mark the public RequiresConfirmation accessor [Experimental] and [JsonIgnore].
  • Route its get/set to a new non-experimental internal property ([JsonInclude], [JsonPropertyName("requiresConfirmation")], default true) that carries the JSON.
  • Keep the existing static JsonDerivedType registrations on AIContent and InputRequestContent (the type is stable and must remain in the polymorphic contract).

The internal member is only accessible in-assembly, so RequiresConfirmation continues to round-trip through the package's own serialization (AIJsonUtilities.DefaultOptions) while being silently dropped from a consumer's own source-generated AIContent context—no MEAI001, and no experimental requiresConfirmation field in their contract. Direct use of the public RequiresConfirmation still requires experimental opt-in.

For regression coverage, add a holistic, maintenance-free guard rather than a per-type test: a single no-suppression JsonSerializerContext in Microsoft.Extensions.AI.Stabilization.Tests (which treats MEAI001 as an error) rooted at the package's public serialization entry points—List<AIContent>, ChatResponse, ChatResponseUpdate, ChatOptions, Embedding. Because every Microsoft.Extensions.AI experiment shares the single MEAI001 id and new members hang off these existing roots, any future experimental member that leaks into a consumer-generated contract—polymorphic derived type or plain property alike—fails the build automatically.

This preserves RequiresConfirmation in the supported default serialization path while preventing it from appearing in consumer source-generated contracts.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions