Update to M.E.AI 10.3.0 (#3822)

Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
This commit is contained in:
Stephen Toub
2026-02-11 17:02:07 +00:00
committed by GitHub
co-authored by Roger Barreto
parent a427af91a9
commit b52136952f
7 changed files with 73 additions and 62 deletions
@@ -283,8 +283,13 @@ public static partial class AzureAIProjectChatClientExtensions
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
};
// Attempt to capture breaking glass options from the raw representation factory that match the agent definition.
if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions)
// Map reasoning options from the abstraction-level ChatOptions.Reasoning,
// falling back to extracting from the raw representation factory for breaking glass scenarios.
if (options.ChatOptions?.Reasoning is { } reasoning)
{
agentDefinition.ReasoningOptions = ToResponseReasoningOptions(reasoning);
}
else if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions)
{
agentDefinition.ReasoningOptions = respCreationOptions.ReasoningOptions;
}
@@ -770,6 +775,36 @@ public static partial class AzureAIProjectChatClientExtensions
}
return name;
}
private static ResponseReasoningOptions? ToResponseReasoningOptions(ReasoningOptions reasoning)
{
ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch
{
ReasoningEffort.Low => ResponseReasoningEffortLevel.Low,
ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium,
ReasoningEffort.High => ResponseReasoningEffortLevel.High,
ReasoningEffort.ExtraHigh => ResponseReasoningEffortLevel.High,
_ => null,
};
ResponseReasoningSummaryVerbosity? summary = reasoning.Output switch
{
ReasoningOutput.Summary => ResponseReasoningSummaryVerbosity.Concise,
ReasoningOutput.Full => ResponseReasoningSummaryVerbosity.Detailed,
_ => null,
};
if (effortLevel is null && summary is null)
{
return null;
}
return new ResponseReasoningOptions
{
ReasoningEffortLevel = effortLevel,
ReasoningSummaryVerbosity = summary,
};
}
}
[JsonSerializable(typeof(JsonElement))]
@@ -217,16 +217,15 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
}
});
List<string> tempFiles = [];
string? tempDir = null;
try
{
// Build prompt from text content
string prompt = string.Join("\n", messages.Select(m => m.Text));
// Handle DataContent as attachments
List<UserMessageDataAttachmentsItem>? attachments = await ProcessDataContentAttachmentsAsync(
(List<UserMessageDataAttachmentsItem>? attachments, tempDir) = await ProcessDataContentAttachmentsAsync(
messages,
tempFiles,
cancellationToken).ConfigureAwait(false);
// Send the message with attachments
@@ -245,7 +244,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
}
finally
{
CleanupTempFiles(tempFiles);
CleanupTempDir(tempDir);
}
}
finally
@@ -410,45 +409,23 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
return new SessionConfig { Tools = mappedTools, SystemMessage = systemMessage };
}
private static readonly Dictionary<string, string> s_mediaTypeExtensions = new(StringComparer.OrdinalIgnoreCase)
{
["image/png"] = ".png",
["image/jpeg"] = ".jpg",
["image/jpg"] = ".jpg",
["image/gif"] = ".gif",
["image/webp"] = ".webp",
["image/svg+xml"] = ".svg",
["text/plain"] = ".txt",
["text/html"] = ".html",
["text/markdown"] = ".md",
["application/json"] = ".json",
["application/xml"] = ".xml",
["application/pdf"] = ".pdf"
};
private static string GetExtensionForMediaType(string? mediaType)
{
return mediaType is not null && s_mediaTypeExtensions.TryGetValue(mediaType, out string? extension) ? extension : ".dat";
}
private static async Task<List<UserMessageDataAttachmentsItem>?> ProcessDataContentAttachmentsAsync(
private static async Task<(List<UserMessageDataAttachmentsItem>? Attachments, string? TempDir)> ProcessDataContentAttachmentsAsync(
IEnumerable<ChatMessage> messages,
List<string> tempFiles,
CancellationToken cancellationToken)
{
List<UserMessageDataAttachmentsItem>? attachments = null;
string? tempDir = null;
foreach (ChatMessage message in messages)
{
foreach (AIContent content in message.Contents)
{
if (content is DataContent dataContent)
{
// Write DataContent to a temp file
string tempFilePath = Path.Combine(Path.GetTempPath(), $"agentframework_copilot_data_{Guid.NewGuid()}{GetExtensionForMediaType(dataContent.MediaType)}");
await File.WriteAllBytesAsync(tempFilePath, dataContent.Data.ToArray(), cancellationToken).ConfigureAwait(false);
tempFiles.Add(tempFilePath);
tempDir ??= Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), $"af_copilot_{Guid.NewGuid():N}")).FullName;
string tempFilePath = await dataContent.SaveToAsync(tempDir, cancellationToken).ConfigureAwait(false);
// Create attachment
attachments ??= [];
attachments.Add(new UserMessageDataAttachmentsItem
{
@@ -460,19 +437,16 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
}
}
return attachments;
return (attachments, tempDir);
}
private static void CleanupTempFiles(List<string> tempFiles)
private static void CleanupTempDir(string? tempDir)
{
foreach (string tempFile in tempFiles)
if (tempDir is not null)
{
try
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
Directory.Delete(tempDir, recursive: true);
}
catch
{
@@ -304,7 +304,7 @@ internal sealed class WorkflowRunner
ChatMessage? responseMessage =
requestItem switch
{
FunctionCallContent functionCall => await InvokeFunctionAsync(functionCall).ConfigureAwait(false),
FunctionCallContent functionCall when !functionCall.InformationalOnly => await InvokeFunctionAsync(functionCall).ConfigureAwait(false),
FunctionApprovalRequestContent functionApprovalRequest => ApproveFunction(functionApprovalRequest),
McpServerToolApprovalRequestContent mcpApprovalRequest => ApproveMCP(mcpApprovalRequest),
_ => HandleUnknown(requestItem),