diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs index 07c7103c1a..88a2b1d610 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs @@ -32,4 +32,11 @@ builder.Services.AddFoundryResponses(agent); var app = builder.Build(); app.MapFoundryResponses(); + +// In Development, also map the OpenAI-compatible route that AIProjectClient uses. +if (app.Environment.IsDevelopment()) +{ + app.MapFoundryResponses("openai/v1"); +} + app.Run(); diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/Program.cs index 0fe1ad9ac3..c946d60058 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/Program.cs @@ -29,4 +29,11 @@ builder.Services.AddFoundryResponses(agent); var app = builder.Build(); app.MapFoundryResponses(); + +// In Development, also map the OpenAI-compatible route that AIProjectClient uses. +if (app.Environment.IsDevelopment()) +{ + app.MapFoundryResponses("openai/v1"); +} + app.Run(); diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs index 774cd3781a..27e0a404d7 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.ClientModel.Primitives; using Azure.AI.Projects; using Azure.Identity; using DotNetEnv; @@ -11,9 +12,21 @@ Env.TraversePath().Load(); string agentEndpoint = Environment.GetEnvironmentVariable("AGENT_ENDPOINT") ?? "http://localhost:8088"; // ── Create an agent-framework agent backed by the remote agent endpoint ────── -// The Foundry Agent SDK's AIProjectClient can target any OpenAI-compatible endpoint. -var aiProjectClient = new AIProjectClient(new Uri(agentEndpoint), new AzureCliCredential()); +var endpointUri = new Uri(agentEndpoint); +var options = new AIProjectClientOptions(); + +// For local HTTP dev: tell AIProjectClient the endpoint is HTTPS (to satisfy +// BearerTokenPolicy's TLS check), then swap the scheme back to HTTP right +// before the request hits the wire. +Uri clientEndpoint = endpointUri; +if (endpointUri.Scheme == "http") +{ + clientEndpoint = new UriBuilder(endpointUri) { Scheme = "https" }.Uri; + options.AddPolicy(new HttpSchemeRewritePolicy(), PipelinePosition.BeforeTransport); +} + +var aiProjectClient = new AIProjectClient(clientEndpoint, new AzureCliCredential(), options); var agent = aiProjectClient.AsAIAgent(); AgentSession session = await agent.CreateSessionAsync(); @@ -66,3 +79,31 @@ while (true) } Console.WriteLine("Goodbye!"); + +/// +/// Rewrites HTTPS URIs to HTTP right before transport, allowing AIProjectClient +/// to target a local HTTP dev server while satisfying BearerTokenPolicy's TLS check. +/// +internal sealed class HttpSchemeRewritePolicy : PipelinePolicy +{ + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + RewriteScheme(message); + ProcessNext(message, pipeline, currentIndex); + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + RewriteScheme(message); + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + } + + private static void RewriteScheme(PipelineMessage message) + { + var uri = message.Request.Uri!; + if (uri.Scheme == Uri.UriSchemeHttps) + { + message.Request.Uri = new UriBuilder(uri) { Scheme = "http" }.Uri; + } + } +} diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj index d2651ef7a7..814b5dba2d 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj @@ -12,6 +12,8 @@ + + diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs index 4b895e4bc0..f429221c15 100644 --- a/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Foundry/AzureAIProjectChatClientExtensions.cs @@ -13,7 +13,6 @@ using Microsoft.Agents.AI; using Microsoft.Agents.AI.Foundry; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; using OpenAI.Responses; @@ -230,8 +229,7 @@ public static partial class AzureAIProjectChatClientExtensions { Throw.IfNull(aiProjectClient); Throw.IfNull(agentOptions); - Throw.IfNull(agentOptions.ChatOptions); - Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId); + agentOptions.ChatOptions ??= new(); IChatClient chatClient = aiProjectClient .GetProjectOpenAIClient()