Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@
<!-- Newtonsoft.Json -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.3" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.2" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.0" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.1" />
<PackageVersion Include="System.Text.Json" Version="10.0.2" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.2" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.3" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
Expand All @@ -61,21 +61,21 @@
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.2.0-preview.1.26063.2" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-5";
Expand All @@ -15,14 +14,10 @@
.AsIChatClient().AsBuilder()
.ConfigureOptions(o =>
{
o.RawRepresentationFactory = _ => new CreateResponseOptions()
o.Reasoning = new()
{
ReasoningOptions = new()
{
ReasoningEffortLevel = ResponseReasoningEffortLevel.Medium,
// Verbosity requires OpenAI verified Organization
ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed
}
Effort = ReasoningEffort.Medium,
Output = ReasoningOutput.Full,
};
}).Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

ChatMessage message = new(ChatRole.User, [
new TextContent("What do you see in this image?"),
new DataContent(File.ReadAllBytes("assets/walkway.jpg"), "image/jpeg")
await DataContent.LoadFromAsync("assets/walkway.jpg"),
]);

AgentSession session = await agent.CreateSessionAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,13 @@ public static async Task<ChatClientAgent> CreateAIAgentAsync(
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;
}
Expand Down Expand Up @@ -770,6 +775,36 @@ private static string ThrowIfInvalidAgentName(string? name)
}
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))]
Expand Down
52 changes: 13 additions & 39 deletions dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,15 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
}
});

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
Expand All @@ -245,7 +244,7 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
}
finally
{
CleanupTempFiles(tempFiles);
CleanupTempDir(tempDir);
}
}
finally
Expand Down Expand Up @@ -410,45 +409,23 @@ private AgentResponseUpdate ConvertToAgentResponseUpdate(SessionEvent sessionEve
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
{
Expand All @@ -460,19 +437,16 @@ private static string GetExtensionForMediaType(string? mediaType)
}
}

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
{
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ private async IAsyncEnumerable<ChatMessage> ProcessInputMessageAsync(ChatMessage
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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,8 +524,15 @@ public async Task RunAsync_MiddlewareThrowsPreInvocation_ExceptionSurfacesAsync(
{
// Arrange
var testFunction = AIFunctionFactory.Create(() => "Function result", "TestFunction", "A test function");
var functionCall = new FunctionCallContent("call_123", "TestFunction", new Dictionary<string, object?>());
var mockChatClient = CreateMockChatClientWithFunctionCalls(functionCall);
var mockChatClient = new Mock<IChatClient>();

mockChatClient.Setup(c => c.GetResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(() => new ChatResponse([
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("call_123", "TestFunction", new Dictionary<string, object?>())])
]));

var innerAgent = new ChatClientAgent(mockChatClient.Object);
var messages = new List<ChatMessage> { new(ChatRole.User, "Test message") };
Expand Down
Loading