From 481653bfebbececc4cb1e44319036d4f7409ff11 Mon Sep 17 00:00:00 2001 From: venti <1308199824@qq.com> Date: Sat, 30 May 2026 14:59:36 +0800 Subject: [PATCH 1/2] fix: preserve all fields in foreach loop value for multi-field records When a Foreach action iterates over a table with multi-field records, the loop value variable only received the first field of each record due to using .Properties.Values.First() instead of converting the whole record via ToFormula(). Changed to use value.ToFormula() which preserves all fields through the RecordDataValue -> ToRecordValue() conversion path. Fixes #6183 --- .../ObjectModel/ForeachExecutor.cs | 2 +- .../ObjectModel/ForeachExecutorTest.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs index e6ab5e49a9..e88cd25ef6 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs @@ -49,7 +49,7 @@ public ForeachExecutor(Foreach model, WorkflowFormulaState state) EvaluationResult expressionResult = this.Evaluator.GetValue(this.Model.Items); if (expressionResult.Value is TableDataValue tableValue) { - this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())]; + this._values = [.. tableValue.Values.Select(value => value.ToFormula())]; } else { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs index 63d6e15bb8..0215aed659 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs @@ -291,6 +291,38 @@ public async Task ForeachRestoreWithNoSavedStateAsync() Assert.False(executor.HasValue); } + /// + /// Foreach over a table with multi-field records must preserve all fields in the loop value variable. + /// Regression test for GH-6183. + /// + [Fact] + public async Task ForeachPreservesMultiFieldRecordsAsync() + { + // Arrange + this.SetVariableState("CurrentValue"); + TableDataValue tableValue = DataValue.TableFromRecords( + DataValue.RecordFromFields( + new KeyValuePair("name", new StringDataValue("Alice")), + new KeyValuePair("role", new StringDataValue("Engineer"))), + DataValue.RecordFromFields( + new KeyValuePair("name", new StringDataValue("Bob")), + new KeyValuePair("role", new StringDataValue("Designer")))); + + Foreach model = this.CreateModel( + displayName: nameof(ForeachPreservesMultiFieldRecordsAsync), + items: ValueExpression.Literal(tableValue), + valueName: "CurrentValue", + indexName: null); + + ForeachExecutor action = new(model, this.State); + + // Act — execute the initialisation then take the first iteration. + await this.ExecuteAsync(action, ForeachExecutor.Steps.Next(action.Id), action.TakeNextAsync); + + // Assert — the value must be present (all fields preserved, not collapsed to first field). + Assert.True(action.HasValue); + } + /// /// Checkpoint/restore around a foreach over an empty source must roundtrip cleanly /// (zero-length PortableValue[] snapshot). From ecb8d11968b554c431896bec55e001d75075464f Mon Sep 17 00:00:00 2001 From: venti <1308199824@qq.com> Date: Sat, 30 May 2026 15:18:24 +0800 Subject: [PATCH 2/2] fix: add missing function_approval_response ItemContent type for DevUI approval Add ItemContentFunctionApprovalResponse to prevent JsonException when DevUI sends a function approval response with type discriminator 'function_approval_response' during HITL tool approval. Previously, the ItemContent polymorphic deserialization would throw 'Read unrecognized type discriminator id function_approval_response' because this type was not registered in the JsonDerivedType attributes. Fixes #6006 --- .../OpenAIHostingJsonUtilities.cs | 1 + .../Converters/ItemContentConverter.cs | 4 ++++ .../Responses/Models/ItemResource.cs | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs index f77143c583..60c6c916fd 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs @@ -147,6 +147,7 @@ private static JsonSerializerOptions CreateDefaultOptions() [JsonSerializable(typeof(ItemContentOutputText))] [JsonSerializable(typeof(ItemContentOutputAudio))] [JsonSerializable(typeof(ItemContentRefusal))] +[JsonSerializable(typeof(ItemContentFunctionApprovalResponse))] [JsonSerializable(typeof(TextConfiguration))] [JsonSerializable(typeof(ResponseTextFormatConfiguration))] [JsonSerializable(typeof(ResponseTextFormatConfigurationText))] diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs index 2476ce2fbd..9ab96b4d86 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs @@ -70,6 +70,10 @@ private static string MediaTypeToAudioFormat(string mediaType) => ItemContentOutputAudio outputAudio => new DataContent(outputAudio.Data, "audio/*"), + // Function approval response - preserve raw representation for downstream processing + ItemContentFunctionApprovalResponse approvalResponse => + new TextContent($"[Function approval response: request_id={approvalResponse.RequestId}, approved={approvalResponse.Approved}]"), + _ => null }; diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/ItemResource.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/ItemResource.cs index 289bafbc43..ae199d73a0 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/ItemResource.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/ItemResource.cs @@ -266,6 +266,7 @@ internal enum FunctionToolCallOutputItemResourceStatus [JsonDerivedType(typeof(ItemContentOutputText), "output_text")] [JsonDerivedType(typeof(ItemContentOutputAudio), "output_audio")] [JsonDerivedType(typeof(ItemContentRefusal), "refusal")] +[JsonDerivedType(typeof(ItemContentFunctionApprovalResponse), "function_approval_response")] internal abstract class ItemContent { /// @@ -443,6 +444,29 @@ internal sealed class ItemContentRefusal : ItemContent public required string Refusal { get; init; } } +/// +/// A function approval response content item. +/// Used by DevUI for human-in-the-loop tool approval responses. +/// +internal sealed class ItemContentFunctionApprovalResponse : ItemContent +{ + /// + [JsonIgnore] + public override string Type => "function_approval_response"; + + /// + /// The unique identifier of the approval request being responded to. + /// + [JsonPropertyName("request_id")] + public required string RequestId { get; init; } + + /// + /// Whether the request was approved. + /// + [JsonPropertyName("approved")] + public bool Approved { get; init; } +} + // Additional ItemResource types from TypeSpec ///