Skip to content

.NET: dotnet: Add hosted-agent User-Agent supplement to outgoing requests#5453

Merged
rogerbarreto merged 7 commits intomicrosoft:mainfrom
alliscode:hosted-useragent
Apr 30, 2026
Merged

.NET: dotnet: Add hosted-agent User-Agent supplement to outgoing requests#5453
rogerbarreto merged 7 commits intomicrosoft:mainfrom
alliscode:hosted-useragent

Conversation

@alliscode
Copy link
Copy Markdown
Member

@alliscode alliscode commented Apr 23, 2026

Summary

When an agent is served by Microsoft.Agents.AI.Foundry.Hosting, every outgoing OpenAI Responses-API request now carries a foundry-hosting/agent-framework-dotnet/{version} segment in its User-Agent header — alongside the existing Azure.AI.Projects.Agents/... and MEAI/... segments.

Approach — polyfill at the ResponsesClient boundary

When AgentFrameworkResponseHandler resolves an agent (the only context where we KNOW we are hosted), it calls a new TryApplyUserAgent(AIAgent) helper that mirrors the existing ApplyOpenTelemetry(AIAgent) pattern. TryApplyUserAgent:

  1. Walks agent.GetService<IChatClient>() then chatClient.GetService(meaiResponsesChatClientType) to find MEAI's internal OpenAIResponsesChatClient instance.
  2. If found, reflectively swaps the private _responseClient field with a DelegatingResponsesClient wrapper.

The wrapper subclasses OpenAI.Responses.ResponsesClient and overrides every RequestOptions-accepting public-virtual protocol method (Create/Get/Delete/Cancel + GetInputTokenCount + CompactResponse + GetResponseInputItemCollectionPage, both sync and async — 14 overrides total). Each override augments the per-call RequestOptions with a HostedAgentUserAgentPolicy and delegates to the inner ResponsesClient.

The OpenAI SDK's internal CreateResponseStreamingAsync(CreateResponseOptions, RequestOptions) and GetResponseStreamingAsync(GetResponseOptions, RequestOptions) (which MEAI binds via reflection) bottom out in calls to the public-virtual non-streaming overloads via virtual dispatch on this — so streaming traffic is covered without overriding any non-virtual member.

The wrapper accepts any ResponsesClient-derived inner — both Foundry's ProjectResponsesClient and the native OpenAI ResponsesClient — and preserves the inner's full pipeline (Transport, RetryPolicy, NetworkTimeout, OrganizationId / ProjectId / UserAgentApplicationId, custom policies) because every override delegates to the inner instance.

Idempotency: TryApplyUserAgent is a no-op when the agent has no IChatClient, when the chat client is not backed by MEAI's OpenAIResponsesChatClient, or when the inner _responseClient is already a DelegatingResponsesClient.

What's NOT covered

FoundryAgent.CreateConversationSessionAsync makes a direct call to ProjectConversationsClient.CreateConversation that does not flow through MEAI's chat client. That single bootstrap call does not carry the supplement; chat invocations on the same agent (which are the dominant traffic) DO.

Validation

  • 16 new unit tests in Microsoft.Agents.AI.Foundry.UnitTests cover streaming AND non-streaming paths, retry policy preservation, OrganizationId / ProjectId / UserAgentApplicationId pass-through, idempotency, all 14 protocol-method overrides via a [Theory], native OpenAI.ResponsesClient (not just ProjectResponsesClient), and reflection guards for MEAI / OpenAI SDK shape drift.
  • One end-to-end test (HostedOutboundUserAgentTests) spins up a real ASP.NET Core TestServer with AddFoundryResponses(agent) + MapFoundryResponses(), sends an inbound POST /responses as the Foundry runtime would, and asserts the OUTBOUND HTTP request from inside the hosted environment carries the supplement. Proves the full pipeline: inbound HTTP → AgentFrameworkResponseHandlerTryApplyUserAgent → wrapper swap → MEAI invocation → outbound transport.
  • All 380 unit tests pass on net10.0.

Cleanup

Removes earlier-iteration code that did not belong in the final design:

  • FoundryHostingExtensions.AddHostedAgentTelemetry(AIProjectClientOptions) extension method (was never invoked).
  • HostedUserAgentPolicy private class (replaced by the polyfill mechanism).
  • AgentFrameworkUserAgentMiddleware and its app.UseMiddleware<>() registration in MapFoundryResponses — inbound-side User-Agent decoration was not the intended scope; only outbound matters.
  • HostedAgentContext.cs (the cross-package static signal is no longer needed; the supplement is a static readonly inside the policy that only runs when TryApplyUserAgent swaps the wrapper in).
  • RequestOptionsExtensions.ToRequestOptions(this CancellationToken, bool) extension method (no callers anywhere in dotnet/src or dotnet/samples).
  • Updates two error messages in AgentFrameworkResponseHandler.cs that referenced AddAIAgent(...) (which lives in Microsoft.Agents.AI.Hosting, not Microsoft.Agents.AI.Foundry.Hosting) to point users to the correct registration APIs.
  • Reverts an unrelated whitespace change in Agent_Step25_ToolboxServerSideTools/Program.cs.

The net effect: the PR now does exactly one thing — decorate outgoing Responses-API requests from a hosted agent with the foundry-hosting User-Agent supplement. No inbound header manipulation, no other side effects.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests
  • Is this a breaking change? No.

Copilot AI review requested due to automatic review settings April 23, 2026 20:03
@moonbox3 moonbox3 added the .NET label Apr 23, 2026
@github-actions github-actions Bot changed the title dotnet: Add hosted-agent User-Agent supplement to outgoing requests .NET: dotnet: Add hosted-agent User-Agent supplement to outgoing requests Apr 23, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 94%

✗ Correctness

The core library changes adding User-Agent supplement via AsyncLocal are well-structured and correct. However, the sample program has what appears to be an accidental debug change: the environment variable name was changed from FOUNDRY_PROJECT_ENDPOINT to FOUNDRY_PROJECT_ENDPOINT_2 while the error message still references the original name.

✗ Security Reliability

The PR adds async-local User-Agent supplement propagation from the hosting layer to the HTTP pipeline policy. The AsyncLocal save/restore pattern in AgentFrameworkResponseHandler correctly mirrors the existing McpConsentContext pattern (McpConsentContext.cs:44). The UserAgentSupplement value is constructed solely from assembly metadata, not external input, so no header injection risk. One issue: the sample renames the env var to FOUNDRY_PROJECT_ENDPOINT_2 while the error message still references FOUNDRY_PROJECT_ENDPOINT, which appears to be a debugging artifact that should not be merged.

✗ Test Coverage

This PR introduces a new HostedAgentContext class, modifies the MEAI pipeline policy to append a User-Agent supplement, and wires save/restore logic in AgentFrameworkResponseHandler — all without any new tests. The existing test suite has zero coverage of HostedAgentContext, UserAgentSupplement, or CreateHostedUserAgentValue (verified via grep across dotnet/tests). The existing FoundryAgentTests.Constructor_UserAgentHeaderAddedToRequestsAsync only exercises the null-supplement path. Additionally, the sample Program.cs renames the env var to FOUNDRY_PROJECT_ENDPOINT_2 but leaves the error message referencing the old name.

✗ Design Approach

The hosted User-Agent propagation itself looks like a reasonable cross-layer solution, but the sample change in Agent_Step25_ToolboxServerSideTools is a design regression: it switches to a one-off FOUNDRY_PROJECT_ENDPOINT_2 variable without updating the surrounding contract, so the sample no longer follows the repo’s normal endpoint configuration and reads like a local workaround rather than a real fix.


Automated review by alliscode's agents

Comment thread dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an async-local “hosted agent” context so Foundry-hosted executions can append an extra product token to the existing MEAI User-Agent header on outgoing requests, enabling better request attribution from hosted environments.

Changes:

  • Introduces HostedAgentContext (AsyncLocal<string?>) for per-execution User-Agent supplementation.
  • Updates the MEAI per-call pipeline policy to append the supplement when present.
  • Sets/restores the supplement in the Foundry hosting response handler; updates a Foundry sample’s endpoint environment variable.
Show a summary per file
File Description
dotnet/src/Microsoft.Agents.AI.Foundry/RequestOptionsExtensions.cs Appends an optional hosted-agent supplement to the MEAI User-Agent header per request.
dotnet/src/Microsoft.Agents.AI.Foundry/HostedAgentContext.cs Adds async-local context carrier for a User-Agent supplement string.
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs Sets/restores the hosted-agent supplement around agent execution; computes hosted supplement value from assembly version.
dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs Changes which environment variable is read for the Foundry project endpoint.

Copilot's findings

Comments suppressed due to low confidence (1)

dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs:397

  • The hosted User-Agent segment described in the PR/nearby comment is agent-framework-hosted/{version}, but CreateHostedUserAgentValue() currently builds foundry-hosting/agent-framework-dotnet/{version}. Please align the emitted value and the documentation/PR intent (either update the constant/format here, or update the docs/comments if foundry-hosting/... is the intended token).
    private static string CreateHostedUserAgentValue()
    {
        const string Name = "foundry-hosting/agent-framework-dotnet";

        if (typeof(AgentFrameworkResponseHandler).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion is string version)
        {
  • Files reviewed: 4/4 changed files
  • Comments generated: 4

Comment thread dotnet/src/Microsoft.Agents.AI.Foundry/HostedAgentContext.cs Outdated
Comment thread dotnet/src/Microsoft.Agents.AI.Foundry/RequestOptionsExtensions.cs Outdated
Comment thread dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AgentFrameworkResponseHandler.cs Outdated
Copy link
Copy Markdown
Member Author

@alliscode alliscode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 93%

✗ Correctness

The core feature — flowing a supplemental User-Agent segment via AsyncLocal from the hosted-agent handler to the outgoing pipeline policy — is well-designed and correctly implemented. The save/restore pattern in the finally block, the static helper for version extraction, and the per-call AsyncLocal read in AddUserAgentHeader are all sound. However, the sample Program.cs contains what appears to be a debugging leftover: the environment variable name was changed from FOUNDRY_PROJECT_ENDPOINT to FOUNDRY_PROJECT_ENDPOINT_2 without updating the error message, and the _2 suffix itself looks accidental.

✗ Security Reliability

The PR introduces a hosted-agent User-Agent supplement via AsyncLocal context. The core mechanism (HostedAgentContext, save/restore in finally block, pipeline policy reading) is sound. However, there is one clear accidental change in the sample code: the environment variable name was changed from FOUNDRY_PROJECT_ENDPOINT to FOUNDRY_PROJECT_ENDPOINT_2, which appears to be a debugging artifact — the error message still references the original name.

✗ Test Coverage

This PR introduces HostedAgentContext (an AsyncLocal<string?> for supplemental User-Agent data), modifies the pipeline policy in RequestOptionsExtensions.AddUserAgentHeader to append the supplement, and sets/restores the value in AgentFrameworkResponseHandler.CreateAsync. None of these new behaviors have corresponding unit tests. There are no tests for HostedAgentContext itself (default null value, set/get, async-local scoping), no tests for the pipeline policy's new supplement-appending logic, no tests for the save/restore of UserAgentSupplement in the response handler's finally block, and no tests for CreateHostedUserAgentValue. Additionally, the sample change renames the environment variable to FOUNDRY_PROJECT_ENDPOINT_2 but keeps the old name in the error message.

✗ Design Approach

The overall design of using AsyncLocal<string?> to propagate a User-Agent supplement from the hosting layer to the HTTP pipeline policy is sound and idiomatic for .NET. The save-and-restore pattern in AgentFrameworkResponseHandler is correct for the nested-handler scenario. One clear blocking issue exists: the sample Program.cs accidentally reads FOUNDRY_PROJECT_ENDPOINT_2 instead of FOUNDRY_PROJECT_ENDPOINT while the error message still references the original name — an obvious testing artifact that would silently break the sample for any user who sets the documented variable.

Suggestions

  • Add unit tests for HostedAgentContext: verify default is null, set/get round-trips, and async-local scoping (value set in one async context doesn't leak to another).
  • Add a test for AgentFrameworkResponseHandler.CreateAsync verifying that HostedAgentContext.UserAgentSupplement is restored to its previous value after the method completes (including on exception paths).

Automated review by alliscode's agents

alliscode and others added 3 commits April 30, 2026 12:23
When an agent runs inside a Foundry Hosted Agent, the outgoing
User-Agent header now includes 'agent-framework-hosted/{version}'
alongside the existing 'MEAI/{version}' segment.

- Add HostedAgentContext with AsyncLocal<string?> property
- MeaiUserAgentPolicy reads the supplement per-call
- AgentFrameworkResponseHandler sets/restores the context

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…net/{version}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r-Agent supplement

When AgentFrameworkResponseHandler resolves an agent (i.e. we are running in a
hosted context), TryApplyUserAgent walks the agent's IChatClient decorator chain
to find MEAI's internal OpenAIResponsesChatClient and reflectively swaps its
inner _responseClient field with a DelegatingResponsesClient wrapper. The
wrapper overrides the public-virtual protocol methods to add a per-call
HostedAgentUserAgentPolicy to the RequestOptions and delegate to the inner
ResponsesClient. The OpenAI SDK's internal streaming overloads bottom out in
calls to the public-virtual non-streaming overloads via virtual dispatch on
this, so streaming is covered without overriding any non-virtual member.

The wrapper accepts any ResponsesClient-derived inner — both the Foundry
ProjectResponsesClient and the native OpenAI ResponsesClient — and preserves
the inner client's full pipeline (Transport, RetryPolicy, NetworkTimeout,
OrganizationId / ProjectId / UserAgentApplicationId, custom policies).

- Add DelegatingResponsesClient + HostedAgentUserAgentPolicy in Microsoft.Agents.AI.Foundry.Hosting.
- Add TryApplyUserAgent next to ApplyOpenTelemetry in FoundryHostingExtensions; wire it into AgentFrameworkResponseHandler.GetAgent for both keyed and default-agent paths.
- Drop earlier-iteration dead code: AddHostedAgentTelemetry extension, HostedUserAgentPolicy class, HostedAgentContext.cs, and the never-called ToRequestOptions helper.
- Revert RequestOptionsExtensions.MeaiUserAgentPolicy to MEAI-only (the supplement is now injected by the polyfill).
- Revert unrelated whitespace change in Agent_Step25_ToolboxServerSideTools sample.
- Tests cover streaming AND non-streaming, retry policy preservation, OrganizationId/ProjectId/UserAgentApplicationId pass-through, idempotency, native OpenAI ResponsesClient, and reflection guards for MEAI/OpenAI shape drift.
@rogerbarreto rogerbarreto marked this pull request as ready for review April 30, 2026 12:09
@rogerbarreto rogerbarreto moved this to In Review in Agent Framework Apr 30, 2026
Copy link
Copy Markdown
Member

@SergeyMenshykh SergeyMenshykh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall: Well-structured PR with a clear, focused scope — decorating outgoing Responses-API requests with a hosted-agent User-Agent supplement. The approach is sound, the test coverage is thorough (unit + E2E), and the cleanup of dead code is welcome.

Positives

  • Single-responsibility: The PR does exactly one thing and does it well. The removal of the inbound middleware approach in favor of outbound-only decoration is the correct design.
  • Defensive design: TryApplyUserAgent is idempotent, handles null/missing services gracefully, and the ThrowingTransport on the dummy pipeline is a good safety net.
  • Thorough testing: 16 unit tests + 1 E2E test covering streaming/non-streaming, retry preservation, org/project headers, idempotency, all 14 protocol methods, and reflection guards for MEAI shape drift.
  • Good cleanup: Removing HostedAgentContext, AgentFrameworkUserAgentMiddleware, HostedUserAgentPolicy, and unused ToRequestOptions extension.

Issues

1. Null-forgiving return on null agent (Minor bug smell)
ServiceCollectionExtensions.csTryApplyUserAgent:
csharp if (agent is null) { return agent!; // null-forgiving on a null value }
The ! suppresses the nullable warning, but the caller in ResolveAgent already null-checked before calling. If someone calls this from another site without null-checking, they get a silent null back from a method typed as returning non-null AIAgent. Consider either:

  • Changing the return type to AIAgent? to be honest about nullability, or
  • Throwing ArgumentNullException (consistent with the rest of the codebase).

2. AddUserAgentPolicy mutates shared RequestOptions (Low risk)
DelegatingResponsesClient.AddUserAgentPolicy:
csharp private static RequestOptions AddUserAgentPolicy(RequestOptions? options) { options ??= new RequestOptions(); options.AddPolicy(HostedAgentUserAgentPolicy.Instance, PipelinePosition.PerCall); return options; }
When the caller passes a non-null RequestOptions, this mutates it by adding a policy. If a caller reuses the same RequestOptions across calls, policies would accumulate. The HostedAgentUserAgentPolicy has an idempotency guard on the header itself (Contains check), so it won't double-append the UA string, but the policy list itself grows unboundedly on retries or reuse. In practice MEAI creates fresh RequestOptions per call, so this is low risk — but worth a comment or a defensive check (if (!options.ContainsPolicy(...))) if the SDK doesn't deduplicate internally.

3. Reflection fragility is acknowledged but not mitigated at runtime (Nit)
The static fields s_meaiResponsesChatClientType and s_meaiResponseClientField are resolved once at startup. If they're null (MEAI version drift), TryApplyUserAgent silently no-ops. This is correct fail-safe behavior, but there's no logging/telemetry when this happens — the team would have no signal that the supplement stopped being applied after an MEAI upgrade. Consider emitting a one-time warning log when the reflection targets are null.

4. Test: RecordingHandler duplicated across test files
DelegatingResponsesClientTests and HostedOutboundUserAgentTests each define their own RecordingHandler with slightly different RecordedRequest shapes. Consider extracting to a shared test helper to reduce duplication.

5. RequestOptionsExtensionsTests — new tests for code not changed in this PR
The new RequestOptionsExtensionsTests.cs (115 lines) tests the pre-existing MeaiUserAgentPolicy which wasn't modified in this PR (only the unused ToRequestOptions was removed). These tests are useful but could be called out as additive coverage for existing code, not regression tests for this change.

Summary

The design is solid. The reflection-based polyfill is the pragmatic choice given that MEAI's OpenAIResponsesChatClient is internal. The main actionable item is #1 (null-forgiving return). The rest are minor observations. Approve with the suggestion to address #1.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 87%

✓ Correctness

This PR refactors User-Agent handling from an inbound ASP.NET Core middleware (modifying incoming request headers) to an outbound pipeline policy approach via DelegatingResponsesClient and HostedAgentUserAgentPolicy. The architectural change is sound — the old middleware only affected inbound headers while the new approach correctly stamps the hosted-agent supplement onto outbound AI-service calls. The reflection-based TryApplyUserAgent is fragile by nature but handles failures gracefully (no-op on missing types/fields) and is protected by reflection-guard tests. The ThrowingTransport in the dummy pipeline is good defensive design that will surface unexpected code paths loudly. Test coverage is comprehensive. No blocking correctness issues found.

✓ Security Reliability

This PR replaces inbound ASP.NET Core User-Agent middleware with an outbound pipeline policy (HostedAgentUserAgentPolicy) injected via a DelegatingResponsesClient wrapper. The approach is architecturally sound: reflection-based mutation of MEAI's internal _responseClient field is well-guarded with null checks, type checks, and idempotency (skipping DelegatingResponsesClient instances). The ThrowingTransport safety net in the dummy pipeline is good defensive design. The removed ToRequestOptions has no remaining callers. HostedAgentContext (previously flaged for AsyncLocal leak) is fully removed. Test coverage is thorough, including reflection-guard tests to detect MEAI internal changes. No significant security or reliability issues found.

✓ Test Coverage

The test suite is comprehensive overall — it covers E2E hosted pipeline tests, multiple client types (Azure + native OpenAI), streaming/non-streaming, retry policy preservation, and reflection guards for MEAI internals. However, TryApplyUserAgent only has unit tests for negative/bail-out paths. The critical idempotency path (current is DelegatingResponsesClient) is not directly tested, despite ResolveAgent calling it per-request on potentially singleton agents. The existing ApplyOpenTelemetry test suite establishes a clear pattern for testing idempotency that should be mirrored here. Additionally, the HostedAgentUserAgentPolicy double-append guard has no direct test coverage.

✓ Design Approach

The outbound User-Agent change fixes the right symptom, but the current implementation does it by mutating the resolved agent’s private MEAI client in place. Because the common AddFoundryResponses(agent, ...) path registers that agent as a singleton, the hosted-only wrapper can leak into later non-hosted uses of the same agent instance, so the behavior becomes dependent on whether the hosted endpoint has ever touched it.


Automated review by rogerbarreto's agents

- TryApplyUserAgent: replace silent null-return with ArgumentNullException to match the codebase's convention.
- Add idempotency test (TryApplyUserAgent_CalledTwiceOnSameAgent_DoesNotDoubleWrap) — runs the polyfill twice on the same agent and asserts the wire UA contains exactly one foundry-hosting segment, proving the 'current is DelegatingResponsesClient' guard prevents nested wrapping.
- Add retry-double-append test (Polyfill_RetryWithinCall_DoesNotDuplicateSupplementInUserAgent) — exercises the HostedAgentUserAgentPolicy Contains-guard via a custom retry policy that re-runs the inner pipeline on the same message.
- Replace TryApplyUserAgent_NullAgent_ReturnsNullWithoutThrowing with TryApplyUserAgent_NullAgent_ThrowsArgumentNullException to match the new contract.
The two call sites in AgentFrameworkResponseHandler.GetAgent already null-check the agent before invoking TryApplyUserAgent, so the defensive ArgumentNullException is unreachable. Remove it and the corresponding test.
…lectionExtensions

The Throw.IfNull helper from this namespace was used by the now-removed null check in TryApplyUserAgent. Drop the unused import to satisfy IDE0005 in CI's full-project dotnet format run.
@rogerbarreto rogerbarreto added this pull request to the merge queue Apr 30, 2026
Merged via the queue into microsoft:main with commit 6cd8128 Apr 30, 2026
23 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in Agent Framework Apr 30, 2026
rogerbarreto added a commit to rogerbarreto/agent-framework-public that referenced this pull request Apr 30, 2026
…esClient

Address westey-m's review feedback on PR microsoft#5453: `Delegating*` is conventionally reserved for inheritable base classes (mirroring `DelegatingHandler`) where consumers override one or two members. This polyfill is sealed and only injects the User-Agent supplement, so the new name reflects its actual purpose.

Renamed via `git mv` to preserve history:
* `src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs` to `UserAgentResponsesClient.cs`
* `tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs` to `UserAgentResponsesClientTests.cs`

Class, constructor, and all references updated across:
* `src/.../UserAgentResponsesClient.cs` (class + constructor + internal log message)
* `src/.../ServiceCollectionExtensions.cs` (cref + type check + instantiation)
* `src/.../HostedAgentUserAgentPolicy.cs` (cref)
* `tests/Foundry.UnitTests/RequestOptionsExtensionsTests.cs` (comment)
* `tests/Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs` (class + cref + instantiations)
github-merge-queue Bot pushed a commit that referenced this pull request Apr 30, 2026
* Foundry.Hosting.UnitTests: extract project from Foundry.UnitTests

Move all Hosting/* tests, three toolbox TestData JSONs, and the FakeAuthenticationTokenProvider/HttpHandlerAssert/TestDataUtil helpers (trimmed to toolbox getters) into a new Microsoft.Agents.AI.Foundry.Hosting.UnitTests project. Add it to the slnx and grant the new assembly InternalsVisibleTo from Microsoft.Agents.AI.Foundry and Microsoft.Agents.AI.Foundry.Hosting.

* Foundry.Hosting.UnitTests: align namespaces to assembly name

Rename namespaces from Microsoft.Agents.AI.Foundry.UnitTests(.Hosting) to Microsoft.Agents.AI.Foundry.Hosting.UnitTests across all moved tests, the duplicated helpers, and the trimmed TestDataUtil. Also fixes the prior namespace inconsistency in FoundryToolboxTests.

* Foundry.Hosting.UnitTests: split WorkflowIntegrationTests by SUT

Replace the WorkflowIntegrationTests file (an IT-named file inside a UT project) with two SUT-focused files plus a shared test-doubles file:

- AgentFrameworkResponseHandlerWorkflowTests.cs - the 5 handler-driven tests that exercise AgentFrameworkResponseHandler with a real workflow agent.
- OutputConverterWorkflowTests.cs - the 5 OutputConverter tests driven by hand-crafted update sequences mirroring real workflow patterns.
- WorkflowTestAgents.cs - StreamingTextAgent and ThrowingStreamingAgent extracted as internal types used by both files.

* Foundry.UnitTests: trim Hosting-related conditionals and dead testdata

Now that Hosting tests live in their own project:
- drop the Compile Remove guard for the Hosting subfolder,
- drop the .NETCoreApp-only PackageReferences (Azure.AI.AgentServer.Responses, Microsoft.AspNetCore.TestHost, OpenTelemetry, OpenTelemetry.Exporter.InMemory),
- drop the conditional ProjectReference to Microsoft.Agents.AI.Foundry.Hosting,
- delete the three Toolbox JSON files and the matching Toolbox getters in TestDataUtil.

* Foundry.Hosting.UnitTests: drop redundant 'using Microsoft.Agents.AI.Foundry.Hosting'

The new project namespace is Microsoft.Agents.AI.Foundry.Hosting.UnitTests, which already brings the parent Microsoft.Agents.AI.Foundry.Hosting namespace into scope. The explicit using statement is therefore redundant (IDE0005). Caught by 'dotnet format --verify-no-changes' running on Linux against the .NET 10 SDK.

* Foundry.Hosting: drop InternalsVisibleTo to Foundry.UnitTests

The non-hosting Foundry.UnitTests project no longer holds any Hosting tests after the split, so it doesn't need access to internal types in Microsoft.Agents.AI.Foundry.Hosting. Only Microsoft.Agents.AI.Foundry.Hosting.UnitTests needs it.

* Foundry.Hosting: rename DelegatingResponsesClient to UserAgentResponsesClient

Address westey-m's review feedback on PR #5453: `Delegating*` is conventionally reserved for inheritable base classes (mirroring `DelegatingHandler`) where consumers override one or two members. This polyfill is sealed and only injects the User-Agent supplement, so the new name reflects its actual purpose.

Renamed via `git mv` to preserve history:
* `src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs` to `UserAgentResponsesClient.cs`
* `tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs` to `UserAgentResponsesClientTests.cs`

Class, constructor, and all references updated across:
* `src/.../UserAgentResponsesClient.cs` (class + constructor + internal log message)
* `src/.../ServiceCollectionExtensions.cs` (cref + type check + instantiation)
* `src/.../HostedAgentUserAgentPolicy.cs` (cref)
* `tests/Foundry.UnitTests/RequestOptionsExtensionsTests.cs` (comment)
* `tests/Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs` (class + cref + instantiations)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants