Description
ImageGeneratingChatClient.RequestState.ReplaceImageGenerationFunctionResults() can duplicate every AIContent item that appears before a matched image-generation function item and silently drop every item that appears after it.
The affected items are not limited to FunctionCallContent or FunctionResultContent. Any AIContent subtype can be duplicated or dropped based on its position, including UsageContent, TextContent, TextReasoningContent, DataContent, and UriContent.
This can cause duplicate tool-result events, inaccurate token usage, missing MCP results, and incomplete text or reasoning content. No exception is thrown.
Reproduction Steps
Use an ImageGeneratingChatClient wrapping a FunctionInvokingChatClient:
builder
.UseImageGeneration(imageGenerator)
.UseFunctionInvocation(loggerFactory);
Produce a response update whose Contents contains unrelated content on both sides of an image-generation function result:
[A, ImageGenerationFunctionResult, B]
For example:
[
FunctionResultContent("mcp-call", mcpResult),
FunctionResultContent("image-call", imageResult),
UsageContent(...)
]
The relevant implementation behaves approximately as follows:
for (int index = contents.Count - 1; index >= 0; index--)
{
AIContent content = contents[index];
if (IsImageGenerationFunctionContent(content))
{
if (list is null)
{
list = CopyList(contents, index);
}
list.Add(ConvertImageGenerationContent(content));
}
else
{
list?.Add(content);
}
}
Items after the image-generation item are visited while list is null and are therefore discarded. When the image-generation item is encountered, CopyList(contents, index) copies all preceding items. The reverse loop then visits those same preceding indexes and appends them a second time.
Expected behavior
Only the matched image-generation function call or result should be replaced. All unrelated content should retain its original order and cardinality:
[A, ImageGenerationToolResultContent, B]
Actual behavior
The preceding content is duplicated and the following content is dropped:
[A, ImageGenerationToolResultContent, A]
A concrete observed sequence was:
[
FunctionResultContent(CallId = "mcp-call"),
ImageGenerationToolResultContent,
FunctionResultContent(CallId = "mcp-call")
]
If UsageContent or another AIContent item follows the image-generation result, it is silently absent from the returned list.
Regression?
Unknown.
Known Workarounds
Deduplicating FunctionResultContent by CallId downstream can suppress duplicate tool-result events, but it cannot restore content that has already been dropped. There is currently no complete downstream workaround.
Configuration
- Microsoft.Extensions.AI: 10.7.0
- Microsoft.Extensions.AI.Abstractions: 10.7.0
- .NET 10
- macOS
- Blazor: not applicable
Other information
The issue appears to be in ImageGeneratingChatClient.RequestState.ReplaceImageGenerationFunctionResults().
The method scans the list in reverse, creates the replacement list by copying only the prefix before the first matched image-generation item, and then continues scanning that already-copied prefix. A forward-order replacement or cloning the full list and replacing matched indexes in place would preserve unrelated content.
Suggested regression coverage:
- Image-generation content at the beginning
- Image-generation content in the middle
- Image-generation content at the end
- Multiple unrelated AIContent subtypes before and after the matched item
- Both FunctionCallContent and FunctionResultContent image-generation conversions
ImageGeneratingChatClient is currently marked with the experimental diagnostic MEAI001.
Description
ImageGeneratingChatClient.RequestState.ReplaceImageGenerationFunctionResults() can duplicate every AIContent item that appears before a matched image-generation function item and silently drop every item that appears after it.
The affected items are not limited to FunctionCallContent or FunctionResultContent. Any AIContent subtype can be duplicated or dropped based on its position, including UsageContent, TextContent, TextReasoningContent, DataContent, and UriContent.
This can cause duplicate tool-result events, inaccurate token usage, missing MCP results, and incomplete text or reasoning content. No exception is thrown.
Reproduction Steps
Use an ImageGeneratingChatClient wrapping a FunctionInvokingChatClient:
Produce a response update whose Contents contains unrelated content on both sides of an image-generation function result:
For example:
The relevant implementation behaves approximately as follows:
Items after the image-generation item are visited while list is null and are therefore discarded. When the image-generation item is encountered, CopyList(contents, index) copies all preceding items. The reverse loop then visits those same preceding indexes and appends them a second time.
Expected behavior
Only the matched image-generation function call or result should be replaced. All unrelated content should retain its original order and cardinality:
Actual behavior
The preceding content is duplicated and the following content is dropped:
A concrete observed sequence was:
If UsageContent or another AIContent item follows the image-generation result, it is silently absent from the returned list.
Regression?
Unknown.
Known Workarounds
Deduplicating FunctionResultContent by CallId downstream can suppress duplicate tool-result events, but it cannot restore content that has already been dropped. There is currently no complete downstream workaround.
Configuration
Other information
The issue appears to be in ImageGeneratingChatClient.RequestState.ReplaceImageGenerationFunctionResults().
The method scans the list in reverse, creates the replacement list by copying only the prefix before the first matched image-generation item, and then continues scanning that already-copied prefix. A forward-order replacement or cloning the full list and replacing matched indexes in place would preserve unrelated content.
Suggested regression coverage:
ImageGeneratingChatClient is currently marked with the experimental diagnostic MEAI001.