.NET: dotnet: Add hosted-agent User-Agent supplement to outgoing requests#5453
.NET: dotnet: Add hosted-agent User-Agent supplement to outgoing requests#5453rogerbarreto merged 7 commits intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
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_ToolboxServerSideToolsis a design regression: it switches to a one-offFOUNDRY_PROJECT_ENDPOINT_2variable 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
There was a problem hiding this comment.
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-executionUser-Agentsupplementation. - 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}, butCreateHostedUserAgentValue()currently buildsfoundry-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 iffoundry-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
alliscode
left a comment
There was a problem hiding this comment.
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_ENDPOINTtoFOUNDRY_PROJECT_ENDPOINT_2, which appears to be a debugging artifact — the error message still references the original name.
✗ Test Coverage
This PR introduces
HostedAgentContext(anAsyncLocal<string?>for supplemental User-Agent data), modifies the pipeline policy inRequestOptionsExtensions.AddUserAgentHeaderto append the supplement, and sets/restores the value inAgentFrameworkResponseHandler.CreateAsync. None of these new behaviors have corresponding unit tests. There are no tests forHostedAgentContextitself (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 ofUserAgentSupplementin the response handler'sfinallyblock, and no tests forCreateHostedUserAgentValue. Additionally, the sample change renames the environment variable toFOUNDRY_PROJECT_ENDPOINT_2but 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 inAgentFrameworkResponseHandleris correct for the nested-handler scenario. One clear blocking issue exists: the sampleProgram.csaccidentally readsFOUNDRY_PROJECT_ENDPOINT_2instead ofFOUNDRY_PROJECT_ENDPOINTwhile 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.CreateAsyncverifying thatHostedAgentContext.UserAgentSupplementis restored to its previous value after the method completes (including on exception paths).
Automated review by alliscode's agents
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>
70a6a49 to
9711eb0
Compare
…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.
9711eb0 to
516ea56
Compare
SergeyMenshykh
left a comment
There was a problem hiding this comment.
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:
TryApplyUserAgentis idempotent, handles null/missing services gracefully, and theThrowingTransporton 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 unusedToRequestOptionsextension.
Issues
1. Null-forgiving return on null agent (Minor bug smell)
ServiceCollectionExtensions.cs — TryApplyUserAgent:
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.
There was a problem hiding this comment.
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
DelegatingResponsesClientandHostedAgentUserAgentPolicy. 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-basedTryApplyUserAgentis fragile by nature but handles failures gracefully (no-op on missing types/fields) and is protected by reflection-guard tests. TheThrowingTransportin 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,
TryApplyUserAgentonly has unit tests for negative/bail-out paths. The critical idempotency path (current is DelegatingResponsesClient) is not directly tested, despiteResolveAgentcalling it per-request on potentially singleton agents. The existingApplyOpenTelemetrytest suite establishes a clear pattern for testing idempotency that should be mirrored here. Additionally, theHostedAgentUserAgentPolicydouble-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.
…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)
* 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)
Summary
When an agent is served by
Microsoft.Agents.AI.Foundry.Hosting, every outgoing OpenAI Responses-API request now carries afoundry-hosting/agent-framework-dotnet/{version}segment in itsUser-Agentheader — alongside the existingAzure.AI.Projects.Agents/...andMEAI/...segments.Approach — polyfill at the
ResponsesClientboundaryWhen
AgentFrameworkResponseHandlerresolves an agent (the only context where we KNOW we are hosted), it calls a newTryApplyUserAgent(AIAgent)helper that mirrors the existingApplyOpenTelemetry(AIAgent)pattern.TryApplyUserAgent:agent.GetService<IChatClient>()thenchatClient.GetService(meaiResponsesChatClientType)to find MEAI's internalOpenAIResponsesChatClientinstance._responseClientfield with aDelegatingResponsesClientwrapper.The wrapper subclasses
OpenAI.Responses.ResponsesClientand overrides everyRequestOptions-accepting public-virtual protocol method (Create/Get/Delete/Cancel + GetInputTokenCount + CompactResponse + GetResponseInputItemCollectionPage, both sync and async — 14 overrides total). Each override augments the per-callRequestOptionswith aHostedAgentUserAgentPolicyand delegates to the innerResponsesClient.The OpenAI SDK's
internal CreateResponseStreamingAsync(CreateResponseOptions, RequestOptions)andGetResponseStreamingAsync(GetResponseOptions, RequestOptions)(which MEAI binds via reflection) bottom out in calls to the public-virtual non-streaming overloads via virtual dispatch onthis— so streaming traffic is covered without overriding any non-virtual member.The wrapper accepts any
ResponsesClient-derived inner — both Foundry'sProjectResponsesClientand the native OpenAIResponsesClient— and preserves the inner's full pipeline (Transport, RetryPolicy, NetworkTimeout, OrganizationId / ProjectId / UserAgentApplicationId, custom policies) because every override delegates to the inner instance.Idempotency:
TryApplyUserAgentis a no-op when the agent has noIChatClient, when the chat client is not backed by MEAI'sOpenAIResponsesChatClient, or when the inner_responseClientis already aDelegatingResponsesClient.What's NOT covered
FoundryAgent.CreateConversationSessionAsyncmakes a direct call toProjectConversationsClient.CreateConversationthat 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
Microsoft.Agents.AI.Foundry.UnitTestscover streaming AND non-streaming paths, retry policy preservation,OrganizationId/ProjectId/UserAgentApplicationIdpass-through, idempotency, all 14 protocol-method overrides via a[Theory], nativeOpenAI.ResponsesClient(not justProjectResponsesClient), and reflection guards for MEAI / OpenAI SDK shape drift.HostedOutboundUserAgentTests) spins up a real ASP.NET CoreTestServerwithAddFoundryResponses(agent)+MapFoundryResponses(), sends an inboundPOST /responsesas the Foundry runtime would, and asserts the OUTBOUND HTTP request from inside the hosted environment carries the supplement. Proves the full pipeline: inbound HTTP →AgentFrameworkResponseHandler→TryApplyUserAgent→ wrapper swap → MEAI invocation → outbound transport.Cleanup
Removes earlier-iteration code that did not belong in the final design:
FoundryHostingExtensions.AddHostedAgentTelemetry(AIProjectClientOptions)extension method (was never invoked).HostedUserAgentPolicyprivate class (replaced by the polyfill mechanism).AgentFrameworkUserAgentMiddlewareand itsapp.UseMiddleware<>()registration inMapFoundryResponses— 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 astatic readonlyinside the policy that only runs whenTryApplyUserAgentswaps the wrapper in).RequestOptionsExtensions.ToRequestOptions(this CancellationToken, bool)extension method (no callers anywhere indotnet/srcordotnet/samples).AgentFrameworkResponseHandler.csthat referencedAddAIAgent(...)(which lives inMicrosoft.Agents.AI.Hosting, notMicrosoft.Agents.AI.Foundry.Hosting) to point users to the correct registration APIs.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