Commit Graph
588 Commits
Author SHA1 Message Date
b559545fa4 .NET: Fix declarative workflow regressions for hosted agents (#5905)
* Fix declarative workflow regressions for hosted agents

Three regressions surfaced when running a declarative workflow as a
Foundry hosted agent. Together they caused every condition group to fall
through to elseActions and the raw agent JSON to leak to the caller.

1. AgentProviderExtensions.InvokeAgentAsync forced autoSend to true
   whenever the agent ran on the workflow conversation, which overrode
   the explicit autoSend: false declared in workflow.yaml and streamed
   the raw structured-output JSON straight to the user. Honor the
   caller-supplied autoSend instead.

2. IWorkflowContextExtensions.ReadState / QueueStateUpdateAsync /
   QueueStateResetAsync took the variable name and namespace alias
   directly from PropertyPath.VariableName / NamespaceAlias. Against
   Microsoft.Agents.ObjectModel 2026.2.4.1 those properties return null
   for a dotted reference such as `Local.Triage` even when
   SegmentCount == 2 and IsValid == true, so every assignment threw
   ArgumentNullException via Throw.IfNull. Fall back to Segments() to
   reconstruct the name and alias when the parser returns null.

3. The same ObjectModel version no longer recognizes the user-facing
   `Local` scope alias: VariableScopeNames.IsValidName(`Local`)
   returns false and GetNamespaceFromName(`Local`) returns Unknown, so
   the declarative interpreter's IsManagedScope check fails and the
   State.Set call is silently skipped. Translate the `Local` alias to
   its canonical `Topic` form before forwarding to
   QueueStateUpdateAsync; WorkflowFormulaState.Bind continues to expose
   it as `Local` to PowerFx.

Verified end-to-end against a deployed Foundry hosted agent: the
declarative triage workflow now routes Technical / Billing / General
inputs correctly and only the autoSend-eligible messages reach the
caller.

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

* Hosted-agent HITL: persist session across previous_response_id chains; run approved local AIFunctions

Two regressions hit declarative workflows that use require_approval=true when
the client chains turns via previous_response_id (no conversation_id):

1. AgentFrameworkResponseHandler keyed the AgentSession store solely on
   conversation_id, so when only previous_response_id was present the
   StateBag (which holds ToolApprovalIdMap) was discarded after each turn.
   The next turn then threw 'No approval mapping recorded for wire id ...'
   in InputConverter.ConvertMcpApprovalResponse.

   Fix: fall back to previous_response_id on load and to context.ResponseId
   on save so the response-id chain becomes a valid session key. Conversation
   id remains preferred when present.

2. InvokeFunctionToolExecutor.CaptureResponseAsync only acted on
   FunctionResultContent. In the hosted Foundry path the approval response
   arrives as a ToolApprovalResponseContent with no FunctionResultContent,
   so the local AIFunction never ran and downstream PropertyPath/SendActivity
   consumers (e.g. {Local.RefundResult}) saw empty values.

   Fix: when no FunctionResultContent matches but an approved
   ToolApprovalResponseContent does, look up the registered AIFunction by
   name on agentProvider.Functions and invoke it with the evaluated
   arguments, surfacing the result through the existing assignment path.

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

* Apply PropertyPath workaround to initialization path; share + tidy helpers

Address PR #5905 review feedback:

* Move the PropertyPath VariableName/NamespaceAlias fallback and 'Local'
  -> 'Topic' scope remap into a shared internal PropertyPathExtensions
  helper. Materializes Segments() once, names the magic 'Local' alias
  as a const, and carries a TODO referencing the tracking issue.

* Apply the same helper in WorkflowDiagnostics.InitializeDefaults so a
  declared default for a dotted variable like 'Local.Triage' is no
  longer silently skipped at workflow startup (closes the gap flagged
  by the reviewer: runtime ReadState/QueueStateUpdateAsync worked but
  state.Initialize did not).

* Restore the previous strict failure mode on namespace alias by
  wrapping GetNamespaceAlias() in Throw.IfNull at call sites so a
  malformed single-segment path keeps failing fast rather than
  silently passing null to State.Get/Set.

All 821 unit tests pass.

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

* Add tests for AgentProviderExtensions.InvokeAgentAsync autoSend behavior

Covers the autoSend regression fix: when the agent runs on the workflow conversation with autoSend=false, no AgentResponseUpdateEvent or AgentResponseEvent is added to the context. Also covers autoSend=true (events emitted) and autoSend=false on a non-workflow conversation.

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

* Surface SendActivity output via AgentResponseUpdateEvent

SendActivityExecutor previously only emitted the activity text via YieldOutputAsync, which the runtime converts to an AgentResponseEvent. WorkflowSession gates AgentResponseEvent behind includeWorkflowOutputsInResponse, so when a host opts out of summary outputs (the default for AsAIAgent) the SendActivity reply is silently dropped.

Mirror the pattern used by AgentProviderExtensions for autoSend agent invocations: also emit an AgentResponseUpdateEvent, which WorkflowSession yields unconditionally. This makes SendActivity reliably reach chat-protocol clients without requiring includeWorkflowOutputsInResponse = true (which would also duplicate autoSend agent output).

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

* Revert previous_response_id session-key fallback

The fallback let a session be keyed by an unbroken previous_response_id chain,
but conversation_id is the right way to thread state across turns: it survives
shared/branched chains (e.g. when another agent generates a response in between)
and is the documented model for stateful clients. Restore conversation_id as the
sole session key and rely on the client to thread it. The InvokeFunctionTool
approval/local-function half of 1baf4af4d remains.

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

* Set Foundry ProductContext per-executor instead of via PropertyPath workaround

ObjectModel 2026.2.4.1 resolves PropertyPath.VariableName / NamespaceAlias and VariableScopeNames.IsValidName against AsyncLocal<ProductContext> at access time. In hosted-agent scenarios each HTTP request runs on a fresh async context where that AsyncLocal is default, so dotted refs like Local.Triage returned null and the Local scope alias was rejected.

Replace the PropertyPathExtensions helper (which papered over both symptoms) with a single WorkflowDiagnostics.SetFoundryProduct() call at the entry of DeclarativeActionExecutor.HandleAsync. The set writes to the request's logical async context before any code reads PropertyPath, letting the existing parser and scope resolver work as designed.

Validated: 824/824 declarative unit tests pass; technical/billing/general routes all dispatch correctly against a deployed Foundry hosted agent.

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

* Address review feedback on InvokeFunctionToolExecutor

- Surface registered-function lookup failures and invocation exceptions via FunctionResultContent.Exception instead of returning the error text as a successful Result, so downstream {Local.X} assignments can distinguish failures from successes.

- Use AIJsonUtilities.DefaultOptions to JSON-serialize non-string function results (matching FunctionInvokingChatClient / ToolBridge), so complex types stay consumable by PropertyPath consumers instead of degrading to Object.ToString().

- Drop the explicit System. prefix on StringComparison / Exception now that the file imports System.

- Add AutoSendTrueOnExternalConversationEmitsResponseEventsAndCopiesMessagesAsync to cover the (autoSend: true, external conversation) quadrant, asserting that response events are emitted and that messages are mirrored to the workflow conversation.

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

* Honor AutoSendIsDefaultValue when computing autoSend

AzureAgentOutput.AutoSend and InvokeToolOutput.AutoSend in
Microsoft.Agents.ObjectModel 2026.2.4.1 are never null — they
return a literal-false default when the YAML omits the field.
The previous null check in Get/AutoSendValue therefore always
fell through to evaluating the literal false, so every action
whose YAML had any output block but no explicit autoSend was
treated as autoSend = false. This was previously masked by
`autoSend |= isWorkflowConversation` in AgentProviderExtensions
(removed earlier in this PR to honor explicit autoSend: false),
which silently re-enabled autoSend on the workflow conversation.

Use AutoSendIsDefaultValue to distinguish an explicit autoSend
value from the implicit default and treat the implicit default
as true, restoring the historical behavior for ValidateCaseAsync
InvokeAgent.yaml (3 InvokeAzureAgent actions, last one captures
to Local.RatingResponse via output.messages with no autoSend
specified) while keeping the hosted-agent fix that honors an
explicit autoSend: false.

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

---------

Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 01:06:38 +00:00
afd2739e38 .NET: Surface x-ms-served-model header as ChatResponse.ModelId for Foundry agents (#5979)
* .NET: Surface x-ms-served-model header as ChatResponse.ModelId for Foundry agents

Mirrors Python PR #5910. Adds an internal SCM PipelinePolicy that reads the x-ms-served-model HTTP response header on Azure OpenAI Responses calls and writes it into an AsyncLocal box. A DelegatingChatClient sits between OpenTelemetry and the MEAI OpenAIResponsesChatClient and overwrites ChatResponse.ModelId with the served snapshot so OTel spans report the actual model rather than the deployment alias. Wired through all AsAIAgent paths in Microsoft.Agents.AI.Foundry.

* .NET: Fix line endings and BOM on ResponsesAgentServedModelTests

* .NET: Address Copilot review on Foundry served-model PR

- Restore previous ServedModelScope in finally to avoid AsyncLocal leak into caller execution context.
- Make served-model integration test assertion robust to deployment names that already match the snapshot pattern.
- Broaden UnitTests csproj comment to cover all conditional removals (net8.0+ requirement).

* .NET: Split ServedModelTests into per-SUT files with regions

Split the combined ServedModelTests.cs into one test class per SUT:

- ServedModelScopeTests.cs (AsyncLocal carrier)
- ServedModelPolicyTests.cs (SCM pipeline policy)
- ServedModelChatClientTests.cs (delegating client, with regions for Non-streaming / Streaming / End-to-end)

Shared helpers and fake clients moved into ServedModelTestHelpers.cs.

Csproj net8.0+ exclusion list updated accordingly.

* .NET: Consolidate served-model logic into FoundryChatClient

Move x-ms-served-model header capture from the standalone ServedModelChatClient
decorator directly into FoundryChatClient, eliminating a separate wrapper that
had to be applied at every Foundry entry point via WireServedModel().

- Register ServedModelPolicy in FoundryChatClient constructors (alongside the
  existing AgentFrameworkUserAgentPolicy registration)
- Add StrongBox push/read logic to FoundryChatClient.GetResponseAsync and
  GetStreamingResponseAsync
- Delete ServedModelChatClient.cs and its unit tests
- Remove WireServedModel() from FoundryAgent and AIProjectClientExtensions
- Update ServedModelPolicy/Scope XML docs to reference FoundryChatClient
- Simplify ServedModelTestHelpers to use FoundryChatClient directly

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-21 21:26:42 +00:00
westeyandGitHub bda40ba0e1 .NET: Add shell support to the HarnessAgent (#6005)
* Add shell support to the HarnessAgent

* Address PR comments

* Address PR comments
2026-05-21 17:25:33 +00:00
westeyandGitHub 4050107942 .NET: Add background agents support to HarnessAgent (#5977)
* Add background agents support to HarnessAgent

* Add unit tests

* Address PR comments
2026-05-21 10:57:06 +00:00
Roger BarretoandGitHub a12cc3878e .NET: Promote FoundryChatClient to public, add file/vector-store helpers and ToPromptAgentAsync converter (#5940)
* Consolidate Foundry chat client decorators into FoundryChatClient

- Replace AzureAIProjectChatClient and AzureAIProjectResponsesChatClient with a single internal sealed FoundryChatClient that covers three modes (pure responses, server-side agent reference, hosted agent endpoint).
- Rename AzureAIProjectChatClientExtensions to AIProjectClientExtensions to reflect that it extends AIProjectClient.
- All four AsAIAgent extension overloads and both FoundryAgent constructors now construct FoundryChatClient internally so the microsoft.foundry telemetry tag is uniform across paths.
- Introduce AgentFrameworkUserAgentPolicy that stamps agent-framework-dotnet/{version} on outbound requests, mirroring the Python agent-framework-python/{version} contract.
- Delete the Foundry-local MeaiUserAgentPolicy duplicate; rely on MEAI 10.5.1 to stamp MEAI/{version} automatically.
- HostedAgentUserAgentPolicy keeps the combined foundry-hosting/agent-framework-dotnet/{version} segment (Python parity) and upgrades the bare segment in place to avoid duplication.
- Tests reorganized: FoundryChatClientTests, AIProjectClientExtensionsTests, AgentFrameworkUserAgentPolicyTests, MeaiAutoUserAgentVerificationTests, plus in-place upgrade unit tests in HostedOutboundUserAgentTests.

* Promote FoundryChatClient to public; add file/vector-store helpers and ToPromptAgentAsync converter

- Promote FoundryChatClient from internal sealed to public sealed for Python parity, so .NET developers can hold and pass a FoundryChatClient directly the way Python developers do.
- Mode 3 (hosted agent endpoint) now materializes an AIProjectClient from the parsed project root, making GetService<AIProjectClient>() non-null across all three construction modes. This eliminates the per-mode asymmetry that previously hid project-level helpers from agents constructed via an agent endpoint URL.
- Add four new instance methods on FoundryChatClient mirroring Python's spec: UploadFileAsync, DeleteFileAsync, CreateVectorStoreAsync (bundles upload + create + wait), DeleteVectorStoreAsync. Single overload each, path-only inputs to start; additional overloads can be added later without breaking callers. All are Experimental, consistent with the rest of the Foundry package.
- Add ToPromptAgentAsync extension methods on ChatClientAgent and FoundryAgent for the agent-to-prompt-agent converter described in the Foundry spec. Mode 1 (responses API) synthesizes a DeclarativeAgentDefinition from the agent's ChatOptions; mode 2 (server-side agent reference, version, or record) returns the cached or freshly fetched Definition; mode 3 throws InvalidOperationException because no local definition exists to convert.
- Strict AITool to ResponseTool mapping for mode 1: AIFunction becomes CreateFunctionTool with the function's JSON schema; AITool instances that wrap a ResponseTool unwrap via GetService(typeof(ResponseTool)); anything else throws InvalidOperationException naming the offending tool type. Matches the Python spec's unsupported-tools-raise-ValueError contract.
- New unit tests: FoundryChatClientVectorStoreTests (22 tests covering all four helpers across the three FoundryChatClient construction modes plus validation and cancellation), FoundryPromptAgentConverterTests (16 tests covering both extension entry points across mode 1 synthesis, mode 2 cached and fetched paths, all failure modes, and a Python-parity guard asserting both extensions produce equivalent definitions for equivalent inputs), plus four new tests in FoundryChatClientTests for the mode 3 AIProjectClient materialization.

* Stop building duplicate ProjectOpenAIClient in FoundryAgent agent-endpoint ctor

After Plan #2's mode-3 AIProjectClient materialization, the inner FoundryChatClient already exposes a project-level AIProjectClient (via GetService) that internally provides the project-level ProjectOpenAIClient via GetProjectOpenAIClient(). FoundryAgent's agent-endpoint constructor was still independently constructing a second project-level ProjectOpenAIClient via the now-redundant CreateProjectLevelOpenAIClientFromAgentEndpoint helper — two handles to the same logical resource.

Refactor: the agent-endpoint constructor now reads the inner FoundryChatClient's materialized AIProjectClient via base.GetService(typeof(AIProjectClient)) and derives the project-level ProjectOpenAIClient from it. The dead helper on both FoundryAgent (private static wrapper) and FoundryChatClient (the actual implementation) is removed. The user-supplied per-agent ClientPipelineOptions primitives (Transport, RetryPolicy, NetworkTimeout, UserAgentApplicationId) are propagated into the materialized AIProjectClientOptions so test-injected transports and explicit retry / timeout / user-agent settings reach the project-level pipeline — preserving the behavior the dead helper used to provide.

Updated AgentEndpointConstructor_GetServiceAIProjectClient_ReturnsNull to its now-correct counterpart AgentEndpointConstructor_GetServiceAIProjectClient_ReturnsNonNull, since after Plan #2 the agent-endpoint ctor surfaces a non-null AIProjectClient (per user direction in Plan #2 Q2).

* Strip duplicated AIProjectClient/ProjectOpenAIClient state from FoundryAgent

Both _aiProjectClient and _projectOpenAIClient fields on FoundryAgent were redundant:

- _aiProjectClient: FoundryAgent's GetService<AIProjectClient> override returned this field, but DelegatingAIAgent.GetService → ChatClientAgent.GetService → FoundryChatClient.GetService<AIProjectClient> already returns the same instance through the delegating chain. Field + override are pure duplication.

- _projectOpenAIClient: only used by FoundryAgent's own GetService<ProjectOpenAIClient> override and by CreateConversationSessionAsync. Per user direction, ProjectOpenAIClient is no longer exposed via GetService on either FoundryChatClient or FoundryAgent — callers retrieve it from the AIProjectClient themselves (aiProjectClient.GetProjectOpenAIClient()) the same way the framework does internally. This eliminates the mode-3 asymmetry where the chat client's stored ProjectOpenAIClient was per-agent (URL /agents/{name}/endpoint/protocols/openai) while the agent's was project-level.

Refactor:
- Delete both fields on FoundryAgent and the GetService override.
- Delete the ProjectOpenAIClient branch from FoundryChatClient.GetService.
- CreateConversationSessionAsync now resolves AIProjectClient at call time via this.GetService<AIProjectClient>() and derives the conversations client from it.
- Update FoundryChatClient tests that asserted on GetService<ProjectOpenAIClient> to assert Null (deliberate removal).
- Update FoundryAgent tests AgentEndpointConstructor_GetServiceProjectOpenAIClient_ReturnsNonNull and ProjectEndpointConstructor_GetServiceProjectOpenAIClient_ReturnsNonNull to ...ReturnsNull, and rewrite AgentEndpointConstructor_PropagatesUserAgentApplicationId_ToProjectLevelClient to look up AIProjectClient instead.

No production code (only tests) referenced GetService<ProjectOpenAIClient>, so this is a safe surface reduction. Net: 30 insertions, 61 deletions; FoundryAgent shrinks to a pure delegator with only the two convenience methods (CreateSessionAsync, CreateConversationSessionAsync) on top of the delegating chain.

* Rename FoundryChatClient.HostedAgentName to AgentName and populate it for mode 2

The previous name implied a mode 3 only property tied to the hosted-agent endpoint URL. Today only hosted endpoints surface this name, but conceptually an agent name exists for every server-side agent the client talks to. Renaming to AgentName makes the property general-purpose and ready for future modes where the same chat client may target other server-side agent shapes that are not necessarily 'hosted'.

Mode 2 (server-side agent reference) now mirrors AgentReference.Name into AgentName so callers have a uniform handle regardless of construction mode:

* Mode 1 (pure responses): AgentName is null. There is no agent.
* Mode 2 (AgentReference): AgentName == AgentReference.Name.
* Mode 3 (agent endpoint URL): AgentName is parsed from the URL segment as before.

Converter discriminator update: FoundryPromptAgentConverter previously used 'HostedAgentName is not null' to detect mode 3 and reject it. Now that mode 2 also populates AgentName, the mode 3 guard moves to the end of the resolution chain and uses the unambiguous 'AgentName is set AND no AgentReference exists' test. The user-visible error message and behavior are preserved.

Dead-state cleanup spotted during format verify:

* IDE0052 surfaced that FoundryChatClient._projectOpenAIClient is never read since the prior refactor stopped exposing ProjectOpenAIClient via GetService and rewired CreateConversationSessionAsync to resolve the AIProjectClient through the delegating chain. The field is deleted and its three ctor assignments removed.
* HostedAgentEndpointInner.PerAgentClient only existed to plumb the per-agent ProjectOpenAIClient into that now-deleted field, so the property and its ctor parameter are removed. The local 'perAgentClient' variable inside BuildHostedAgentEndpointInner is still needed to derive the inner IChatClient, but no longer escapes the helper.

Tests:

* Mode1_PureResponses_ReturnsNullForAgentSpecificServices now also asserts AgentName is null.
* New Mode2_AgentReference_PopulatesAgentNameFromAgentReference asserts the mode 2 mirror.
* Mode3_HostedAgentEndpoint_ParsesAgentNameFromUrl renamed assertion target HostedAgentName to AgentName.

Verification: 335/335 net10.0, 273/273 net472 Foundry unit; 229/229 Foundry.Hosting unit; format-verify (WSL2 + Docker mcr.microsoft.com/dotnet/sdk:10.0) clean on Microsoft.Agents.AI.Foundry.

* Adopt canonical mode names: Responses Agent, Prompt Agent, Agent Endpoint

Three FoundryChatClient construction modes now have one canonical noun used everywhere.

* Responses Agent (Mode 1): inline ChatClientAgent, project-level Responses API, no server-side def.
* Prompt Agent (Mode 2): server-side ProjectsAgentDefinition invoked by AgentReference.
* Agent Endpoint (Mode 3): per-agent URL /agents/{name}/endpoint/protocols/openai. Hosted-or-not.

'Hosted' stays the kind of agent (Microsoft.Agents.AI.Foundry.Hosting). Not synonym of Mode 3.

Rings:
1. XML docs + error messages use canonical names. en-GB to en-US: centralises, synthesise.
2. HostedAgentEndpointInner -> AgentEndpointInner, BuildHostedAgentEndpointInner -> BuildAgentEndpointInner.
3. Tests: Mode1_PureResponses_* -> Mode1_ResponsesAgent_*, Mode2_AgentReference_* -> Mode2_PromptAgent_*, Mode3_HostedAgentEndpoint_* -> Mode3_AgentEndpoint_*.

Pure rename. No behavior change. 335/335 net10 + 273/273 net472 unit, format clean.

* Address PR #5940 design feedback (Q-A through Q-F)

Q-A: poll vector store til status leaves InProgress before return. Exp backoff 250ms-2s. Honor cancel.
Q-B: try/catch upload loop. Mid-fail = best-effort DeleteFileAsync on already-uploaded ids. Swallow cleanup errors.
Q-C: pinned AgentReference.Version uses GetAgentVersionAsync. Empty/whitespace/'latest' = GetLatest path.
Q-D: HostedAgentUserAgentPolicy detects existing combined 'foundry-hosting/...' segment. No double prefix.
Q-E: mode-3 vector-store test uses fake transport. No DNS to example.com.
Q-F: no shim. Class always [Experimental] (since 8015e00f5, before dotnet-1.0.0). No compat contract. Callers rename to AIProjectClientExtensions.

Rebase onto origin/main reconciliation: aad20c2b3 added public AsAIAgent(this AIProjectClient, Uri agentEndpoint, ...) extension that calls an internal FoundryAgent(AIProjectClient, Uri, ...) ctor. Reintroduced that ctor + a new FoundryChatClient(AIProjectClient, Uri, ProjectOpenAIClientOptions?) overload that reuses the supplied AIProjectClient's pipeline (via GetProjectResponsesClientForAgentEndpoint) instead of stamping a fresh credential.

Verified: 346/346 net10 + 284/284 net472 Foundry unit, 230/230 Foundry.Hosting unit, format clean.

* Add FoundryAgent helper extensions: UploadFile/DeleteFile/CreateVectorStore/DeleteVectorStore

4 thin forwarders on FoundryAgent that route to the inner FoundryChatClient's helpers via agent.GetService<FoundryChatClient>().X(). Live in existing FoundryAgentExtensions.cs alongside ToPromptAgentAsync.

Throws InvalidOperationException when agent does not expose a FoundryChatClient via GetService (same pattern as ToPromptAgentAsync).

Unit tests: FoundryAgentExtensionsTests covers all 4 forwarders + null-agent ArgumentNullException for each. 8 new tests, 354/354 net10 + 292/292 net472.

Integration tests: parallel FoundryAgentExtensionsTests under Foundry.IntegrationTests mirrors the existing CreateAgent_CreatesAgentWithVectorStoresAsync shape (upload -> create vector store -> FileSearch tool answers question -> cleanup), but routes every helper call through the new FoundryAgent extensions. 4 new IT tests, all verified pass live against the real Foundry project (12-30s each). Skipped by default like the existing vector-store IT.

* Address Sergey's PR review comments

#1 (FoundryAgent.cs:139): drop unused aiProjectClient param from internal FoundryAgent(AIProjectClient, ChatClientAgent) ctor. Was discarded after null-check. Inner FoundryChatClient already surfaces AIProjectClient via GetService. 3 call sites in AIProjectClientExtensions updated.

#2 (FoundryChatClient.cs:376): add pollingTimeout param to CreateVectorStoreAsync. Defaults to 5 min, configurable, Timeout.InfiniteTimeSpan disables. Throws TimeoutException with vector store id and elapsed seconds when bound exceeded. CancellationToken still wins. New unit test PollingTimeout_ThrowsTimeoutExceptionAsync. FoundryAgentExtensions forwarder updated to plumb the new param.

Verified: 355/355 net10 + 293/293 net472 Foundry unit, 230/230 Foundry.Hosting unit, format clean.
2026-05-21 10:05:58 +00:00
dd1e615dad .NET: Add A2AAgentOptions and align A2AAgent constructors with ChatClientAgent pattern (#5954)
* .NET: Add A2AAgentOptions and align A2AAgent constructors with ChatClientAgent pattern

Adds a new A2AAgentOptions class (Id, Name, Description, Clone) and an options-based constructor on A2AAgent, mirroring ChatClientAgent/ChatClientAgentOptions. The existing parameter-based constructor is preserved for backward compatibility and now delegates to the options-based one.

Extension methods are extended with options-based overloads:

- A2AClientExtensions.AsAIAgent(IA2AClient, A2AAgentOptions, ...)

- A2AAgentCardExtensions.AsAIAgent(AgentCard, A2AAgentOptions, ...)

- A2ACardResolverExtensions.GetAIAgentAsync(A2ACardResolver, A2AAgentOptions, ...)

For card-based creation, user-supplied options override values from the agent card; Name and Description fall back to card values when not set.

Options are cloned when stored on the agent to prevent post-construction mutation, matching the ChatClientAgent pattern.

Resolves #5870.

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

* Address PR review comments

- Add Throw.IfNull(client) in A2AClientExtensions.AsAIAgent

- Add Throw.IfNull(card) in A2AAgentCardExtensions.AsAIAgent

- Clarify httpClient docs in A2ACardResolverExtensions.GetAIAgentAsync: it applies to the created A2A client, not to card discovery

- Rename test methods from GetAIAgent_* to AsAIAgent_* to match the API under test

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 10:05:24 +00:00
Peter IbekweandGitHub 3ebbdb01b4 .NET: Delegate MCP ContentBlock to AIContent conversion to the MCP SDK (#5903)
* Add sample for invoking Foundry Toolbox tools from declarative workflows

* Addressed initial PR comments.

* Delegate MCP ContentBlock to AIContent conversion to the MCP SDK

* Addressed additional properties metadata in the conversion fallback.
2026-05-18 20:39:56 +00:00
Roger BarretoandGitHub aad20c2b33 .NET: Bump Azure.AI.Projects to 2.1.0-beta.2 and add agent-endpoint AsAIAgent path (#5899)
* .NET: Bump Azure.AI.Projects to 2.1.0-beta.2 and add agent-endpoint AsAIAgent path

Bumps Azure.AI.Projects to 2.1.0-beta.2 with the matching transitive pins (Azure.Core 1.55.0, System.ClientModel 1.11.0).

Foundry agent endpoint plumbing:
* FoundryAgent now routes the agent-endpoint constructor through the new GetProjectResponsesClientForAgentEndpoint helper.
* Adds an internal FoundryAgent ctor that takes an existing AIProjectClient plus a parsed agent endpoint so the public extension does not need to construct a second project client.
* Adds public AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) extension. This is the path consumer samples are expected to use for hosted agents because version selection happens server-side.
* Trims the dangling "If you want to construct a FoundryAgent against a project endpoint..." sentence from ParseAgentEndpoint.

Unit tests:
* Four new tests in AzureAIProjectChatClientExtensionsTests cover the AIProjectClient.AsAIAgent(Uri agentEndpoint, ...) overload. 263/263 pass.

Consumer samples (Using-Samples):
* SimpleAgent and SessionFilesClient now read AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_AGENT_NAME (both required, throw on missing), derive the agent endpoint with new Uri($"{projectEndpoint}/agents/{agentName}/endpoint/protocols/openai"), then call aiProjectClient.AsAIAgent(agentEndpoint, ...).
* SessionFilesClient README updated.

Contributor samples (responses/*):
* New HostedContributorRouteExtensions.MapDevTemporaryLocalAgentEndpoint() wildcard route extension so localhost contributor servers accept the per-agent OpenAI endpoint shape the production Hosted runtime exposes.
* All 11 contributor Program.cs files call MapDevTemporaryLocalAgentEndpoint() with a contributor-only warning comment.
* Hosted-Files and Hosted-AzureSearchRag were importing Hosted_Shared_Contributor_Setup but never calling AddDevTemporaryLocalContributorSetup(). Both now call it so HostedSessionIsolationKeyProvider resolves correctly in dev.
* Hosted-AzureSearchRag, Hosted-Files, Hosted-MemoryAgent csprojs drop stale VersionOverride="2.1.0-beta.1" pins.
* Hosted-AzureSearchRag and Hosted-Files csprojs add ProjectReference to Hosted_Shared_Contributor_Setup.
* Hosted-Observability/.dockerignore removed the out/ exclusion that was blocking COPY out/ . in Dockerfile.contributor.

Verified:
* Full solution-scoped build of changed projects: green.
* Scoped CI-parity dotnet format via WSL2 + Docker (mcr.microsoft.com/dotnet/sdk:10.0) over every changed csproj: clean.
* Foundry unit tests: 263/263.
* Contributor docker smoke for 8 hosted samples (publish + docker build + docker run + curl POST to the wildcard route): HTTP 200 / 500 with route matched.
* End-to-end smoke against the real Azure Foundry project with a fresh bearer token: Hosted-Files contributor container served HTTP 200, the agent invoked ListBundledFiles, and returned the expected file name.

* Address PR review: forward pipeline settings; add UTs

- CreateProjectClientOptions also carries RetryPolicy, NetworkTimeout, ClientLoggingOptions, MessageLoggingPolicy (was Transport+UserAgentApplicationId only).

- Make CreateProjectClientOptions internal so tests can verify the copy directly.

- Add AsAIAgent(Uri) UTs covering tools forwarding to inner ChatOptions and null tools handling.

- Add CreateProjectClientOptions UTs covering null caller and full pipeline-settings copy.
2026-05-18 20:20:56 +00:00
westeyandGitHub eff36b504e .NET: Add otel file logging and switch samples to projects client with store=true (#5924)
* Add otel file logging and switch samples to projects client with store=true

* Fix formatting and remove rogue file
2026-05-18 17:39:29 +00:00
westeyandGitHub 7cea5e162a .NET: Require TODO finish reason and rename SubAgents to BackgroundAgents (#5902)
* Require TODO finish reason and rename SubAgents to BackgroundAgents

* Address PR comments
2026-05-18 15:37:25 +00:00
ddc0fcf81f .NET: Adding default providers and tools to HarnessAgent (#5896)
* Adding default providers and tools to HarnessAgent

* Address PR comments

* Add further comments to clarify certain setings.

* Apply suggestion from @SergeyMenshykh

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
2026-05-18 10:07:16 +00:00
Yufeng HeandGitHub a60e541c9a .NET: fix: avoid AGUI tool result message id collisions (#5800)
* fix: avoid AGUI tool result message id collisions

* fix: split mixed tool result message ids
2026-05-15 21:52:25 +00:00
westeyandGitHub c885ca3d7a .NET: Fix bug in store-false helper to ensure addition rather than replacement (#5895)
* Fix bug in store-false helper to ensure addition rather than replacement

* Address PR comments
2026-05-15 15:51:45 +00:00
Roger BarretoandGitHub ad95f2f2fa .NET: Add Hosted-MemoryAgent sample with isolation key plumbing (#5692) (#5702)
* .NET: Add Hosted-MemoryAgent sample with isolation key plumbing (#5692)

Adds HostedSessionContext + HostedSessionIsolationKeyProvider in Microsoft.Agents.AI.Foundry.Hosting so AIContextProviders (notably FoundryMemoryProvider) can scope per user via the platform's x-agent-user-isolation-key / x-agent-chat-isolation-key headers.

- New types: HostedSessionContext (sealed), HostedSessionContextExtensions (public Get, internal Set), abstract HostedSessionIsolationKeyProvider (async), internal PlatformHostedSessionIsolationKeyProvider mapping ResponseContext.Isolation.

- AgentFrameworkResponseHandler now resolves the provider, tags fresh sessions, and validates resumed sessions against the live request (strict 403 'Hosted session identity context mismatch' on any mismatch; 500 on null keys).

- New shared sample project Hosted_Shared_Contributor_Setup hosts DevTemporaryTokenCredential and DevTemporaryLocalSessionIsolationKeyProvider plus AddDevTemporaryLocalContributorSetup. All 9 existing responses samples migrated to consume it so local runs keep working under the strict isolation contract.

- New Hosted-MemoryAgent sample: travel assistant wired through FoundryMemoryProvider with stateInitializer reading session.GetHostedContext().UserId. Includes Dockerfile, smoke.ps1, agent.yaml/manifest.

- New IT scenario 'memory' in Foundry.Hosting.IntegrationTests + MemoryHostedAgentFixture + MemoryHostedAgentTests. Verified end to end against the tao Foundry project.

- ADR 0026 captures the design tree.

* Address PR review feedback

- Dockerfile: add header noting it targets NuGet builds; contributors must use Dockerfile.contributor for ProjectReference source builds.

- PlatformHostedSessionIsolationKeyProvider: doc said 'returns context with empty values'; corrected to 'returns null' which the handler treats as 500.

- FakeHostedSessionIsolationKeyProvider: doc clarifies that null configurations are allowed for testing the handler error path.

- HostedSessionContextExtensions.SetHostedContext: enforce write-once with InvalidOperationException; doc + xml exception updated.

- AgentFrameworkResponseHandler: cache PlatformHostedSessionIsolationKeyProvider as static readonly to avoid per-request allocation.

- MemoryHostedAgentTests: tighten waits from 20s to 5s (FoundryMemoryProvider defaults UpdateDelay=0; ingestion ~3s).

- Sample Program.cs imports reordered to satisfy IDE0005.

* Add HostedFoundryMemoryProviderScopes built-in helpers (#5692)

Addresses review feedback from @lokitoth on Hosted-MemoryAgent/Program.cs:54.

- New HostedFoundryMemoryProviderScopes static class with PerUser, PerChat, PerUserAndChat factories returning Func<AgentSession?, FoundryMemoryProvider.State>.

- All helpers throw InvalidOperationException when GetHostedContext() is null, with a message pointing at writing a custom stateInitializer for non-hosted scenarios.

- New HostedFoundryMemoryScope enum and AddHostedFoundryMemoryProvider DI extension (two overloads: explicit AIProjectClient and DI-resolved). Singleton lifetime. Default scope = PerUser.

- Hosted-MemoryAgent sample and the memory IT scenario container both swap their inline lambdas for HostedFoundryMemoryProviderScopes.PerUser().

- 14 new unit tests (241/241 hosting unit tests pass).

* Replace HostedFoundryMemoryScope enum with Func<...> parameter (#5692)

Address PR review feedback from @westey-m: enums are a breaking-change hazard when extended, and the enum was redundant with the existing HostedFoundryMemoryProviderScopes static class.

- Delete HostedFoundryMemoryScope.cs.

- AddHostedFoundryMemoryProvider DI extensions now take Func<AgentSession?, FoundryMemoryProvider.State>? stateInitializer = null. When null, default to HostedFoundryMemoryProviderScopes.PerUser().

- Callers pick a built-in helper (PerUser/PerChat/PerUserAndChat) or pass a custom delegate. New built-ins are a single static method addition with zero impact on existing callers.

- Tests updated; 244/244 hosting unit tests pass.

* Fix isolation context resume for externally-created conversations (#5692)

Branch on the session's existing hosted-context (not on conversation_id presence) so a conversation provisioned externally (e.g. via conversations.CreateProjectConversationAsync) is treated as fresh on first hosted-agent request and stamped, rather than rejected with 403 hosted_session_identity_mismatch. Strict equality is preserved on real resume of an already-stamped session.

Also tighten dotnet/global.json to version 10.0.204 + rollForward latestPatch so local builds match the CI Docker image SDK and avoid 10.0.300 dotnet format stripping required usings.

* Revert global.json SDK pin to upstream (#5692)

The 10.0.204 + latestPatch pin from the previous commit broke the dotnet-format CI job (hostfxr_resolve_sdk2 could not find a compatible SDK in the mcr.microsoft.com/dotnet/sdk:10.0 image). Restore upstream 10.0.200 + minor; local Release builds with SDK 10.0.300 should set GITHUB_ACTIONS=true to bypass the auto-format-on-build target.
2026-05-15 05:42:12 +00:00
CopilotGitHublokitothcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Jacob Alber
2ef20cd0aa .NET: Add Magentic E2E workflow coverage (#5833)
* Add E2E test plan for Magentic orchestrator

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/96d76349-1ffd-482b-a3ee-ed208778b1bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add MagenticOrchestrationTests.cs scaffold for Magentic E2E tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/44a4fd8a-3828-40e5-9435-90381aeffdb8

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Fix MagenticOrchestrator output declaration and add first E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/322c9e2d-59bc-42ad-9a1e-f6fd4c866b26

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add plan review test and event emission tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/322c9e2d-59bc-42ad-9a1e-f6fd4c866b26

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add next speaker validation test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/322c9e2d-59bc-42ad-9a1e-f6fd4c866b26

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Magentic E2E implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/b2c60ce7-4d05-4a0d-b05d-d4284f5b7bb3

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add PlanSignoff_Disabled_Proceeds_Immediately E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add NextSpeaker_Empty_Falls_Back_To_First E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Task_Completes_After_Multiple_Rounds E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add PlanReview_Revised_Triggers_Replan E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add MaxRoundLimit_Terminates_Workflow E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add MaxStallCount_Triggers_Reset E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update MagenticE2E_ImplementationReview.md with full coverage status

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6e8bca46-448d-4f21-a7e9-240179571970

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite Magentic E2E implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/1f878ef4-61b0-410a-a8bc-ebf618b3e5de

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add MaxResetLimit_Terminates_Workflow E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/aba19507-7c7e-40dd-850d-d1fabb5dfa65

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add PlanReview_On_Stall_Replan E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/aba19507-7c7e-40dd-850d-d1fabb5dfa65

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Instruction_Message_Sent_When_Present E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/aba19507-7c7e-40dd-850d-d1fabb5dfa65

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update ImplementationReview.md to reflect 14 tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/aba19507-7c7e-40dd-850d-d1fabb5dfa65

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite Magentic E2E implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6fe88a80-2e05-40d5-9539-ca7c59b9022b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add ProgressLedger_Retry_On_Parse_Failure E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/125f6628-6b3b-4c51-9a51-ae84baece6bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add ProgressLedger_Max_Retries_Triggers_Reset E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/125f6628-6b3b-4c51-9a51-ae84baece6bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Stall_NoProgress_Increments_StallCount E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/125f6628-6b3b-4c51-9a51-ae84baece6bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add PlanReview_Multiple_Revisions E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/125f6628-6b3b-4c51-9a51-ae84baece6bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update ImplementationReview.md to reflect 18 tests and new coverage

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/125f6628-6b3b-4c51-9a51-ae84baece6bb

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite Magentic E2E implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/21f3b1ae-183e-4fea-99ad-14efc19f084d

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Preserve IsStalled on stall-triggered plan review requests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/1b9e74e8-69e1-43f2-8467-c5ba963c2622

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rename isStalled parameter to replanAfterStall for clarity

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/1b9e74e8-69e1-43f2-8467-c5ba963c2622

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Task_Delegates_To_Correct_Agent E2E test with multi-participant routing assertion

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9b34e409-61b8-4650-ae55-34efad034ed0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Progress_Made_Decrements_StallCount E2E test verifying stall count decrement avoids reset

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9b34e409-61b8-4650-ae55-34efad034ed0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add Consecutive_Stalls_Trigger_Reset E2E test for multi-stall threshold reset

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9b34e409-61b8-4650-ae55-34efad034ed0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Magentic E2E: preserve IsStalled on stall-triggered plan reviews, add routing/stall tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9b34e409-61b8-4650-ae55-34efad034ed0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Fix replan-on-every-turn: skip plan on agent return; align StallCount to > (match Python)

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/43e46b0d-4263-4353-856a-c3730abb1734

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update implementation review doc for replan-fix and stall threshold alignment

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/43e46b0d-4263-4353-856a-c3730abb1734

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite Magentic E2E implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/3d15763b-3a68-488e-9412-3fa280e083c0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update stall docs to use > semantics, skip checkpoint-state tests, simplify NextSpeaker fallback test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/cc9ea5a8-84d8-4b6d-bb60-ac9619824d81

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite Magentic implementation review

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/ed87670a-bf4d-4ba5-a2f3-395a2eead9de

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add empty-team validation to MagenticWorkflowBuilder.Build() and E2E test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/e490fdf7-f107-4fde-ba1f-efdfd9a729c6

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add IsTerminated guard to TakeTurnAsync and post-termination rejection test

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/e490fdf7-f107-4fde-ba1f-efdfd9a729c6

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Rewrite ImplementationReview.md with final 23-test status

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/e490fdf7-f107-4fde-ba1f-efdfd9a729c6

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add PR description markdown

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/df9b4579-10c3-4bfb-927e-da3a0e70009e

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Remove temporary markdown files

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/b3e67553-a3a3-4282-98f2-afd8ad7a6b5d

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Fix IDE1006: add Async suffix to async test methods in MagenticOrchestrationTests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/629fcc07-865e-4832-9e59-ea13df561c5a

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Update error messages per review comments in MagenticOrchestrator and MagenticWorkflowBuilder

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/053e5ded-81e3-4e56-acf1-2a8a939a04b0

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Escape JSON string values in CreateProgressLedgerResponse test helper

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/ec610c61-0a14-44e2-82fd-1cf35e85d6cc

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-14 19:53:07 +00:00
3256550c55 .NET: fix: allow naming handoff workflows (#5799)
* fix: allow naming handoff workflows

* Only set name/description if not NullOrWhitespace

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Jacob Alber <jalber@fernir.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-14 17:10:27 +00:00
CopilotGitHublokitothcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Jacob Alber
190ca75b6a .NET: Add Workflow Builder Specialized Edge tests (#5826)
* Add workflow builder edge tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/3c3d5324-cdcd-4a38-8c67-94e4e78e29c5

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Strengthen workflow edge helper tests

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Normalize edge helper bad input validation

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Clarify edge helper target validation

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Use explicit target parameter names

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Document workflow edge test helpers

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Clarify null element validation messages

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add repeated chain executor coverage

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Preserve Throw helper validation style

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Cover empty switch case targets

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Relax builder null assertion parameter checks

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/af831ee2-0a99-4427-9ffd-a3b5022c1b3b

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Inline ValidateTargets into call sites

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/cb9a6a6a-02c7-41a8-a4b4-da16ad62ef86

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Refactor ForwardExcept with TFM-specialized TryGetNonEnumeratedCount

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/b081f61f-93ce-45dc-abbd-82c465395470

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Use TFM-specialized count check: TryGetNonEnumeratedCount for NET6+, ICollection pattern for NETFX

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/8ec28a43-e7b7-456e-8d8e-921511b4accc

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Apply TFM-specialized count check to ForwardMessage as well

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/9238ea32-a3e8-4b83-9683-484ad400071f

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Address review feedback: simplify Throw.IfNull in SwitchBuilder per westey-m suggestion

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/299950fd-4457-47f3-a373-f65d601b7ea5

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Use indexed parameter name in SwitchBuilder Throw.IfNull: executors[index]

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/c5655707-5b0b-44f3-98a9-5f3961e32cfe

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Revert #if NET6_0_OR_GREATER back to #if NET; inline executorIndex++

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/c5655707-5b0b-44f3-98a9-5f3961e32cfe

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

* Add comment explaining unusual Throw.IfNull use for null elements inside collection

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/c5655707-5b0b-44f3-98a9-5f3961e32cfe

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-14 16:23:41 +00:00
Peter IbekweandGitHub 189e64bfdd .NET: Add sample for invoking Foundry Toolbox tools from declarative workflows (#5829)
* Add sample for invoking Foundry Toolbox tools from declarative workflows

* Addressed initial PR comments.
2026-05-14 15:30:48 +00:00
westeyandGitHub 3047ad3066 .NET: Harness console refactoring (#5811)
* Restructure harness console so that reactive app is the entry point

* Further refactoring to split tool formatters, improve UX, make console configurable and fix bugs

* Address PR comments.

* UX tweak

* Fix streaming text bug

* Address PR comments.
2026-05-14 15:22:11 +00:00
westeyandGitHub 4e65fabafc .NET: Filestore improvements (#5842)
* Filestore improvements

* Address PR comments
2026-05-14 11:09:01 +00:00
741259476f Fix CA1873 in DevUI by using LoggerMessage source generator (#5831)
Replaces two ILogger.LogWarning(string, params object?[]) calls in DevUIAuthFilter and DevUIExtensions with allocation-free [LoggerMessage] partial methods on a new internal DevUILog class. Preserves original message templates and structured property names ({RemoteIp}, {EnvVar}).

Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-13 22:48:49 +00:00
981726cc15 .NET: feat(evals): add ground_truth/expected_output support for workflow evaluation (#5755)
* .NET: feat(evals): add ground_truth/expected_output support for workflow eval

Brings .NET to parity with Python PR #5234 for issue #5135:

- Add expectedOutput parameter to Run.EvaluateAsync (workflow) and stamp on the overall EvalItem.ExpectedOutput.
- Map EvalItem.ExpectedOutput -> ground_truth in the Foundry JSONL payload, item_schema, and data_mapping for similarity.
- Add GroundTruthEvaluators set (currently builtin.similarity) and a FindMissingGroundTruthEvaluators helper.
- Fail fast with InvalidOperationException when a ground-truth evaluator is selected but no item provides an ExpectedOutput, instead of surfacing a remote provider error.
- Add tests in FoundryEvalConverterTests and WorkflowEvaluationTests.
- Add Evaluation_WorkflowExpectedOutputs sample (workflow + Foundry similarity).

Fixes microsoft/agent-framework#5135 (.NET side).

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

* Address review: relax BuildOverallItem events to IReadOnlyList<WorkflowEvent>

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

* Sample: disable per-agent breakdown when using reference-based evaluator

Per-agent EvalItems are intentionally left without ExpectedOutput, so the new fail-fast validation in FoundryEvals would throw when Similarity is invoked for per-agent items. Pass includePerAgent: false in the workflow + similarity sample, and document this gotcha in the EvaluateAsync XML doc.

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

* Fix BuildOverallItem: fall back to last ExecutorCompletedEvent

AgentResponseEvent is only emitted when AIAgentHostOptions.EmitAgentResponseEvents is enabled, which is not the default for WorkflowBuilder(agent).AddEdge(...). When it is absent, fall back to the last non-internal ExecutorCompletedEvent whose Data is an AgentResponse / ChatMessage / string so the overall EvalItem (and any expectedOutput) is produced. Without this, samples wired up the standard way returned 0 evaluation items.

Update test to cover the fallback path.

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

* Sample: enable EmitAgentResponseEvents; eval throws clear error when no overall response found

Root cause of '0 results': AIAgentHostExecutor only emits AgentResponseEvent when AIAgentHostOptions.EmitAgentResponseEvents is true (default false). For ordinary AIAgent executors the runtime's ExecutorCompletedEvent.Data is null, so the prior fallback couldn't find a final response either.

Sample now builds executors with EmitAgentResponseEvents=true via BindAsExecutor(hostOptions). EvaluateAsync now throws InvalidOperationException with a remediation hint when the user supplies expectedOutput but no overall final response can be located, instead of silently returning 0/0.

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

* Guard against null sample/error/usage/datasource_item in ParseDetailedItem

Foundry eval responses can have these properties present with JSON null
or non-object values, which caused JsonElement.TryGetProperty to throw
'requires Object, has Null'. Check ValueKind == Object before drilling in.

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

* Address PR review: reorder expectedOutput, tighten ground-truth check, add fail-fast test

* WorkflowEvaluationExtensions.EvaluateAsync: move 'expectedOutput' to
  after 'splitter' so the original positional contract of (splitter,
  cancellationToken) is preserved for existing callers.
* FoundryEvals: require ALL items to carry ExpectedOutput when a
  ground-truth evaluator is selected (e.g. similarity), not just any.
  Reference-based evaluators score per-item, so a single missing GT
  would still surface as a provider-side validation error. Updated
  fail-fast message accordingly.
* WorkflowEvaluationTests: add EvaluateAsync_WithExpectedOutputButNoFinalResponse_ThrowsAsync
  to verify the InvalidOperationException is thrown (and that the
  message mentions EmitAgentResponseEvents).

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

* Fail-fast on missing overall item regardless of expectedOutput; harden BuildOverallItem default

* EvaluateAsync now throws InvalidOperationException whenever 'includeOverall'
  is requested but BuildOverallItem cannot produce an item, instead of only
  when 'expectedOutput' is supplied. Same misconfiguration (agents not bound
  with EmitAgentResponseEvents) used to silently return empty results — now
  it surfaces a clear, actionable error in both cases.
* BuildOverallItem switch default now throws instead of returning null. The
  preceding for-loop already constrains Data to AgentResponse/ChatMessage/
  string, so reaching default would indicate a contract drift; throw to make
  the bug visible.
* Test renamed and broadened to verify the throw fires without expectedOutput.

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

---------

Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-13 19:03:27 +00:00
9b9604ce18 fix: avoid mutating handoff message roles (#5808)
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-13 18:52:19 +00:00
CopilotGitHubrogerbarretocopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
37a043a797 .NET: [Breaking Change] Auto-wire ChatClient with OpenTelemetryChatClient in OpenTelemetryAgent (#5750)
* Initial plan

* .NET: Auto-wire ChatClient with OpenTelemetryChatClient in OpenTelemetryAgent

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/96dd033a-0c48-4d3f-9148-324bfd436b75

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Address review: remove extension overload; honor UseProvidedChatClientAsIs; drop redundant check

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/6ac3f75d-eeb7-4811-8043-9a27511b0a8b

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Resolve ChatClientAgent via GetService before checking options/chat client

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/008d914d-8cbb-4e9f-81b6-f8c3c8bd8d04

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Split OpenTelemetryAgent ctor to preserve original (innerAgent, sourceName) signature

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/a890c9a7-0b77-40ab-802c-dfbf09f8c260

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Preserve base AgentRunOptions properties and avoid double-wrap on user factory

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/3afbf18c-de22-4236-a2f2-02ca1e98ae21

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* .NET: OpenTelemetryAgent normalize sourceName once and add OTEL wiring path coverage

Normalize the configured source name once in the constructor so the outer OpenTelemetryChatClient and the auto-wired inner OpenTelemetryChatClient always emit spans on the same ActivitySource. A caller passing an empty string previously produced agent-level spans on DefaultSourceName but auto-wired chat spans on the empty source, causing the chat spans to be silently dropped by exporters subscribed to the default source.

Tests added to cover the previously unexercised OTEL wiring branches:

- Ctor_NullOrEmptySourceName_AutoWiredChatClientUsesDefaultSource_Async (Theory: null and empty)

- AutoWireChatClient_PlainAgentRunOptions_PreservesContinuationToken_Async

- AutoWireChatClient_ChatClientAgentRunOptions_NoUserFactory_PreservesChatOptions_Async

- AutoWireChatClient_StreamingDisabled_DoesNotEmitChatSpan_Async

* .NET: Mark OpenTelemetryAgent autoWireChatClient ctor as [Experimental]

Annotate the new 3-arg OpenTelemetryAgent(AIAgent, string?, bool) constructor with [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] (MAAI001) so callers must explicitly opt in to the auto-wire toggle. The original 2-arg constructor stays non-experimental and delegates with autoWireChatClient: true; the delegating call is locally suppressed so the existing source compatibility surface is preserved.

* .NET: OpenTelemetryAgent address westey-m PR review

- Use string.IsNullOrWhiteSpace (not IsNullOrEmpty) when normalizing the constructor sourceName, so callers passing whitespace-only strings still land on OpenTelemetryConsts.DefaultSourceName instead of an unsubscribed ActivitySource.

- Fix the misleading pragma comment on the 2-arg ctor delegating call: auto-wiring is the new default, it does not preserve the original (pre-PR) behavior.

- Expand the GetRunOptionsWithChatClientWiring XML doc to spell out that a base AgentRunOptions (not ChatClientAgentRunOptions) is also accepted: it is converted to ChatClientAgentRunOptions with the auto-wire factory installed and base properties copied.

- Tests: extend the source-name normalization Theory with whitespace cases ('   ' and '\t'); add end-to-end coverage for plain AgentRunOptions over a real ChatClientAgent (sync + streaming) asserting the inner chat client is invoked and both invoke_agent + chat spans are emitted.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
2026-05-13 13:06:45 +00:00
westeyandGitHub f16cb9a118 .NET: Add harness agent package (#5782)
* Add harness agent package

* Fix formatting.

* Fix formatting.

* Update release filter

* Address PR comments.
2026-05-13 10:58:05 +00:00
4409b00b86 .NET: Feat/dotnet shell tool (#5604)
* feat(dotnet): add Microsoft.Agents.AI.Tools.Shell with LocalShellTool

Ports Python LocalShellTool to .NET as a new package (net8/9/10).

- Microsoft.Agents.AI.Tools.Shell: LocalShellTool, ShellPolicy (deny-list
  guardrail), ShellResolver (cross-OS pwsh/powershell/cmd vs bash/sh),
  ShellResult with head+tail truncation, timeout + process-tree kill,
  AsAIFunction with required-by-default human approval gate.
- Persistent mode via ShellSession (sentinel protocol over pwsh/bash).
- acknowledgeUnsafe parity gate matches the Python implementation.
- Auto-injected platform context in the AIFunction description so the
  LLM sees the active OS and shell at tool-discovery time.
- 17 xunit.v3 tests cover policy allow/deny, echo roundtrip, exit
  codes, timeout/kill, AsAIFunction shape + approval wrapping,
  persistent cwd/env carry-over, head+tail truncation, sentinel race.

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

* feat(shell): close Python parity gaps for LocalShellTool

Closes the .NET vs Python parity gaps identified in the competitive eval:

- Default mode flipped to ShellMode.Persistent (matches Python). Every
  call now reuses a long-lived shell so cd/exports/functions persist;
  pass mode: ShellMode.Stateless to opt out.
- New IShellExecutor interface — pluggable backend so future
  DockerShellTool / Hyperlight / SSH executors don't fork the framework.
  LocalShellTool implements it.
- Workdir confinement: confineWorkingDirectory (default true) re-anchors
  every persistent-mode command back to workingDirectory so a wandering
  cd in one call doesn't leak to the next. Mirrors Python _maybe_reanchor.
- Graceful interrupt on timeout: ShellSession sends SIGINT (POSIX) or
  Ctrl+C-on-stdin (Windows) before falling back to a hard close+respawn.
  Successfully-interrupted commands return exit 124 + TimedOut=true while
  preserving session state for the next call.
- cleanEnvironment opt-in: when true, only PATH/HOME/USER/USERNAME/
  USERPROFILE/SystemRoot/TEMP/TMP plus user-supplied vars are visible.
- shellArgv: IReadOnlyList<string> override accepted alongside the
  string shell binary param (mutually exclusive). Lets advanced callers
  inject flags like --rcfile or --login.
- Typed exceptions ShellTimeoutException and ShellExecutionException
  replace InvalidOperationException for launch / liveness failures.

Tests: 17 -> 23. New cases cover persistent-default ctor, mutually-
exclusive shell/shellArgv, confined re-anchor, confine-disabled leak,
clean-env strip, and IShellExecutor implementation. All green on net10.0.

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

* feat(shell): add DockerShellTool sandboxed shell tier

Ports the Python DockerShellTool to .NET. Mirrors the public surface of
LocalShellTool but executes commands inside an isolated container, where
the container is the security boundary. Stateless and persistent modes
both supported; persistent mode reuses ShellSession by launching
'docker exec -i <ctr> bash --noprofile --norc' as the long-lived REPL,
so the sentinel protocol works unchanged.

Defaults chosen for safety:
- --network none, --user 65534:65534 (nobody), --read-only root
- --cap-drop=ALL, --security-opt=no-new-privileges
- 512m memory cap, pids-limit 256, --tmpfs /tmp
- Optional host workdir mount, ro by default

Public surface:
- DockerShellTool ctor with image/container_name/mode/host_workdir/
  workdir/network/memory/pids_limit/user/read_only_root/extra_run_args/
  environment/policy/timeout/max_output_bytes/on_command/docker_binary
- StartAsync, CloseAsync, RunAsync, AsAIFunction, IShellExecutor impl
- IsAvailableAsync(binary) probe
- Static argv builders (BuildRunArgv, BuildExecArgv) — pure, side-
  effect free, so unit tests don't need a Docker daemon

AsAIFunction defaults to requireApproval: false (the container IS the
boundary). LocalShellTool keeps the opposite default.

Tests: 23 -> 35. 12 new tests cover argv builders, env/extra-args/host-
workdir flags, exec interactive vs stateless, container name uniqueness,
IShellExecutor implementation, AsAIFunction approval defaults, and
IsAvailableAsync false-path. None require Docker. Multi-TFM build
(net8/9/10) green.

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

* test(shell): add DockerShellTool integration tests

Adds 9 end-to-end tests that exercise DockerShellTool against a live
Docker (or Podman) daemon. Tests are tagged [Trait("Category",
"Integration")] and auto-skip via Assert.Skip when no daemon is
available, so they are CI-safe.

Coverage:
- IsAvailableAsync probe
- Persistent mode basic command + state preservation across calls
- --network none blocks outbound DNS
- --read-only root prevents writes outside /tmp; /tmp tmpfs is writable
- --user 65534:65534 (nobody) is in effect
- Stateless mode: env vars do not leak across calls
- HostWorkdir bind-mount + read-only enforcement
- Environment variables passed via -e

Tests use debian:stable-slim (alpine ships only busybox sh, which
ShellSession persistent bash REPL cannot drive).

Run locally:
  dotnet test --filter "Category=Integration"
or filter by class on the test exe directly.

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

* style(shell): apply dotnet format pass

- Whitespace and code-style fixes from `dotnet format` across both
  projects
- Convert all new files to UTF-8 with BOM and LF line endings
  (repo convention)
- Rename ShellSession statics to s_ prefix (IDE1006)
- Add Async suffix to async test methods (IDE1006)

No behavioral changes. All 44 tests still pass on net10.0; multi-TFM
build (net8/net9/net10) green. `dotnet format --verify-no-changes`
now reports clean for both projects.

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

* docs(shell): add DockerShellTool walkthrough with sequence diagrams

Explains the mental model (we shell out to the docker CLI; we never speak the engine API), the hardened docker run argv, persistent vs stateless lifecycles with mermaid sequence diagrams, the full agent-to-bash call ladder, and the failure modes.

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

* PR 5604 review fixes (group a): libc DllImport, namespace cleanup, policy-msg dedup

Three quick-win review comments on PR #5604:

1. ShellSession: the libc `killpg` P/Invoke was annotated with
   `DllImportSearchPath.System32`, a Windows-only loader hint that does
   nothing for libc.so on POSIX. Switched to `SafeDirectories` (CA5392
   /CA5393 clean) and added a comment noting the call site is gated to
   non-Windows.

2. DockerShellToolTests: replaced the fully-qualified
   `Extensions.AI.ApprovalRequiredAIFunction` with a `using
   Microsoft.Extensions.AI;` import and the bare type name, matching
   `LocalShellToolTests`.

3. LocalShellTool / DockerShellTool: `AsAIFunction`'s catch block was
   producing a doubled "Command blocked by policy: Command rejected by
   policy: ..." prefix because the `ShellPolicyException` message
   already starts with "Command rejected by policy". Now we return
   `ex.Message` directly.

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

* PR 5604 review fix (group b): add ShellKind.Sh for /bin/sh fallback

Review comment (#3): when /bin/bash is missing the resolver fell back to
/bin/sh but tagged it as ShellKind.Bash, so the launcher passed bash-only
flags --noprofile --norc to dash/ash/busybox, which interpret them as
positional script names.

Fix:

* Added ShellKind.Sh for minimal POSIX shells (sh, dash, ash, busybox).
* /bin/sh fallback is now tagged Sh.
* ClassifyKind maps "SH" / "DASH" / "ASH" / "BUSYBOX" binary names to Sh.
* StatelessArgvForCommand emits just `-c <command>` for Sh (no
  bash-only flags); PersistentArgv emits no flags at all.
* LocalShellTool's system-prompt builder describes Sh distinctly and
  warns the model away from bash-only constructs.

Tests: ShellResolverTests covers Sh/Bash classification through the
observable argv output (14 new theory cases). Total: 58/58.

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

* PR 5604 review fix (group d): honor timeout=null, add DefaultTimeout

Review comment (#5): both LocalShellTool and DockerShellTool documented
`timeout: null` as "disables timeouts" but the constructor coerced null
to 30 seconds, making the documented disable mechanism unreachable
through the public API.

Fix:

* Drop the `?? TimeSpan.FromSeconds(30)` coercion in both ctors.
  `_timeout` now faithfully reflects what the caller passed (null =
  disabled). The downstream CTS-construction sites already short-circuit
  on null, so no other code changes are required.
* Add `public static readonly TimeSpan DefaultTimeout` (30 s) on both
  tools so callers who want a bounded timeout can opt in explicitly.

Tests:

* New `RunAsync_NullTimeout_DoesNotTimeOutAsync` confirms a quick
  command runs to completion when the caller passes `timeout: null`.
* New `DefaultTimeout_IsThirtySeconds` documents the constant.

Behavioral note: this is a deliberate change-of-default. Callers that
previously omitted `timeout` and relied on the implicit 30 s now get
"no timeout". They should pass `LocalShellTool.DefaultTimeout` or
`DockerShellTool.DefaultTimeout` explicitly to preserve the prior
behavior.

Tests: 60/60 (44 baseline + 14 resolver + 2 new timeout tests).

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

* PR 5604 review fix (group e): smart requireApproval default for DockerShellTool

Review comment (#6, design): requireApproval: false baked in a
safety decision the type cannot prove on its own. Callers can
weaken any isolation knob (network, user, readOnlyRoot, mount,
extraRunArgs) and still get an unapproved tool by default.

Fix:

* New public IsHardenedConfiguration property returns true iff the
  effective config matches the safe defaults: network=="none",
  non-root user, read-only root, host mount (if any) read-only,
  no extra run args.
* AsAIFunction's requireApproval parameter is now bool? defaulting
  to null. When null, approval is enabled iff
  IsHardenedConfiguration is false. Pass false explicitly to opt
  out, or true to force.
* docker-shell-tool.md updated with the new approval matrix.

Tests: 4 new theory cases + 2 facts cover hardened-default,
relaxed-network, root-user, writable-root, extraRunArgs, and
explicit-opt-out branches. Total: 66/66.

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

* PR 5604 review fix (group c): wrap POSIX shell in setsid for correct killpg

Review comment (#1): killpg(proc.Id, SIGINT) only behaves like a
process-group signal when proc.Id IS a process group id. Since the
.NET launcher does not call setsid() / setpgid() itself, the spawned
shell inherits the agent host's process group — so killpg targeted
the wrong group and the cancel signal could leak to the agent.

Fix:

* On non-Windows, EnsureStartedAsync probes for setsid (well-known
  paths first, then PATH). When found it wraps the shell launch as
  `setsid <shell> <args...>` so the spawned shell becomes a session
  leader (PID == PGID).
* A new _isSessionLeader flag tracks whether the wrap succeeded.
* InterruptCurrentCommandAsync only calls killpg when
  _isSessionLeader is true. Without setsid, killpg on an unsuited
  PID could signal the agent itself, so we skip the fast path and
  let the caller's hard close-and-respawn handle the timeout.
* Windows behaviour is unchanged (Ctrl+C-via-stdin to pwsh).

No public-API changes; existing tests cover the interrupt path and
all 66/66 still pass.

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

* .Net: DockerShellTool design + caller-cancel container leak fixes (PR #5604)

Addresses three Copilot review findings on PR #5604.

Design (group f):
* StartAsync: change inner ResolvedShell from ShellKind.Bash to ShellKind.Sh.
  BuildExecArgv() already includes `--noprofile --norc` in ExtraArgv;
  Bash's PersistentArgv() was appending those flags a second time,
  yielding `bash --noprofile --norc --noprofile --norc`. Sh's
  PersistentArgv() returns Array.Empty so ExtraArgv is forwarded
  unchanged.
* BuildExecArgv: remove the dead `interactive: false` branch and the
  `interactive` parameter. The `false` path produced an unusable argv
  ending in `-c` with no command and was never invoked internally
  (stateless mode uses BuildRunArgvStateless). Updated tests and
  docs/docker-shell-tool.md sequence diagram.

Reliability (group g):
* RunStatelessAsync: add a second `catch (OperationCanceledException)`
  guarded on `cancellationToken.IsCancellationRequested` that issues
  `docker kill --signal KILL <perCallName>` before rethrowing.
  Previously, caller-driven cancellation bypassed the timeout-only
  catch and propagated without killing the container; because `--rm`
  only fires when PID 1 exits, the container ran indefinitely.
  Extracted the kill-by-name logic into a `BestEffortKillContainerAsync`
  helper shared by both the timeout and caller-cancel paths.

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

* .Net: Fill PR #5604 test coverage gaps for Shell tools

Addresses the test-coverage findings in the latest Copilot review.

* ShellResultTests (new): direct branch coverage for
  ShellResult.FormatForModel() — empty stdout, non-empty stderr,
  truncated, timed-out, success, and the truncated-with-empty-stdout
  edge where the marker is intentionally suppressed. This method's
  string is what the language model sees, so it benefits from
  explicit unit-level coverage independent of integration tests.
* ShellSessionTests (new): direct unit tests for the internal
  TruncateHeadTail head-tail truncation utility — under-cap (no
  truncation), exactly at cap (no truncation), over-cap (truncated
  with marker, both head and tail preserved), and empty-string.
  Reachable via InternalsVisibleTo.
* LocalShellToolTests: Theory test exercising 8 representative
  patterns from ShellPolicy.DefaultDenyList (rm -rf /, mkfs.ext4,
  curl|sh, wget|sh, Remove-Item /, shutdown, reboot, Format-Volume)
  to catch deny-list regex regressions; previously only 1/16 was
  tested.
* LocalShellToolTests: explicit stderr-capture assertion (echo to
  stderr → result.Stderr contains the message). Stderr capture was
  not directly asserted anywhere in the suite.
* DockerShellToolTests: RunAsync_RejectedCommand throws
  ShellCommandRejectedException. The Docker-side policy check is a
  pure-logic path that runs before any docker invocation, so this
  test covers the rejection branch without needing a Docker daemon.

Total: 66 -> 85 tests, all passing on net10.0.

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

* feat(dotnet/shell): add ShellEnvironmentProvider for OS-aware shell instructions

Pairs LocalShellTool/DockerShellTool with an AIContextProvider that
probes the live shell once per session (OS, family, version, CWD,
configurable CLI versions) and injects authoritative instructions so
the agent uses platform-native idioms (PowerShell vs POSIX). Fixes the
class of bugs where the model emits 'VAR=value' / '/tmp' / '$VAR' on
a Windows PowerShell session.

- ShellEnvironmentProvider/Snapshot/Options public surface in the
  existing Microsoft.Agents.AI.Tools.Shell package (one new project
  reference to Microsoft.Agents.AI.Abstractions).
- Probes go through the same IShellExecutor that runs agent commands,
  so they respect the configured policy and (for DockerShellTool) the
  container boundary.
- 8 unit tests covering snapshot capture, default formatter idioms,
  missing-tool handling, custom formatter override, and refresh.
- Agent_Step21_ShellWithEnvironment sample replays the DEMO_TOKEN
  cross-call scenario using a persistent local shell.

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

* fix(dotnet/shell): address PR review feedback round 3

- ShellEnvironmentProvider.cs split into one-type-per-file (ShellFamily,
  ShellEnvironmentSnapshot, ShellEnvironmentProviderOptions, plus the
  provider class) to match FoundryMemoryProvider/AgentSkillsProvider
  layout.
- csproj: drop IsPackable=false (package will publish on merge), add
  IsReleased=true and disable package validation baseline (first release),
  use TargetFrameworksCore, add InjectSharedDiagnosticIds and
  InjectExperimentalAttributeOnLegacy to align with shipping packages.
- Sample: refactor to demonstrate stateless mode first (independent
  read-only commands), then persistent mode (state carried across calls,
  e.g. DEMO_TOKEN). Strip narrative/historical comments.
- Move docker-shell-tool.md out of the package — that doc lives in
  the docs repo (semantic-kernel-pr/agent-framework, branch
  feat/dotnet-shell-tool).

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

* Address PR #5604 round 4 review feedback

- Sample (Agent_Step21_ShellWithEnvironment): add prominent WARNING block
  noting LocalShellTool runs real commands on the host. Restructure sample
  to demonstrate stateless mode first (cd does not carry across calls) then
  persistent mode (cd and env vars persist), motivating when to pick each.
- DockerShellTool class XML doc: reframe as a best-effort baseline rather
  than a security guarantee; list mitigations users should still apply.
- DockerShellTool ShellKind.Sh comment: rephrase as forward-looking design
  rationale (avoid duplicate --noprofile/--norc if Bash is reintroduced)
  instead of bug-history narrative.
- DockerShellTool.IsHardenedConfiguration / AsAIFunction XML docs: clarify
  these are configuration-shape checks and convenience defaults, not
  security guarantees.
- Drop IDisposable from LocalShellTool and DockerShellTool. The previous
  sync Dispose() blocked on DisposeAsync().GetAwaiter().GetResult() with a
  VSTHRD002 suppression, which is fragile under sync contexts. Both tools
  now expose IAsyncDisposable only; tests updated to await using.

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

* Add Async suffix to async test methods to satisfy IDE1006

Fixes check-format CI failure on PR #5604.

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

* Fix CPU busy-spin in WaitForSentinelAsync

When new bytes arrived in the stdout read loop, the producer called
TrySetResult on _stdoutSignal but did not replace it with a fresh TCS.
A consumer looping inside WaitForSentinelAsync would then re-read the
same already-completed TCS, causing WaitAsync(100ms) to return
synchronously every iteration — a tight busy-spin that pinned a core
until the sentinel arrived or the timeout fired.

Swap the signal before completing the old one so the next consumer
iteration observes a fresh (uncompleted) TCS, matching the pattern
already used in ReadExitCodeAsync.

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

* Remove unused onCommand audit hook from shell tools

The Action<string> onCommand callback was a redundant audit-logging seam:
no production callers, no Python parity, and the framework already
provides function-invocation middleware for cross-cutting concerns at
the AIFunction layer. Removing the parameter from LocalShellTool and
DockerShellTool keeps the public surface lean.

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

* Align Shell csproj with Foundry.Hosting preview-package conventions

- Add RootNamespace
- Move Title/Description into the primary PropertyGroup with
  TargetFrameworks/VersionSuffix to match the Foundry.Hosting layout
- Drop IsReleased (preview packages do not set it)
- Drop UTF-8 BOM

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

* Document why ShellEnvironmentProvider uses Instructions, not Messages

Expand the class XML doc to record the design rationale: the shell
environment is stable runtime metadata, not per-turn retrieval, so it
belongs in AIContext.Instructions (matching AgentSkillsProvider).
Messages is reserved for retrieval payloads (TextSearchProvider,
ChatHistoryMemoryProvider). System-role placement also has higher
steering weight and benefits from prompt caching in major providers.

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

* Clarify which probe failures ShellEnvironmentProvider swallows

Name the four exception types explicitly (timeout, policy rejection,
spawn failure, cancellation) and note that all other exceptions
propagate normally. Avoids the misleading impression that the provider
is a blanket try/catch.

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

* Strip cross-language and bug-history narrative from shell tool comments

Remove "hard-won" framing and explicit "Mirrors the Python ..." cross
references from class XML docs and inline comments in ShellSession,
DockerShellTool, and ShellResolver. Comments now describe current
behavior without commentary on prior implementations or development
history.

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

* Address PR #5604 round 5 review feedback

- ShellResolver: classify only `bash` as ShellKind.Bash; sh/zsh/dash/ash/ksh/busybox now route through ShellKind.Sh so bash-only --noprofile/--norc flags are not emitted to shells that reject them. Update enum doc and tests.

- ShellEnvironmentProvider.ProbeToolVersionAsync: validate the tool name against ^[A-Za-z0-9._-]+$ before interpolating into a shell command (prevents injection if ProbeTools is sourced from untrusted config). Fall back to stderr when stdout is empty so CLIs like java/older gcc still report a version. Drop misleading 'quoted' comment.

- ShellSession.TruncateHeadTail: truncate by UTF-8 byte count on rune boundaries, honouring the documented maxOutputBytes contract for non-ASCII output.

- ShellEnvironmentProviderTests: drop reflection on private _options; assert against the options instance the test already owns. Rename misnamed RefreshAsync test to reflect re-probing semantics. Add coverage for invalid tool names and stderr-only version output.

- ShellSessionTests: add multi-byte UTF-8 truncation tests (byte-budget honoured, no rune split, no U+FFFD).

- Move DockerShellToolIntegrationTests.cs from the unit test project into a new Microsoft.Agents.AI.Tools.Shell.IntegrationTests project so 'dotnet test' on the unit suite no longer requires a Docker daemon. Wire the new project into agent-framework-dotnet.slnx.

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

* Address PR #5604 round 6 review feedback

- ShellSession.MaybeReanchor: switch from double-quoted to single-quoted literal-quoting per shell. Double quotes still expand $VAR, ``, and backticks in both PowerShell and POSIX, so a working directory containing shell metacharacters could trigger command substitution. Add QuotePowerShell (escape ' as '') and QuotePosix (close-and-reopen around ') helpers and route MaybeReanchor through them. Add tests covering ``, $VAR, backticks, and embedded single quotes.

- ShellEnvironmentProvider.RunProbeAsync: narrow the OperationCanceledException filter to `when (!cancellationToken.IsCancellationRequested)` so caller-driven cancellation propagates instead of being silently converted to a null snapshot. Update the class XML doc to call out the distinction. Add tests for both paths (caller cancellation throws, probe-timeout returns null fields).

- DockerShellTool.RunStatelessAsync / RunDockerCommandAsync: replace unbounded StringBuilder accumulators with a shared HeadTailBuffer (extracted from LocalShellTool into its own internal type). Caps memory at roughly maxOutputBytes regardless of how much output a command emits; drops the now-redundant trailing TruncateHeadTail call. RunDockerCommandAsync caps helper-command output at 1 MiB (defends against chatty docker pull progress streams). Add HeadTailBufferTests covering bounded behaviour over 10 MiB of streamed input.

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

* Address PR #5604 round 7 review feedback

- HeadTailBuffer: switch to UTF-8 byte-aware truncation. The class previously

  capped on UTF-16 char count while callers pass _maxOutputBytes, so multi-byte

  output could exceed the budget and head/tail boundaries could split surrogate

  pairs into orphaned halves. Now tracks UTF-8 byte counts and treats each rune

  as an indivisible unit (encode -> bytes -> head/tail), guaranteeing the final

  string round-trips through UTF-8 and never contains an unpaired surrogate.

  The truncation marker now reads `bytes` instead of `chars` to match.

- ShellEnvironmentProvider: clear cached _snapshotTask on failure. Previously a

  faulted/cancelled first probe permanently poisoned the provider — every later

  ProvideAIContextAsync await replayed the same exception. Now the failed task

  is cleared via a CompareExchange so the next caller starts a fresh probe.

Tests: added rune-boundary coverage for HeadTailBuffer, plus two regression

tests for poison-recovery (executor-throw and caller-cancellation paths).

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

* Address PR #5604 round 8 review feedback

- HeadTailBuffer odd-cap data loss: previously _halfCap = cap / 2 was used as

  both the head fill bound and the tail eviction threshold, so an odd cap (e.g.

  cap=5 -> halfCap=2) would silently drop a byte while ToFinalString still

  reported truncated == false. Split into _headCap = cap / 2 and _tailCap =

  cap - _headCap so head + tail budgets always sum to exactly cap; any input

  whose UTF-8 size is <= cap now round-trips losslessly.

- ShellSession.TakePrefixByBytes unpaired-high-surrogate: the prefix walker

  advanced 2 chars whenever it saw a high surrogate, without verifying that the

  next char was actually a low surrogate. Mirrored the pair check from

  TakeSuffixByBytes so unpaired surrogates are treated as a single (invalid)

  BMP char and the encoder substitutes U+FFFD as it would anywhere else.

- Centralize clean-environment preserved-vars list. The {PATH, HOME, USER,

  USERNAME, USERPROFILE, SystemRoot, TEMP, TMP} allowlist was duplicated in

  LocalShellTool (stateless launch) and ShellSession (persistent startup), so

  adding a new variable required touching both. Extracted into

  CleanEnvironmentHelper.PreservedVariables / ApplyPreserved; both call sites

  collapse to a single line.

Tests: HeadTailBuffer round-trip-at-odd-cap regression, ShellSession unpaired-

surrogate test.

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

* Address PR #5604 round 9 review feedback

- ShellSession.TruncateHeadTail odd-cap budget: same fix applied to

  HeadTailBuffer last round but missed here. Use headCap = cap/2 +

  tailCap = cap - headCap so the head/tail budgets sum to exactly cap.

- Replace TakePrefixByBytes / TakeSuffixByBytes Encoder.Convert loops with

  rune iteration. The old code ignored Encoder.charsUsed and trusted the

  caller's hand-rolled surrogate-pair detection, which made the byte count

  fragile around unpaired surrogates. EnumerateRunes + Utf8SequenceLength

  is stateless and self-evidently correct.

- ShellEnvironmentProvider.ProbeAsync now skips case-insensitive duplicates

  in the user-supplied ProbeTools list. Previously {\"git\",\"GIT\"} would

  probe twice and rely on insertion order to determine the kept value.

- DockerShellToolTests.AsAIFunction_RelaxedConfig_DefaultsToApprovalGated:

  removed unused trailing ool _ parameter and matching InlineData column.

Tests: added duplicate-ProbeTools regression test.

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

* Address PR #5604 round 10 review feedback

* ShellSession.ReadLoopAsync: replace per-byte buf.Add(chunk[i]) loop with a single buf.AddRange(new ArraySegment<byte>(chunk, 0, n)) bulk copy on the read hot path.

* ShellPolicy: compile allow-list patterns with RegexOptions.IgnoreCase, matching the deny-list and avoiding case-mismatch surprises.

* LocalShellToolTests.RunAsync_NonZeroExit: drop the redundant ternary that selected between two identical 'exit 7' literals.

* DockerShellToolIntegrationTests.NetworkNone: fix the comment to reference 'getent' (matching the actual command) instead of the stale 'wget' phrasing.

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

* fix(dotnet): address PR #5604 round-3 review feedback

- Rename LocalShellTool/DockerShellTool -> LocalShellExecutor/DockerShellExecutor
- Rename IShellExecutor.StartAsync/CloseAsync -> InitializeAsync/ShutdownAsync
- Rename ShellDecision -> ShellPolicyOutcome
- Rename CleanEnvironmentHelper.ApplyPreserved -> EnvironmentSanitizer.RemoveNonPreserved
- Convert ShellRequest/ShellPolicyOutcome from record struct to plain readonly struct (with IEquatable<T>)
- Split ShellMode, ShellTimeoutException, ShellExecutionException into their own files
- Add DockerNetworkMode static class with None/Bridge/Host constants
- Convert DockerShellExecutor memory parameter from string to long? memoryBytes
- Use Throw.IfNull(image) in DockerShellExecutor ctor
- Make ShellResolver.EnvVarName public const
- Inline-comment each DefaultDenyList regex; document allow-precedence-over-deny on ShellPolicy.Evaluate

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

* fix(dotnet): address PR #5604 round-3 follow-up nits

- DockerShellExecutor / LocalShellExecutor: drop redundant IAsyncDisposable from class declarations (IShellExecutor : IAsyncDisposable already covers it)
- DockerShellExecutor: scope DefaultImage / DefaultContainerUser / DefaultNetwork / DefaultMemoryBytes / DefaultPidsLimit / DefaultContainerWorkdir to internal (only used as parameter defaults; tests have InternalsVisibleTo)
- DockerShellExecutor.RunAsync: blank line after the null-guard block (style consistency)
- csproj: move <Title>/<Description> below the nuget-package.props import so they are not overwritten by the shared defaults; refresh wording to match new executor names

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

* Refactor shell tool: abstract ShellExecutor, options classes, ContainerUser record

Round-3 review responses for PR #5604:

* Replace IShellExecutor interface with abstract ShellExecutor base class so the surface can be extended without breaking implementers (review feedback from @westey-m).

* Drop ShutdownAsync from the executor surface; DisposeAsync is the canonical teardown (review feedback from @SergeyMenshykh).

* Replace the long parameter lists on Local/DockerShellExecutor constructors with LocalShellExecutorOptions and DockerShellExecutorOptions classes so adding new knobs is no longer a breaking change (review feedback from @SergeyMenshykh).

* Introduce ContainerUser(Uid, Gid) record in place of a 'uid:gid' string for the Docker user, with Default and Root statics (review feedback from @lokitoth).

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

* Remove IsHardenedConfiguration; AsAIFunction defaults to approval-gated

Addresses PR #5604 review thread AZpMj. The IsHardenedConfiguration
property was a configuration-shape check, not a security guarantee,
and using it to auto-disable approval gating gave false confidence.

- Delete IsHardenedConfiguration property.
- AsAIFunction(requireApproval: null) now always wraps in
  ApprovalRequiredAIFunction; callers must explicitly pass false to
  opt out.
- Update class- and method-level XML docs to drop hardened-attestation
  language and call out approval gating as the primary safety control.
- Drop two hardening-assertion tests and the relaxed-config theory;
  add one test asserting null requireApproval is approval-gated.

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

* Replace ShellExecutionException/ShellTimeoutException with standard exceptions

Addresses PR #5604 review threads AaqVP and Aasod. The custom
exception types added no behavior beyond the base type — only a
different name — so callers gain nothing from them.

- Delete ShellExecutionException.cs and ShellTimeoutException.cs.
- Process spawn failures (LocalShellExecutor, DockerShellExecutor)
  and broken-pipe to a long-lived shell (ShellSession) now throw
  IOException, which is the natural .NET shape for these failures.
- ShellTimeoutException was declared but never thrown; the only
  in-process timeout path uses the OperationCanceledException raised
  by the linked CancellationTokenSource. The catch-and-swallow in
  ShellEnvironmentProvider now matches IOException + TimeoutException.
- Update XML doc comments accordingly.

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

* Remove ShellPolicy.DefaultDenyList; default policy is empty

Addresses PR #5604 review thread AY7Ba. A regex deny-list is
bypassed in seconds by hex escapes ($(echo -e "\x72\x6D")),
command substitution ($(base64 -d <<<...)), and envvar splicing
($(A=r B=m; echo $A$B)). No major agent framework uses regex
matching as a primary control; AutoGen explicitly removed theirs
in v2. The real defenses are approval gating (default) and the
Docker sandbox tier.

- Delete DefaultDenyList property from ShellPolicy.
- ShellPolicy(denyList: null) now means an empty deny-list.
- Rewrite ShellPolicy class XML docs to frame as a UX pre-filter
  for operator-supplied patterns, not as a security control.
- Update LocalShellExecutorOptions/DockerShellExecutorOptions
  Policy docs to match.
- Tests that exercise the deny-list mechanism now supply patterns
  explicitly, mirroring real operator usage.
- Add Policy_DefaultConstruction_AllowsAnyNonEmptyCommand test.

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

* Document single-session ownership for persistent shell mode

Several PR #5604 review threads (notably AaQh2) raised that the persistent
shell experience has no concurrency story. The framework's actual design
is "one executor per conversation" — there is no per-caller isolation —
but that contract was only stated briefly on ShellExecutor and not at all
on the types and properties developers reach for first.

Strengthen the docs in the places a user is most likely to land:

- ShellMode.Persistent: explicit single-session-ownership paragraph
  (state visible across calls, single pipe, no isolation, one per session).
- ShellExecutor: rewrite the Concurrency paragraph to enumerate what
  leaks (cwd, env, history, background jobs) and call out DI scoping.
- LocalShellExecutor: new Single-session-ownership paragraph mirroring
  the executor-level contract and pointing at Stateless mode as the
  escape hatch.
- DockerShellExecutor: same, framed around the container + bash REPL
  the persistent-mode executor owns end-to-end.
- ShellSession: add a Single-owner paragraph on the type docs and a
  comment on _runLock clarifying that it serializes the owner's calls,
  not multiple tenants.
- LocalShellExecutorOptions.Mode / DockerShellExecutorOptions.Mode:
  per-property note pointing at the executor remarks.

Docs-only; src builds clean with zero warnings, zero errors.

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

---------

Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-12 16:17:49 +00:00
818ae65b77 fix: declare Magentic protocol messages (#5778)
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-12 15:59:15 +00:00
westeyandGitHub ae57616b32 .NET: Refactor harness console rendering (#5751)
* Refactor harness console rendering

* Fix formatting issues

* Address PR comments
2026-05-12 15:13:53 +00:00
Jacob AlberandGitHub 41d6c61f81 .NET fix: Synthesized Handoff FunctionResult is never sent to agent (#5718)
* test: Split out Handoff Orchestration tests

* fix: Synthesized Handoff FunctionResult is never sent to agent

When we receive a handoff request from the agent, we need to service it outside of the Agent Loop to terminate the loop. What this means is that we take ownership of terminating the call by feeding the result back into the agent on a subsequent invocation.

When we refactored Handoff to support HITL and make use of AgentSession, we inadvertantly removed this step, causing subsequent invocations to the Handoff agent to fail (first works, but breaks the state).

The fix is to be more precise about the agent's bookmark when concatenating the result of agent invocation to the shared conversation history.

* test: Add unit tests for Handoff FunctionCall/Result matching fix
2026-05-12 14:35:32 +00:00
dfc3079d68 .NET: Add A2A input-request content for human-in-the-loop scenarios (#5743)
* .NET: Add A2A input-request content for human-in-the-loop scenarios

Adds first-class support for handling user input requests from A2A agents
when they return an `input-required` task state.

- Add `A2AInputRequestContent` (wraps the requested `AIContent`) and
  `A2AInputResponseContent` (wraps the user's `AIContent` reply), with
  `CreateResponse` helper overloads on the request type.
- Surface input requests on `AgentResponse` / `AgentResponseUpdate` via
  `AgentTask` and `TaskStatusUpdateEvent` mappings.
- Link follow-up messages containing `A2AInputResponseContent` to the
  existing task via `TaskId` instead of `ReferenceTaskIds`.
- Add `A2AAgent_HumanInTheLoop` sample and register it in the solution
  and parent README.
- Add unit tests for the new types, extensions, and `A2AAgent` paths.

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

* Remove unnecessary using directive flagged by CI format check

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

* address feedback

* Guard against null TaskId when sending A2AInputResponseContent

Throw InvalidOperationException if TaskId is missing when the message
contains A2AInputResponseContent, preventing silent no-op responses.
Also adds tests for both RunAsync and RunStreamingAsync paths.

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

* Leave Contents null for non-InputRequired status updates

Remove unnecessary '?? []' fallback so Contents stays null when there
are no input requests, matching the other update mapping patterns.

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

* Use consistent GUID format for request IDs

Use ToString("N") to match message ID format used elsewhere in
the A2A component.

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

* Remove Debug build exclusion for the HumanInTheLoop sample so it                                                                                                                                                                                                               participates in normal solution validation.

* Add missing using Microsoft.Extensions.AI to A2AAgent_HumanInTheLoop

The sample uses ChatMessage, TextContent, and ChatRole types from
Microsoft.Extensions.AI but was missing the using directive, causing
CS0246 build errors on all CI jobs.

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

* change the way user input requests are handled based on pr review comments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-12 13:10:18 +00:00
Evan MattsonandGitHub e3875f2c91 .NET: DevUI: add configurable access controls for the DevUI HTTP surface (#5739)
* .NET: DevUI: add configurable access controls for the DevUI HTTP surface

* .NET: DevUI: address review and fix dotnet format

- Restore parameterless AddDevUI overloads for binary compatibility on
  IServiceCollection and IHostApplicationBuilder.
- Keep /meta outside the auth-filtered group so the frontend can discover
  whether a bearer token is required before prompting for one. Surface the
  actual requirement via MetaResponse.auth_required.
- Invoke DevUIOptions.ConfigureEndpoints before mapping protected endpoints
  so RouteGroupBuilder conventions (RequireAuthorization, rate limiting)
  reliably apply.
- Treat a null RemoteIpAddress as non-loopback in DevUIAuthFilter; tests
  now set IPAddress.Loopback explicitly when exercising the loopback path.
- Add a DEVUI_AUTH_TOKEN env-var fallback test and a /meta-public test.
- Fix dotnet format: add UTF-8 BOM to new files, simplify a cref in
  DevUIOptions, and drop an unused using in the new test.

* .NET: DevUI: add missing authRequired param XML tag

* .NET: DevUI tests: set loopback/AllowRemoteAccess for null-RemoteIp default

DevUIIntegrationTests use the default TestServer which leaves RemoteIpAddress
null. With the new conservative loopback default those tests now hit 403; set
AllowRemoteAccess on the option since those tests are not exercising access
control. Also add the missing SimulateRemoteIp call in the wrong-bearer test.

* .NET: DevUI tests: capture DEVUI_AUTH_TOKEN before parallel tests can see it

The env-var test was leaking DEVUI_AUTH_TOKEN into parallel DevUIIntegrationTests,
intermittently causing their requests to be rejected as 401. Eagerly resolve the
singleton DevUIAuthFilter so its constructor captures the token, then restore the
env var before any HTTP requests run.
2026-05-11 22:45:41 +00:00
9199c84d42 .NET: Remove Foundry Toolbox server-side tools support (#5753)
* .NET: Remove Foundry Toolbox server-side tools support

Mirrors the Python cleanup in microsoft/agent-framework#5671. Passing
toolbox tools as server-side Responses tools is not the experience we
want to support; the hosted-agent MCP toolbox path (HostedMcpToolboxAITool
+ FoundryToolboxService) remains the supported way to consume Foundry
Toolboxes.

Removed:
- FoundryToolbox static class (GetToolboxVersionAsync / GetToolsAsync /
  ToAITools / SanitizeAndConvert)
- AIProjectClient.GetToolboxToolsAsync extension
- Agent_Step25_ToolboxServerSideTools sample (+ slnx entry)
- FoundryToolboxTests, TestDataUtil, HttpHandlerAssert, and the toolbox
  JSON fixtures only those tests referenced
- ToolboxHostedAgentTests and ToolboxHostedAgentFixture; the "toolbox"
  switch arm + CreateToolboxAgent helper in TestContainer; matching
  README scenario row and bootstrap script entry

Kept (MCP path, unchanged):
- HostedMcpToolboxAITool, FoundryAITool.CreateHostedMcpToolbox,
  FoundryAIToolExtensions.CreateHostedMcpToolbox(ToolboxRecord/Version)
- FoundryToolboxService, AddFoundryToolboxes, marker injection in
  AgentFrameworkResponseHandler, InputConverter.ReadMcpToolboxMarkers
- Hosted-Toolbox sample, McpToolbox* tests, FoundryToolboxServiceTests

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

* .NET: Add Foundry Toolbox MCP sample (Agent_Step25_FoundryToolboxMcp)

Adds a non-hosted-agent equivalent of the Python foundry_chat_client_with_toolbox.py sample. The agent connects to a Foundry Toolbox's MCP endpoint via Streamable HTTP, injects a fresh Azure AI bearer token on every request, and discovers the toolbox's tools at runtime via McpClient.ListToolsAsync.

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

* .NET: Tighten Agent_Step25_FoundryToolboxMcp README/Program comments

Drop 'non-hosted agent' framing from README (this sample isn't related to hosted agents) and remove narrative comparison to server-side tools from the Program.cs header comment.

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

* Drop python sample reference from Agent_Step25 README

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

* Drop incorrect .NET 10 prereq from Agent_Step25 README

Toolboxes don't require .NET 10 (Microsoft.Agents.AI.Foundry targets net8.0+); the parent AgentsWithFoundry README already lists the sample SDK prereq.

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

* Fix Toolsets api-version in Agent_Step25 example endpoint

Use 2025-05-01-preview to match FoundryToolboxOptions.ApiVersion. The placeholder 'v1' is not accepted by the Toolsets endpoint.

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

---------

Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 22:05:14 +00:00
0bbedc4fa2 .NET: Fix/per service input persistence on stream error (#5744)
* .NET: Persist input messages on streaming errors in PerServiceCallChatHistoryPersistingChatClient

When the underlying chat service emits an in-stream error (for example a
`response.error` SSE event from the OpenAI Responses API on rate limit),
the OpenAI client surfaces it as an `ErrorContent` update and ends the
stream without throwing. Previously, `PerServiceCallChatHistoryPersistingChatClient`
only persisted history when the streaming loop completed successfully and
`NotifyProvidersOfNewMessagesAsync` was called at the end. On the
in-stream-error path, the input messages handed to that iteration -
typically `FunctionResultContent` produced by `FunctionInvokingChatClient`
in the previous iteration - were never persisted. The next run would
replay session history with a dangling `FunctionCallContent` and the
service would reject the request with `No tool output found for function
call <id>`.

This change:

- Adds a `PersistInputOnErrorAsync` helper that persists the input
  messages (with no response messages) so function-call/function-result
  pairings are not split across failures.
- Calls the helper from every error path: pre-loop enumerator creation,
  the first `MoveNextAsync`, the in-loop `MoveNextAsync`, and a new
  `finally` that handles abnormal iterator disposal.
- After the streaming loop, scans the assembled response for any
  `ErrorContent` and, if present, persists the input, notifies
  providers of failure, and throws `InvalidOperationException` so the
  error is surfaced to the caller instead of silently corrupting history.
- Hardens `InMemoryChatHistoryProvider.StoreChatHistoryAsync` to treat
  a null `RequestMessages` as empty, since the new error path can
  invoke it with no response messages.

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

* Fix dropped FunctionResultContent on streaming pipeline early-disposal

When a consumer of ChatClientAgent.RunStreamingAsync stops iterating early
(e.g. ToolApprovalAgent yields the approval request and then `yield break`),
the framework cascades DisposeAsync down the stream. C# async iterators do
not auto-dispose IAsyncDisposable locals, so the inner enumerator returned
by IChatClient.GetStreamingResponseAsync(...).GetAsyncEnumerator(ct) was
left suspended. That suspended FunctionInvokingChatClient downstream, which
suspended PerServiceCallChatHistoryPersistingChatClient at its `yield
return`, so its finally block never ran and the in-flight
FunctionResultContent for the just-completed tool call was not persisted
to chat history. The next turn then loaded a session that contained a
FunctionCallContent with no matching FunctionResultContent and the model
returned HTTP 400 `No tool output found for function call`.

Fixes:

* ChatClientAgent.RunStreamingAsync: wrap the iteration in
  try/finally that disposes the inner enumerator. Disposal now cascades
  through the pipeline and PerService's finally runs on early exit.
* PerServiceCallChatHistoryPersistingChatClient: in the streaming path,
  snapshot input messages with `messages.ToList()` (the caller, FICC,
  reuses a single mutable buffer across iterations and may mutate it
  before our finally / error path persists), wrap GetAsyncEnumerator,
  the first MoveNextAsync, and in-loop MoveNextAsync in try/catch each
  calling PersistInputOnErrorAsync + NotifyProvidersOfFailureAsync, and
  add a finally that calls PersistInputOnErrorAsync when the loop did
  not exit normally so per-iteration FRCs are persisted on early
  disposal as well as on errors.

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

* .NET: Add tests for PerService streaming error/dispose persistence paths

Adds five regression tests covering the new error-path persistence in

PerServiceCallChatHistoryPersistingChatClient.GetStreamingResponseInnerAsync:

- Persists input messages when GetStreamingResponseAsync throws synchronously.

- Persists input messages when the first MoveNextAsync throws.

- Persists input messages when a mid-stream MoveNextAsync throws.

- Persists input messages when the consumer abandons enumeration early

  (the ToolApprovalAgent yield-break / disposal-cascade case).

- Throws and persists input when the stream emits an in-band ErrorContent.

All 66 tests in the class pass on net10.0 and net472.

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

* .NET: Address PR feedback on PerService streaming error persistence

Two follow-ups from PR #5744 review:

1. Prevent duplicate persistence on the in-loop MoveNextAsync catch path.

   The inner catch persists input messages, then rethrows, which propagates

   through the surrounding try/finally where loopExitedNormally is still false,

   causing the finally to persist again. Introduced an inputPersisted flag

   that the inner catch sets after persisting; the finally now skips when

   inputPersisted is true.

2. Use the caller's CancellationToken in the abnormal-exit finally instead

   of CancellationToken.None, so cleanup remains responsive to cancellation.

   Fall back to CancellationToken.None only when the caller's token is

   already canceled (otherwise the persist call would observe the

   cancellation, throw, and mask the original early-exit reason).

Tightened all five new streaming-error tests from Times.AtLeastOnce to

Times.Once on the input-persistence matcher to regression-guard against

duplicate persistence. All 66 tests in the class still pass (net10.0 + net472).

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

* .NET: Scope PerService streaming changes to cooperative early-exit only

Per discussion on PR #5744, scope this PR back to fix only the original
ToolApprovalAgent dropped-FunctionResultContent bug and address the
enumerator-disposal review comment. Specifically:

- Remove input-message persistence from the GetAsyncEnumerator and
  MoveNextAsync error paths. Routing failed service calls through the
  success notification channel was breaking the provider contract; we
  will instead rely on inner-agent retries for transient errors. Failure
  paths still call NotifyProvidersOfFailureAsync as before.
- Remove the in-stream ErrorContent detection block (same rationale).
- Keep the try/finally that calls the (now narrower) early-exit input
  notification on cooperative disposal (e.g. ToolApprovalAgent yield
  break). A new serviceErrorOccurred flag ensures we do NOT renotify
  on exception paths.
- Always DisposeAsync the underlying enumerator on every exit path,
  addressing the copilot-reviewer comment about leaked HTTP/streams.
- Rename PersistInputOnErrorAsync -> NotifyProvidersOfEarlyExitInputAsync
  to better reflect what it does and when it runs (rogerbarreto nit).
- Apply rogerbarreto nit on InMemoryChatHistoryProvider null-coalescing.
- Drop the four tests that covered the removed error-path behavior;
  keep RunStreamingAsync_PersistsInputMessages_WhenConsumerAbandons
  EnumerationAsync (regression guard for the cooperative-pause path).

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

---------

Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 20:28:14 +00:00
Roger BarretoandGitHub 18d7a46a54 .NET: Hosted Agents - RAG Sample with Azure AI Search (#5693) (#5701)
* .NET: Hosted Agents - RAG Sample with Azure AI Search (#5693)

Adds a Hosted-AzureSearchRag sample plus a live Foundry.Hosting integration
test scenario backed by a real Azure AI Search index.

Sample (Hosted-AzureSearchRag): keyword-only Azure AI Search via
SearchClient adapter into TextSearchProvider, scope-aware
DevTemporaryTokenCredential consuming AZURE_BEARER_TOKEN_FOUNDRY +
AZURE_BEARER_TOKEN_SEARCH for local Docker, Dockerfile + contributor
Dockerfile mirroring Hosted-TextRag.

Integration test: AzureSearchRagHostedAgentFixture extends the PR #5598
HostedAgentFixture with the new azure-search-rag scenario branch in the
shared test container; AzureSearchRagHostedAgentTests asserts the model
returns canary tokens (TR-CANARY-7821, SHIP-CANARY-4493) that exist only
in the seeded documents - real proof the agent grounded its answer in
retrieved content rather than training data.

* Address PR 5701 Copilot review feedback

- Sample README: drop stale 'bootstraps the index on first run' line; index is pre-provisioned out of band

- Sample + TestContainer search adapters: propagate CancellationToken to await foreach via .WithCancellation()
2026-05-11 13:59:42 +00:00
Roger BarretoandGitHub 9d8c3f8cb7 Simplify ClientHeadersScope, drop redundant using/Dispose (#5676)
Wesley pointed out (with a clean demo) that AsyncLocal<T> mutations made
inside an awaited async method do not leak back to the caller after the
method returns - the runtime restores the caller's view automatically.

ClientHeadersAgent.RunCoreAsync and RunCoreStreamingAsync are the only
callers of the scope, both are async methods awaited by their callers,
so the explicit using/Dispose pattern was doing work the runtime already
does for us.

* ClientHeadersScope collapsed to a single Current { get; set; } property
  over an AsyncLocal<IReadOnlyDictionary<string,string>?>. Drops Push,
  the Scope struct, and Dispose. XML doc explains the AsyncLocal natural-
  restoration semantics so the design intent is self-documenting.
* ClientHeadersAgent uses a direct ClientHeadersScope.Current = snapshot
  before delegating. Drops the local RunAsyncCoreAsync helper and the
  snapshot-passed-as-parameter dance.
* Test 10 renamed to ClientHeadersScope_IsAsyncLocalIsolatedAndAutoRestoresAsync;
  drops the LIFO claim, keeps the parallel-isolation assertion, and adds
  a Wesley-style 'set inside async, caller sees null on return' assertion.
* Test 12 switches from using ClientHeadersScope.Push to direct
  Current = ... with try/finally for test isolation.

Snapshot deep-copy in TrySnapshot stays - it defends against caller
mutating the source Dictionary mid-run, which is independent of the
AsyncLocal restoration mechanism.
2026-05-11 13:38:14 +00:00
westeyandGitHub 0557b5782b .NET: Add IChatMessageInjector for message injection during function loop (#5679)
* Adding the ability to inject messages during the function call loop

* Split message injection functionality

* Remove interface, since it is not required not that we split the chat client.

* Address conversation id propogation

* Fix formatting issue
2026-05-08 17:16:03 +00:00
Roger BarretoandGitHub eb709d8fc9 .NET: Update FoundryAgent to address HostedAgents strict URL routing (#5677)
* .NET: Foundry agent-endpoint constructor uses ProjectOpenAIClient directly to fix hosted-agent URL routing

Fixes the experimental FoundryAgent(Uri agentEndpoint, AuthenticationTokenProvider, ...)
constructor so it actually works against Foundry hosted agents.

The previous implementation routed through AzureAIProjectChatClient, which
internally called aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForAgent(...).
For an agent-endpoint URL of the canonical shape

  https://<host>/api/projects/<project>/agents/<agentName>/endpoint/protocols/openai

the chain produced

  POST https://<host>/api/projects/<project>/openai/v1/responses

(project-level path, no /agents/ segment). The Foundry service rejects this with
HTTP 400 "Hosted agents can only be called through the agent endpoint:
.../agents/<agentName>/endpoint/protocols/openai/responses".

The constructor also extracted the agent name via
agentEndpoint.Segments[^1].TrimEnd('/'), which returns "openai" (the last segment),
not the agent name.

What changed
- Public ctor signature: clientOptions parameter type changed from
  AIProjectClientOptions? to ProjectOpenAIClientOptions?. The constructor is
  fundamentally building a ProjectOpenAIClient; accepting AIProjectClientOptions
  was a leaky abstraction whose translation silently dropped any pipeline
  policies the caller added via AddPolicy(...). With the direct type, caller
  policies pass through to the per-agent traffic verbatim.
- Per-agent client construction: `new ProjectOpenAIClient(BearerTokenPolicy, ProjectOpenAIClientOptions)`
  with Endpoint and AgentName set, then `GetProjectResponsesClient().AsIChatClient()`.
  The SDK auto-appends ?api-version=v1 when AgentName is set.
- New private static ParseAgentEndpoint helper: single source of truth for both
  agent-name extraction and project-root derivation. Tolerates trailing slash,
  case variants on /agents/ and the suffix segment, strips query/fragment, and
  throws ArgumentException with paramName=nameof(agentEndpoint) for malformed input.
- Project-level client (used by CreateConversationSessionAsync) is built fresh
  from the derived project root with primitive properties copied
  (RetryPolicy/NetworkTimeout/Transport/UserAgentApplicationId) plus MEAI UA.
- New GetService<ProjectOpenAIClient>() entry alongside the existing
  GetService<AIProjectClient>() (the latter returns null in agent-endpoint mode
  since no AIProjectClient is constructed on that path).
- Endpoint and AgentName on caller-supplied ProjectOpenAIClientOptions are
  overridden by values derived from agentEndpoint.

Compatibility
- FoundryAgent is [Experimental(OPENAI001)]. No GA surface touched. The Foundry
  project does not maintain PublicAPI.*.txt baselines so there is no shipped
  baseline to update.
- The Microsoft.Agents.AI.Foundry csproj pins
  Azure.AI.Projects to VersionOverride 2.1.0-beta.1 (matching what the IT and
  hosting projects already use); the central pin in Directory.Packages.props
  stays at 2.0.0.
- WireClientHeaders from PR #5652 is invoked on the agent-endpoint path so
  per-call x-client-* headers behave identically across both ctors.

Tests
- 23 new unit tests in FoundryAgentTests.cs:
  - 12 for the agent-endpoint constructor (URL routing for non-streaming and
    streaming, conversations URL shape, MEAI UA stamping, caller-policy
    passthrough on the per-agent pipeline, Endpoint/AgentName override
    semantics, GetService matrix, ProjectOpenAIClient propagation,
    UserAgentApplicationId propagation, null-arg validation, ID/Name slug)
  - 9 for ParseAgentEndpoint (standard shape, trailing slash, casing,
    sovereign-cloud host without /api/projects/ literal prefix, special chars
    in agent name, query/fragment stripping, three negative cases)
  - 2 null-arg tests for the public ctor
- All 250 Microsoft.Agents.AI.Foundry.UnitTests pass (was 221 baseline plus
  29 from PR #5652 plus 23 new in this PR equals 273; pre-existing tests
  collapsed by the rebase merge keep the total at 250).
- All 225 Microsoft.Agents.AI.Foundry.Hosting.UnitTests pass; no behavioral
  change to the hosting layer.
- dotnet build clean across net8/9/10/netstandard2.0/net472 with
  TreatWarningsAsErrors=true.
- dotnet format --verify-no-changes clean for the touched src and test projects.

* .NET: Bump central Azure.AI.Projects pin to 2.1.0-beta.1 and flip Microsoft.Agents.AI.Foundry to preview

Required to fix the NU1109 downgrade chain that broke CI on the agent-endpoint
constructor rewire (#5677). Microsoft.Agents.AI.Foundry now depends on
ProjectOpenAIClientOptions.AgentName and the (AuthenticationPolicy, options)
constructor that only exist in Azure.AI.Projects 2.1.0-beta.1.

Changes:
* Directory.Packages.props: Azure.AI.Projects 2.0.0 -> 2.1.0-beta.1.
* Microsoft.Agents.AI.Foundry.csproj: drop IsReleased=true so the package ships
  as preview (matches the beta SDK we now depend on). Add a comment noting the
  flip is temporary and should revert once Azure.AI.Projects ships a stable
  2.1.0.
* Drop redundant VersionOverride="2.1.0-beta.1" from the 10 csprojs that had it
  as a workaround; the central pin now suffices.

Verified:
* dotnet build agent-framework-dotnet.slnx --warnaserror clean across all TFMs.
* Microsoft.Agents.AI.Foundry.UnitTests 250/250 pass.
* Microsoft.Agents.AI.Foundry.Hosting.UnitTests 211/211 pass.
* dotnet format --verify-no-changes clean for the touched src and test projects.
2026-05-08 14:46:52 +00:00
76772ffc19 .NET: Fix function_call_output.output to be a JSON string on the wire (#5705)
* Fix function_call_output.output to be a JSON string on the wire

OutputConverter was passing the JSON serialization of complex tool results (e.g. List<TodoItem>) directly into OutputItemFunctionToolCallOutput via BinaryData.FromString. The Responses SDK treats that BinaryData as the *raw JSON value* for the field, so non-string results landed on the wire as an unquoted JSON array (e.g. `"output":[{...}]`) instead of a JSON string.

The OpenAI Responses spec requires `function_call_output.output` to be a JSON string. The strict-parsing OpenAI .NET client (FunctionCallOutputResponseItem) consequently failed when threading a follow-up turn that replayed such an item, with: `The JSON value could not be converted... requires an element of type 'String', but the target element has type 'Array'`.

Always wrap the payload as a JSON string literal:

  - string s   -> JSON-encode s (quoted, with escapes)

  - object o   -> JSON-serialize o, then JSON-encode the resulting text

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

* Address PR feedback: JsonElement special-case, symmetric inbound unwrap, tests

OutputConverter: extract EncodeFunctionResultAsJsonStringPayload helper
that special-cases JsonElement / JsonDocument so a string-kind element
does not get double-encoded into "\"value\"". Other JsonElement kinds
(object/array/number/bool) round-trip via GetRawText() and are then
JSON-string-wrapped, matching the spec.

InputConverter: symmetric DecodeFunctionResultPayload added to
ConvertFunctionCallOutput and ConvertFunctionToolCallOutput so
previously-stored function_call_output items replayed via
previous_response_id unwrap back to the original tool result text
instead of leaking the JSON-encoded form into FunctionResultContent.Result.
Legacy non-conforming raw-JSON-value payloads pass through unchanged.

Tests:
  - Replace ConvertUpdatesToEventsAsync_FunctionResultStringPayload_EmittedAsRawTextAsync
    with EmittedAsJsonStringAsync asserting the new wire contract ("sunny" -> "\"sunny\"").
  - Add coverage for object payloads, JsonElement string kind (no double-encoding),
    and JsonElement array kind (JSON-stringified).
  - Add InputConverter round-trip tests for spec-compliant JSON-string payloads
    and legacy raw-JSON-array payloads.

All 663 tests pass on net8/net9/net10. Verified end-to-end against the local
hosted-harness sample: T1-T4 (incl. TodoList tool replay across turns) all
succeed with no SDK parse errors.

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

---------

Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 23:27:57 +00:00
Jacob AlberGitHublokitothcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
27324a8013 .NET: Mark Magentic Orchestration Experimental (#5704)
* fix: Mark Magentic Orchestration Experimental

* Apply [Experimental] to all public Magentic types and suppress MAAIW001 in project

Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/957a07c1-a805-40eb-989d-bd3425d4c0af

Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
2026-05-07 22:54:01 +00:00
d3518ad19d fix(security): non-thread-safe sequence number generation may cau (#5320)
`SequenceNumber.Increment()` uses `this._sequenceNumber++` without synchronization. In concurrent streaming scenarios, this can produce race conditions and inconsistent sequencing, which may break event ordering guarantees and potentially allow response-mixing or state confusion.

Affected files: SequenceNumber.cs

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
2026-05-07 20:42:49 +00:00
Jacob AlberandGitHub ce70ca1a9f .NET: feat: Implement Magentic Orchestration for .NET (#5595)
* feat: Implement Magentic Orchestration for .NET

* fixup: Update for review comments

* fix: Fix FenceJsonRegexPattern

* fix: Format

* fix: Updates for PR feedback

* fix: Add missing serialized types to source gen for trimming

* fix: Address PR Comments
2026-05-07 18:36:15 +00:00
1489d6620e .NET: feat: Update Github Copilot SDK to 1.0.0-beta.2 (#5699)
* feat: Update Github Copilot SDK to 1.0.0-beta.2

* Fix formatting

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: Update for breaking changes in Github.Copilot.SDK

* fix sample project

* fix: whitespace formatting

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-07 19:15:10 +01:00
44381c051b .NET: Support reasoning events in AGUI (#4953)
* Support reasoning

* MEAI gives the same MessageId for reasoning and text content because they are part of the same logical model response. Create a new GUID for reasoning messages to be consistent with AGUI protocol and establish no link between reasoning and text messages

* When a frontend AG-UI client sends conversation history back in a subsequent POST, any accumulated role: "reasoning" messages fail deserialization in AGUIMessageJsonConverter because the role wasn't handled - causing the request to fail.

This adds AGUIReasoningMessage with Content and EncryptedValue properties, registers it in the JSON converter and serializer context, and converts it to TextReasoningContent (with ProtectedData) in AsChatMessages.

* Added MapReasoningMessage - converts a ChatMessage containing TextReasoningContent to AGUIReasoningMessage for c# client

* review

* Support reasoning

* MEAI gives the same MessageId for reasoning and text content because they are part of the same logical model response. Create a new GUID for reasoning messages to be consistent with AGUI protocol and establish no link between reasoning and text messages

* When a frontend AG-UI client sends conversation history back in a subsequent POST, any accumulated role: "reasoning" messages fail deserialization in AGUIMessageJsonConverter because the role wasn't handled - causing the request to fail.

This adds AGUIReasoningMessage with Content and EncryptedValue properties, registers it in the JSON converter and serializer context, and converts it to TextReasoningContent (with ProtectedData) in AsChatMessages.

* Added MapReasoningMessage - converts a ChatMessage containing TextReasoningContent to AGUIReasoningMessage for c# client

* review

* dotnet format

* Replace hardcoded string with constant

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-07 15:09:04 +00:00
cdd80c61ac .NET: Issue 5662 (#5668)
* Fix dangling function_call on approval response in Foundry hosting (#5662)

Make the wire<->AF approval translation in Microsoft.Agents.AI.Foundry.Hosting lossless so the resume turn pairs function_call/function_call_output correctly.

Root cause: InputConverter.ConvertMcpApprovalResponse rebuilt FunctionCallContent with CallId set to the FICC-composed AF request id (ficc_<callId>) and Name hardcoded to 'mcp_approval'. This (a) broke Azure Conversations pairing because the persisted function_call had CallId <callId> without prefix, and (b) made FICC unable to invoke the original tool by name on resume.

Fix: ToolApprovalIdMap now records the original FunctionCallContent (CallId, Name, Arguments) keyed by wire id at outbound time. InputConverter reconstructs the original FCC on inbound, falling back to the legacy placeholder when no mapping exists.

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

* Suppress orphan function_call items at the wire (#5662)

Foundry-Hosting's OutputConverter was emitting FunctionCallContent as wire `function_call` items while dropping the paired FunctionResultContent. The result: every auto-invoked tool call left an orphan `function_call` in the response store. The next turn (chained via previous_response_id or via a workflow that yields after one turn under externalLoop) reloaded that history and submitted it to Azure Conversations, which rejected it with HTTP 400 `No tool output found for function call ...`.

Function call/result pairs are entirely internal to the agent's tool-calling loop and have no place on the wire. Approval-required calls already surface separately via ToolApprovalRequestContent → mcp_approval_request, so dropping FCC is safe.

FCC's message-close behavior is preserved so pre-tool text doesn't accidentally concatenate with post-tool text under the same MessageId. Existing OutputConverter tests asserting FCC wire emission are updated to assert suppression.

Verified end-to-end against the declarative-workflow-menu external_loop bench: three-turn previous_response_id chain (menu → carbonara price → EXIT) now completes, where it previously failed at turn 2 with HTTP 400.

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

* Fail fast when no approval mapping is recorded (#5662)

The previous best-effort placeholder fallback in InputConverter.ConvertMcpApprovalResponse couldn't actually round-trip — it just delayed and obscured the failure as an HTTP 400 deep inside the agent loop. Throw InvalidOperationException with the wire id and a clear cause hint instead so the failure is local and actionable.

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

* Trim narrative comments and exception message (#5662)

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

* Defer FunctionCallContent emission until matched FunctionResultContent (#5662)

Replace blanket FCC suppression with deferred emission. FunctionCallContent
is buffered (name + serialized arguments) keyed by CallId; the function_call
and function_call_output wire items are only flushed once the matching
FunctionResultContent arrives.

- Auto-invoked FCC/FRC pairs surface as paired wire items so Azure's stored
  conversation has matched call+output and previous_response_id resume
  works (closes the orphan-function_call symptom from #5662).
- Orphan FCCs (e.g. workflow paused at a checkpoint mid-tool-loop) are
  dropped so they never poison the response store.
- Approval flows are unchanged: TARC still emits mcp_approval_request and
  the post-approval FRC has no buffered FCC to pair with so it is dropped;
  the approval round-trip handles its own pairing via mcp_approval_*.
- Leaves the door open for future client-side function calling: that
  pattern would surface an FCC without an FRC, would need to opt out of
  buffering, but the wire shape is already correct.

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

* Emit FunctionCallContent and FunctionResultContent directly (option B)

Replace the deferred-emission/buffer-and-drop strategy with direct emission of both function_call and function_call_output wire items.

Rationale: a lone FunctionCallContent in OutputConverter's input can mean two semantically different things, and only the caller knows which:

- Auto-invoke (FICC response surface): always paired with a matching FRC; both halves should appear on the wire as historical record.

- HITL / port-pause request (typed RequestPort<FunctionCallContent,...> or workflow synthesizing a request): a lone FCC IS the wire signal that the caller must resume by supplying a function_call_output.

Buffering+dropping orphans silently swallows the second case. Emitting both directly is the only correct shape for OpenAI Responses semantics.

The InputConverter already accepts function_call_output and mcp_approval_response on resume, so the round-trip works for both kinds.

The approval-flow round-trip fixes (ToolApprovalIdMap rich ApprovalEntry, fail-fast on missing mapping in ConvertMcpApprovalResponse) remain intact.

Tests: updated 7 OutputConverter tests + 1 OutputConverterWorkflow test that asserted the old buffer/drop semantics; all 227 tests pass.

Refs #5662

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

* Address PR #5668 review feedback on TryLoadMap

Stop swallowing JsonException in ToolApprovalIdMap.TryLoadMap. The catch block recovered to an empty map and a stale comment claimed the caller would gracefully degrade via a 'wire-id fallback path' — but that path no longer exists: InputConverter.ConvertMcpApprovalResponse fails fast when no entry is found.

Letting the JsonException propagate produces an error message that points at the actual cause (a state-bag format incompatibility), instead of converting it into a confusing 'no approval mapping recorded' InvalidOperationException one stack frame later.

Refs #5662, PR #5668

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

* Address PR #5668 review feedback round 2

- OutputConverter FRC: emit string results as raw text (no JSON-quoting),
  matching the wire contract for function_call_output.output.
- OutputConverter FCC: validate non-empty CallId before closing the in-flight
  text message, so a skipped FCC no longer breaks output-item boundaries.
- ToolApprovalIdMap.Record: take pre-serialized arguments JSON (string) and
  primitive callId/name. Drops [RequiresUnreferencedCode]/[RequiresDynamicCode]
  so trim/AOT warnings stop propagating to call sites.
- ToolApprovalIdMap.Record: no-op when callId or name is empty.
- Tests: dedup duplicate ConvertItemsToMessages_McpApprovalResponse no-mapping
  test; add coverage for empty-CallId boundary, raw-string FRC payload, and
  Record empty-key no-op.

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

---------

Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 00:30:41 +00:00
51ad460d5f .NET: Add Foundry.Hosting.IntegrationTests (#5598)
* Foundry.Hosting.IntegrationTests: scaffold project, fixtures, and 24 tests

Add a new integration test project for Foundry hosted agents alongside the existing Foundry.IntegrationTests project. The project provisions a real Foundry hosted agent per scenario via AgentAdministrationClient.CreateAgentVersionAsync, points it at a single test container image (built and pushed out of band by scripts/it-build-image.ps1 in a follow up commit), and exercises the agent through AIProjectClient.AsAIAgent.

Six scenario fixtures are introduced, each pointing at the same image but selecting behavior via the IT_SCENARIO environment variable on the HostedAgentDefinition:
- HappyPathHostedAgentFixture (round trip, multi turn, stored=false flag)
- ToolCallingHostedAgentFixture (server side AIFunctions)
- ToolCallingApprovalHostedAgentFixture (approval flow)
- ToolboxHostedAgentFixture (Foundry toolbox)
- McpToolboxHostedAgentFixture (MCP backed toolbox)
- CustomStorageHostedAgentFixture (custom storage provider)

24 tests across 6 test classes are scaffolded. All are tagged Skip pending the test container build and the end to end smoke iteration in follow up commits. Once the container is in place the Skip annotations can be removed scenario by scenario.

Adds an IT_HOSTED_AGENT_IMAGE constant to the shared TestSettings so every IT project agrees on the env var name the build script emits.

* Foundry.Hosting.IntegrationTests: add TestContainer, build script, slnx, README

Adds the rest of the integration test infrastructure on top of the previous scaffolding commit:

* Foundry.Hosting.IntegrationTests.TestContainer csproj and Program.cs implementing the multi scenario container (one image, IT_SCENARIO env var dispatches between happy-path, tool-calling, tool-calling-approval, toolbox, mcp-toolbox, and custom-storage). The toolbox, mcp-toolbox, and custom-storage branches are placeholders pending API surface stabilization.
* Dockerfile and dockerignore in the test container project, using the contributor pattern matching the investigation work (host side dotnet publish, container only does COPY out/).
* scripts/it-build-image.ps1 with mandatory Registry parameter (no hardcoded ACR), content hashed tags so unchanged source results in a no op push, and emits IT_HOSTED_AGENT_IMAGE for shells and CI to consume.
* slnx entry for both new projects.
* README in the IT project covering env vars, image build, scenario table, and current placeholder status.

Steps still pending: end to end smoke (step 5) and CI workflow integration (step 6) require a live Foundry deployment and ACR push, so they land in follow up commits.

* Foundry.Hosting.IntegrationTests: address PR 5598 review feedback

Fix issues raised by Copilot review:

* it-build-image.ps1: hash file contents, not the path list, so any source edit produces a fresh tag. Normalize Registry input by stripping scheme and trailing slash before deriving the ACR short name. Validate the short name is non empty.
* HostedAgentFixture: route GetAgentAsync through _adminClient (which has the FoundryFeaturesPolicy attached) instead of through _projectClient.AgentAdministrationClient (which does not).
* HostedAgentFixture FoundryFeaturesPolicy: replace Headers.Add with Remove plus Add so retries cannot accumulate duplicate headers.
* HappyPath, ToolCalling, ToolCallingApproval, CustomStorage tests: create the AgentSession before turn 1 and reuse it for both turns. The previous pattern created the session after turn 1 so turn 2 had no link to turn 1, defeating the multi turn assertion.

* .NET: Foundry.Hosting.IntegrationTests: constrain to net10.0 + dotnet format autofix

- Set <TargetFrameworks>net10.0</TargetFrameworks>: the project references both
  Microsoft.Agents.AI.Foundry.Hosting (net8/9/10 only) and AgentConformance.IntegrationTests
  (net10.0;net472 — inherits the tests-default TFM list). The intersection is net10.0;
  the previous $(TargetFrameworksCore) triple caused NU1702 + System.Text.Json version
  conflicts on the net8.0/net9.0 builds because AgentConformance had no matching asset.
- Apply `dotnet format` autofix on the test files (IDE0005, IDE0009, IDE0032, IMPORTS).

* .NET: Foundry.Hosting.IntegrationTests.TestContainer/Program.cs: add UTF-8 BOM

CI's check-format requires charset=utf-8-bom per .editorconfig.

* Foundry.Hosting IntegrationTests: wire end-to-end CI flow against hosted agents

Make the integration tests usable end-to-end against a live Foundry deployment, including
a per-run rebuild of the test container so framework code changes are exercised.

Fixture (HostedAgentFixture.cs)

* Switch from per-run unique agent names to stable scenario-keyed names (it-happy-path,
  it-tool-calling, ...). The agent's managed identity carries the Azure AI User role on
  the project scope, which is required for inbound inference; deleting the agent recycles
  the MI and breaks that role assignment, so we keep the agent across runs and only churn
  versions.
* Add IT_RUN_ID env var to defeat Foundry's content-addressed version dedup; otherwise a
  rerun just receives the existing version and Dispose deletes it.
* PATCH the per-agent endpoint with AgentEndpointConfig (Responses protocol, version
  selector at 100% to the new version). Without this, /agents/{name}/endpoint/protocols/
  openai/responses returns HTTP 400.
* Build a per-agent ProjectOpenAIClient (not the cached projectClient.ProjectOpenAIClient,
  which is bound to the project-level URL); set AgentName in options so the URL routes
  through the agent endpoint, and add the Foundry-Features header to the inference
  pipeline.
* Use Versions (which serializes to container_protocol_versions) instead of the
  deprecated ProtocolVersions; the server now rejects the legacy field.
* On Dispose, delete only the version this fixture created. Never delete the agent.

Tests

* Tag every HostedAgentTests class with [Trait("Category", "FoundryHostedAgents")] so the
  CI workflow can route them to a separate Foundry project than the rest of the
  integration suite.

CI workflow (.github/workflows/dotnet-build-and-test.yml)

* Add a foundryHosting paths-filter covering Microsoft.Agents.AI.Foundry.Hosting and its
  in-repo dependency chain (Foundry, Agents.AI, Agents.AI.Abstractions), the test
  container, the test fixture, Directory.Packages.props, the build script, and this
  workflow file. Skip the costly hosted-agent steps when none of those changed.
* Add "Build and push Foundry Hosted Agents test container" step that invokes
  scripts/it-build-image.ps1 against vars.IT_HOSTED_AGENT_REGISTRY and pipes the resulting
  IT_HOSTED_AGENT_IMAGE=<tag> into GITHUB_ENV.
* Add "Run Foundry Hosted Agents Integration Tests" step that filters in only the new
  trait, with AZURE_AI_PROJECT_ENDPOINT/AZURE_AI_MODEL_DEPLOYMENT_NAME pointed at
  IT_HOSTED_AGENT_PROJECT_ENDPOINT/IT_HOSTED_AGENT_MODEL_DEPLOYMENT_NAME (Tao project,
  East US 2; the SK IT project's region does not yet support hosted agents preview).
* Exclude the new trait from the existing "Run Integration Tests" step.
* TEMP: drop the != 'pull_request' guard on the new steps and on Azure CLI Login when the
  paths-filter triggers, so PR #5598 can validate the wiring before promoting to merge
  queue only. Restore the original guard after one green PR run.

Build script (scripts/it-build-image.ps1)

* Hash now spans TestContainer source AND its referenced framework projects so any
  framework code change forces a fresh tag and a real docker push; the previous
  TestContainer-only hash silently reused stale images on framework edits.

Bootstrap script (dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-bootstrap-agents.ps1)

* New idempotent script that creates the six stable scenario agents and grants Azure AI
  User on the project scope to each agent's MI. Run once per Foundry project. Includes
  AAD-graph propagation retries because newly created MIs take time to appear there.

README (dotnet/tests/Foundry.Hosting.IntegrationTests/README.md)

* Document the bootstrap prerequisite, the regional caveat (East US 2 is the only region
  we have validated; East US returned "Unsupported region" at the time of writing), the
  per-run image rebuild, and the CI wiring including the SP RBAC requirements.

SDK pin (TEMP)

* Bump Microsoft.Agents.AI.Foundry.Hosting's Azure.AI.Projects VersionOverride to
  2.1.0-alpha.20260505.1 from the azure-sdk public daily feed (added to nuget.config).
  This release is the first that builds the per-agent inference URL as
  /agents/{name}/endpoint/protocols/openai (the 2.1.0-beta.1 release builds
  .../openai/openai/v1, which the server rejects). Revert both the feed and the override
  once the URL fix lands in a stable Azure.AI.Projects release.

* Foundry.Hosting IntegrationTests: revert alpha SDK pin; move endpoint PATCH to bootstrap

The alpha SDK pin (Azure.AI.Projects 2.1.0-alpha.20260505.1 from the azure-sdk public
daily feed) was needed only for the URL routing fix and the strongly-typed
AgentEndpointConfig/PatchAgentOptions wrapper. We do not need either right now: the
fixture stays compatible with the public 2.1.0-beta.1 by moving the one-time endpoint
PATCH to the bootstrap script (it sets version_selector to FixedRatio @latest, so each
new fixture run becomes the served version automatically without a per-run PATCH from
the test code). The hosted-agent invocation path will start working end-to-end once the
URL routing fix lands in a stable Azure.AI.Projects release; until then the tests stay
[Fact(Skip = ...)] as documented.

* Revert dotnet/nuget.config: drop the azure-sdk-for-net public feed.
* Revert Microsoft.Agents.AI.Foundry.Hosting.csproj VersionOverride to 2.1.0-beta.1.
* Revert Microsoft.Agents.AI.Foundry.UnitTests and Microsoft.Agents.AI.Foundry.Hosting.UnitTests
  Azure.AI.Projects pin (they had been bumped to align Azure.Core 1.54 transitive).
* Drop the AgentEndpointConfig PATCH block from HostedAgentFixture.cs (the type is
  alpha-only). Replace with a comment pointing at the bootstrap script.
* Bootstrap script (it-bootstrap-agents.ps1) now also PATCHes each agent's endpoint
  with version_selector=@latest if not already set. Idempotent.

* Foundry.Hosting IntegrationTests: drop accidentally committed filtered.slnx

* Foundry.Hosting IntegrationTests: revert TEMP PR override on Azure CLI Login + IT steps

The previous attempt to validate the new hosted-agent IT wiring on PR #5598 failed
because the PR is from a fork (rogerbarreto/agent-framework-public). GitHub never passes
environment secrets to fork PRs regardless of event-name guards on individual steps,
so 'azure/login@v2' fails with 'client-id and tenant-id are not supplied'. Restore the
original github.event_name != 'pull_request' guard. The new steps will execute on
push to main and on merge_group runs.

* Foundry.Hosting IntegrationTests: invoke build-and-push script with absolute path

The pwsh shell on the GitHub Actions runner couldn't resolve ./scripts/it-build-image.ps1
when the step had no working-directory set; the step inherits the runner's PWD which is
not always the repo root after preceding steps. Use github.workspace explicitly to remove
the ambiguity.

* Foundry.Hosting IntegrationTests: move it-build-image.ps1 inside the IT project tree

The previous location at scripts/it-build-image.ps1 lived outside the sparse-checkout
paths the workflow uses (.github, dotnet, python, declarative-agents), so the runner
never had the file when the new step tried to invoke it. Move the script next to its
sibling it-bootstrap-agents.ps1 inside the IT project tree, and anchor its relative
paths to the repo root via  so callers can invoke it from any PWD.

* Move scripts/it-build-image.ps1 -> dotnet/tests/Foundry.Hosting.IntegrationTests/scripts/it-build-image.ps1
* Add Push-Location to the resolved repo root inside the script (Pop-Location in finally)
  so the existing relative paths (TestContainerProject, hashed src dirs) keep working
  no matter where the script is invoked from.
* Update the workflow path filter and the step's invocation path to the new location.

* Foundry.Hosting IntegrationTests: enable 5 HappyPath tests on the live Foundry endpoint

The fixture already constructs ProjectOpenAIClient via the per-agent path that beta.1
supports (new ProjectOpenAIClient(uri, cred, opts { AgentName })), so no SDK pin bump
is required to run the smoke tests end-to-end. Un-skip the 5 tests that pass against
the live test container.

Tests un-skipped (verified passing locally against tao-foundry-prj):

* RunAsync_ReturnsNonEmptyTextAsync
* RunStreamingAsync_YieldsAtLeastOneUpdateAsync
* MultiTurn_WithPreviousResponseId_PreservesContextAsync
* StoredFalse_Baseline_DoesNotPersistResponseAsync
* Instructions_FromContainerDefinition_AreObeyedAsync

Tests still skipped with a more specific reason (4 of 9 in HappyPath plus all
ToolCalling*, McpToolbox, Toolbox, CustomStorage) because the test container does not
yet emit usable response_id / conversation_id chains, and the placeholder scenarios are
not implemented in the test container's Program.cs. These are test container limitations,
not infra bugs, and can be un-skipped as the container surfaces stabilize.

* Foundry.Hosting IntegrationTests: extract hosted IT into parallel job, add Workflows dep

Address Wesley's review feedback on PR #5598:

1. Pull Foundry hosted-agent IT into its own dotnet-foundry-hosted-it job that runs in parallel to dotnet-build and dotnet-test. Same path-filter gate keeps it skipped on unrelated edits. Builds only the filtered solution containing Foundry.Hosting.IntegrationTests and src deps. dotnet-build-and-test-check now waits on it too.

2. Add Microsoft.Agents.AI.Workflows to the foundryHosting paths-filter and to hashedDirs in it-build-image.ps1 since Foundry.Hosting transitively depends on it.

TFM constraint on the IT csproj stays at net10.0 because AgentConformance.IntegrationTests targets net10/net472 and is consumed by ~12 other IT projects on net472.

---------

Co-authored-by: Roger Barreto <rbarreto@microsoft.com>
2026-05-06 16:08:15 +00:00
Roger BarretoandGitHub b12109b7e4 .NET: Bump MEAI to 10.5.1 and add Foundry per-call x-client header support (#5652)
* Bump MEAI to 10.5.1 and add per-call x-client header support

Replaces the brittle UserAgentResponsesClient subclass with a clean
per-call x-client-* header pipeline built on the new Microsoft.Extensions.AI
10.5.1 OpenAIRequestPolicies hook.

Public surface (Microsoft.Agents.AI.Foundry, [Experimental(MAAI001)]):
* chatOptions.WithClientHeader(name, value) and .WithClientHeaders(IEnumerable)
  validate the x-client- prefix (case-insensitive), apply all-or-nothing on
  bulk, and throw InvalidOperationException on foreign-typed slot collision
* myAgent.AsBuilder().UseClientHeaders().Build() opts a customer-built agent
  into the pipeline; idempotent via agent.GetService<ClientHeadersAgent>()
* Foundry-built agents (FoundryAgent.Create*) pre-wire automatically

Internals:
* ClientHeadersAgent decorator snapshots the dict at scope-push time so
  concurrent runs sharing a ChatOptions reference do not leak headers
* ClientHeadersScope is an AsyncLocal<IReadOnlyDictionary<string,string>?>
  with LIFO push/dispose semantics
* ClientHeadersPolicy singleton stamps headers via Headers.Set so per-call
  values overwrite any same-name header from earlier policies and so
  duplicate registration is value-stable
* OpenAIRequestPoliciesReflection dedups against MEAI's private _entries
  field and falls back to AddPolicy on any reflection failure; a CI test
  asserts the field shape on every MEAI bump

Hosting cleanup:
* Deleted UserAgentResponsesClient and its dummy throwing pipeline
* HostedAgentUserAgentPolicy is now registered via OpenAIRequestPolicies
  in FoundryHostingExtensions.TryApplyUserAgent

Tests:
* 19 new unit tests in ClientHeadersExtensionsTests.cs covering validation,
  AsyncLocal isolation, snapshot semantics, end-to-end wire stamping, and
  shared-chat-client dedup
* Updated OpenTelemetryAgentTests for MEAI 10.5.1 changes to web_search
  serialization and the reduced tool definition payload when sensitive
  data capture is disabled

Microsoft.Extensions.Compliance.Abstractions stays at 10.5.0 because no
10.5.1 release exists on nuget.org.

* Address PR review: pre-wire AsAIAgent path and dedup TryApplyUserAgent

* FoundryAgent: extract WireClientHeaders helper and call it from the
  internal (AIProjectClient, ChatClientAgent) constructor used by
  AzureAIProjectChatClientExtensions.AsAIAgent so those Foundry-built
  agents also pre-wire the x-client header pipeline.
* Foundry.Hosting TryApplyUserAgent: dedup HostedAgentUserAgentPolicy
  registration per OpenAIRequestPolicies instance via
  ConditionalWeakTable so per-request resolution does not grow the
  policy list unboundedly on singleton agents.

* Add tests covering AsAIAgent pre-wire and TryApplyUserAgent dedup

Backs the PR review fixes from a4c8f91 with regression tests:
* ClientHeadersExtensionsTests: AsAIAgent_FoundryAgent_HasPreWiredClientHeadersAgent
  asserts the FoundryAgent built via AzureAIProjectChatClientExtensions.AsAIAgent
  contains a ClientHeadersAgent in its delegating chain (catches future
  regressions of the bypass).
* ClientHeadersExtensionsTests: FoundryAgent_PublicConstructor_HasPreWiredClientHeadersAgent
  covers the public constructor path the same way.
* ClientHeadersExtensionsTests: UseClientHeaders_RepeatedRegistrations_OnSameChatClient_OnlyRegistersOnce
  invokes UseClientHeaders 25 times on a shared chat client and asserts via
  reflection that OpenAIRequestPolicies._entries length is exactly 1.
* HostedTryApplyUserAgentDedupTests: two tests asserting
  FoundryHostingExtensions.TryApplyUserAgent stays at one entry per
  OpenAIRequestPolicies instance after 50 calls on the same agent and across
  distinct agents on different chat clients.

* Move tests next to their SUT

Removes the dedicated HostedTryApplyUserAgentDedupTests.cs test class.
Tests are co-located with the SUT they exercise:

* FoundryAgentTests.cs gains the Constructor_PreWiresClientHeadersAgent
  and Constructor_FromAsAIAgentExtension_PreWiresClientHeadersAgent
  cases, since FoundryAgent is the SUT for the pre-wire behavior.
* HostedOutboundUserAgentTests.cs gains the two TryApplyUserAgent dedup
  cases, since FoundryHostingExtensions.TryApplyUserAgent is the SUT
  it already covers.
* ClientHeadersExtensionsTests.cs keeps only the
  UseClientHeaders_RepeatedRegistrations_OnSameChatClient_OnlyRegistersOnce
  case, which exercises the public ClientHeadersExtensions surface.

* Remove redundant WithCancellation on inner streaming call

ct is already passed to InnerAgent.RunStreamingAsync, so
.WithCancellation(ct) on the resulting IAsyncEnumerable is a no-op.
Caught by Sergey on PR review.

* Address PR review: surface downstream MEAI experimental ID

* Add AIOpenAIRequestPolicies = MEAIExperiments alias to
  DiagnosticIds.Experiments (matches the existing AIResponseContinuations,
  AIMcpServers, AIFunctionApprovals pattern).
* Mark public ClientHeadersExtensions with [Experimental(AIOpenAIRequestPolicies)]
  instead of AgentsAIExperiments. Consumers now see the MEAI001 warning,
  surfacing the dependency on MEAI's experimental OpenAIRequestPolicies hook.
* Mark internal OpenAIRequestPoliciesReflection with the same alias to
  suppress warnings at the source rather than via project-wide NoWarn.
* Remove MEAI001 from Foundry csproj NoWarn (kept on Foundry.Hosting where
  pre-PR usages remain).
* Clarify ClientHeadersScope XML doc: AsyncLocal flows values forward but
  does NOT auto-restore on method return; explicit using/Dispose is what
  gives stack-style LIFO semantics.
2026-05-06 14:43:08 +00:00
Teja KusireddyandGitHub 806075ae61 .NET: Fix YAML block scalar parsing for file skills (#5610)
* Fix YAML block scalar parsing for file skills

* Address block scalar parsing review feedback
2026-05-05 16:32:05 +00:00
Peter IbekweandGitHub d2e694dfe1 .NET: Fix QuestionExecutor looping after GotoAction re-entry in declarative workflows (#5635)
* Fix QuestionExecutor looping after GotoAction re-entry in declarative workflows

* Addressed failing integration test and promptcount
2026-05-05 15:53:31 +00:00
Jacob AlberandGitHub 9f3f7fd03b fix: JSON Serialization issue with MultiPartyConversation (#5653)
When MultiPartyConversation gets saved during checkpointing, the data for the chat history is not persisted, resulting in failures to deserialize after. The fix is to make the history visible to the source generated serialization code.
2026-05-05 15:36:05 +00:00
westeyandGitHub e9a6d43237 .NET: Improve Todo multithreading and inject todos into message list (#5655)
* Improve Todo multithreading and inject todos into message list

* Address PR comments
2026-05-05 15:21:51 +00:00