From 650a458eb5bccfb58e3d66ef1e81e4317020f7c3 Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 1 Jun 2026 12:29:13 -0500 Subject: [PATCH 1/2] Fix: OutputConverter.FunctionResultContent creates OutputItemFunctionToolCallOutput with null id - Root cause: 2-arg constructor leaves Id property null (get-only) - Solution: Use ResponseEventStream.OutputItemFunctionCallOutput convenience method - Impact: Prevents HTTP 500 on function_call_output persistence - Ref: issue microsoft/agent-framework (filed separately) --- .../OutputConverter.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs index fe16edeb3b5..4efaa5bd628 100644 --- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/OutputConverter.cs @@ -281,14 +281,12 @@ public static async IAsyncEnumerable ConvertUpdatesToEvents var outputText = EncodeFunctionResultAsJsonStringPayload(functionResult.Result); - var itemId = GenerateItemId("fc"); - var outputItem = new OutputItemFunctionToolCallOutput( + foreach (var evt in stream.OutputItemFunctionCallOutput( functionResult.CallId, - BinaryData.FromString(outputText)); - - var outputBuilder = stream.AddOutputItem(itemId); - yield return outputBuilder.EmitAdded(outputItem); - yield return outputBuilder.EmitDone(outputItem); + BinaryData.FromString(outputText))) + { + yield return evt; + } break; } From e865a9e10899d5f1486c555a23f36b3e4680ce38 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 2 Jun 2026 07:41:29 -0500 Subject: [PATCH 2/2] =?UTF-8?q?test:=20K-07=20regression=20guard=20?= =?UTF-8?q?=E2=80=94=20FunctionResultContent=20output=20item=20must=20have?= =?UTF-8?q?=20non-null=20Id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a unit test that verifies the OutputItemFunctionToolCallOutput emitted by the FunctionResultContent case has a non-null, non-empty Id with the correct 'fco_' prefix. Regression guard for issue #6245: the old code used the 2-arg constructor (OutputItemFunctionToolCallOutput(callId, output)), which leaves the Id property null (get-only). A null id causes the Responses API storage layer to reject the function_call_output with HTTP 500. The fix uses the convenience method ResponseEventStream.OutputItemFunctionCallOutput(), which calls IdGenerator.NewFunctionCallOutputItemId() internally to produce a proper id. --- .../OutputConverterTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs index 4103517a10c..fe7df3127f4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs @@ -704,6 +704,34 @@ public async Task ConvertUpdatesToEventsAsync_FunctionResultJsonElementArrayPayl Assert.Equal("[{\"id\":1}]", inner); } + // K-07: Regression guard for issue #6245. + // The old code used the 2-arg OutputItemFunctionToolCallOutput constructor, which leaves the + // Id property null (get-only). A null id causes the Responses API storage layer to reject the + // function_call_output with HTTP 500. The fix uses the ResponseEventStream convenience method + // OutputItemFunctionCallOutput(), which generates a proper "fco_..." id via IdGenerator. + [Fact] + public async Task ConvertUpdatesToEventsAsync_FunctionResultContent_EmittedOutputItemHasNonNullIdAsync() + { + var (stream, _) = CreateTestStream(); + var update = new AgentResponseUpdate { Contents = [new FunctionResultContent("call_1", "tool result")] }; + + var events = new List(); + await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(new[] { update }), stream)) + { + events.Add(evt); + } + + var added = Assert.Single(events.OfType()); + var output = Assert.IsType(added.Item); + // Id must not be null — a null id causes HTTP 500 on persistence. + Assert.NotNull(output.Id); + Assert.NotEmpty(output.Id); + // The convenience method uses IdGenerator.NewFunctionCallOutputItemId(), which produces + // ids with the "fco_" prefix. Verify the correct prefix is used (not "fc_", which was + // the wrong prefix used by the old GenerateItemId("fc") call). + Assert.StartsWith("fco_", output.Id); + } + // L-01 [Fact] public async Task ConvertUpdatesToEventsAsync_ExecutorInvokedEvent_EmitsWorkflowActionItemAsync()