// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001
namespace Microsoft.Agents.AI.Foundry.UnitTests;
///
/// One-shot verification (kept in tree to detect regressions) that MEAI 10.5.1 stamps its own
/// MEAI/{version} User-Agent segment automatically when an
/// is wrapped via AsIChatClient(). If this test starts failing, the FoundryChatClient
/// implementation must re-register the MEAI policy explicitly via OpenAIRequestPolicies because
/// the local Foundry copy was deleted under the assumption that MEAI provides it built-in.
///
public sealed class MeaiAutoUserAgentVerificationTests
{
[Fact]
public async Task MeaiOpenAIResponsesClient_StampsMeaiSegmentAutomatically_WithoutLocalPolicyAsync()
{
// Arrange: bare OpenAI ResponseClient over a fake HTTP transport, wrapped via MEAI's
// AsIChatClient() with no custom OpenAIRequestPolicies registration. If MEAI auto-stamps
// its own MEAI/{version} segment, it will appear here.
using var handler = new RecordingHandler();
#pragma warning disable CA5399
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var options = new OpenAIClientOptions
{
Transport = new HttpClientPipelineTransport(httpClient),
Endpoint = new Uri("https://example.test/v1"),
};
var responseClient = new ResponsesClient(new ApiKeyCredential("test-key"), options);
var chatClient = responseClient.AsIChatClient("gpt-4o-mini");
// Act: send a request through MEAI's chat client. The fake transport will throw on
// response parsing, but we only care about the outbound headers, which are captured
// before the response is parsed.
try
{
await chatClient.GetResponseAsync("hi", cancellationToken: CancellationToken.None);
}
catch
{
// Expected: the fake response body is not parseable as a Responses API payload.
}
// Assert: at least one outbound request reached the transport, and its User-Agent
// contains either "MEAI/" (auto-stamped by MEAI) or no MEAI segment (verification
// signal — see test summary).
Assert.True(handler.Count > 0, "Expected at least one outbound request from MEAI wrapper.");
Assert.NotNull(handler.LastUserAgent);
// INTENT: assert that MEAI auto-stamps. If the assertion fails, see the FoundryChatClient
// implementation note about needing to register the MEAI policy explicitly.
Assert.Contains("MEAI/", handler.LastUserAgent);
}
private sealed class RecordingHandler : HttpClientHandler
{
public int Count { get; private set; }
public string? LastUserAgent { get; private set; }
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.Count++;
this.LastUserAgent = request.Headers.TryGetValues("User-Agent", out var values)
? string.Join(",", values)
: null;
var resp = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
RequestMessage = request,
};
return Task.FromResult(resp);
}
}
}