Commit Graph

930 Commits

  • Remove duplicate WebhookKey properties from merge
    Both our branch and main added WebhookKey to the Anthropic test
    mock classes, resulting in CS0102 duplicate definition errors.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Merge remote-tracking branch 'upstream/main' into dotnet-split-integration-tests
    # Conflicts:
    #	dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicSkillsIntegrationTests.cs
  • .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>
  • .NET: fix: align Anthropic Extensions AI version (#5709)
    * fix: align Anthropic Extensions AI version
    
    * test: update Anthropic test stubs for new interfaces
    
    ---------
    
    Co-authored-by: Jacob Alber <jaalber@microsoft.com>
  • Remove unnecessary RT0003 warning suppression
    The RT0003 suppression was added during the Anthropic SDK 12.20.0
    upgrade but the warning no longer fires. Removing it to keep the
    NoWarn list minimal.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • .NET: Refactor harness console rendering (#5751)
    * Refactor harness console rendering
    
    * Fix formatting issues
    
    * Address PR comments
  • .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
  • Rename filter parameters for consistency
    TestProjectNameFilter  -> TestProjectNameIncludeFilter
    TestProjectNameExclude -> TestProjectNameExcludeFilter
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • .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>
  • .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.
  • .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>
  • .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>
  • .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()
  • 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.
  • .NET: Hosted-Files sample + AgentSessionFiles SDK companion + integration test (#5698)
    * .NET: Add Hosted-Files sample + alpha AgentSessionFiles SDK companion + integration test
    
    Closes #5691
    
    - Hosted-Files server sample (mirrors python 06_files): 3 local tools reading
      the per-session \C:\Users\rbarreto sandbox volume.
    - SessionFilesClient REPL companion: code-first equivalent of
      zd ai agent files upload using the alpha
      Azure.AI.Projects.AgentSessionFiles SDK (upload/ls/download/rm + session
      lifecycle with isolation key).
    - session-files scenario added to the Foundry.Hosting.IntegrationTests
      multi-scenario harness (PR #5598): SessionFilesHostedAgentFixture +
      SessionFilesHostedAgentTests.UploadAndAgentReadsFileAsync, end-to-end
      validating upload then agent-reads-file (agent_session_id pinned via
      CreateResponseOptions.Patch). Bundled testdata is linked from the sample
      so there is a single source of truth.
    
    * .NET: Hosted-Files: REPL companion now demonstrates file-as-knowledge end-to-end
    
    Adds an 'ask <prompt>' command to SessionFilesClient that pins
    agent_session_id (via CreateResponseOptions.Patch) so the agent invoked from
    the REPL reads files this REPL just uploaded. Surfaces the file content as
    agent knowledge in the same in-process loop instead of telling the user to
    shell out to azd ai agent invoke.
    
    * .NET: Reshape Hosted-Files sample - bake files into image, SessionFilesClient becomes thin chat REPL
    
    The previous SessionFilesClient leaned on the alpha AgentSessionFiles SDK
    to upload files at runtime, which made it diverge from the canonical
    Using-Samples shape (SimpleAgent / SimpleInvocationsAgent: tiny chat REPLs).
    
    This change:
    
    - Bakes the sample resources/ directory into the published output via a
      Content Include in HostedFiles.csproj. Inside the container the files live
      at /app/resources/. Two local function tools (ListFiles, ReadFile) surface
      them to the model.
    - Reshapes SessionFilesClient as a thin FoundryAgent chat REPL, identical
      shape to SimpleAgent. AGENT_ENDPOINT + AGENT_NAME, that is it.
    - Demo flow: user asks 'Give me the total revenue in the contoso file' and
      the agent answers with the figure read from its bundled file. Validated
      end-to-end locally against Hosted-Files on http://localhost:60419.
    - Bypasses SampleEnvironment alias on optional env vars to avoid stdin
      prompts when running unattended.
    
    The Foundry.Hosting.IntegrationTests session-files scenario continues to
    validate the alpha AgentSessionFiles SDK end-to-end (upload + agent reads
    from session HOME) and is unchanged.
    
    * .NET: Foundry.Hosting.IntegrationTests TestContainer - constrain session-files tools to $HOME
    
    Addresses the path-traversal review comment on the session-files scenario:
    ResolveSessionPath in TestContainer used to allow absolute paths and ..
    traversals, which (when chained with indirect prompt injection in an
    uploaded file) would let the model read or list arbitrary container files
    via the ReadFile / ListFiles tools.
    
    Mirrors the canonicalize + StartsWith(home) pattern from the framework's
    own FileSystemAgentFileStore.ResolveSafePath: rejects rooted paths, calls
    Path.GetFullPath, and verifies the result stays under $HOME, throwing
    ArgumentException otherwise.
    
    The Hosted-Files sample is already safe (uses Path.GetFileName which strips
    any directory component) so no change there. The integration test continues
    to upload and read 'contoso_q1_2026_report.txt', a single relative filename
    which passes the new validation unchanged.
    
    * .NET: SessionFilesHostedAgentTests - shrink to alpha SDK round-trip
    
    The previous test attempted to pin agent_session_id into the /responses
    payload via JsonPatch so the agent would read the file uploaded through
    AgentSessionFiles. The Foundry alpha service now consistently rejects the
    explicit-session-id pin with HTTP 400 conflict on /responses, regardless
    of whether the session was pre-created via AgentAdministrationClient or
    left to be auto-provisioned, so the agent leg of the test is no longer
    reachable from the SDK surface.
    
    Reshape the test to exercise what the alpha SDK actually guarantees:
    create session, upload, list (assert presence + size), download (assert
    deterministic token), delete (assert removed), cleanup. Everything stays
    inside Azure.AI.Projects.Agents.AgentSessionFiles.
    
    Verified live against tao-foundry-prj:
      UploadListDownloadAndDeleteAsync passed in 30s.
      Full Foundry.Hosting.IntegrationTests run: 25 total, 6 passed, 19
      skipped (existing placeholders), 0 failed.
    
    * .NET: SessionFilesHostedAgentTests - rewrite as upload-then-FoundryAgent.RunAsync e2e
    
    Per review feedback the integration test must validate the hosted agent
    itself: client uploads a file via the alpha AgentSessionFiles SDK, then
    FoundryAgent.RunAsync invokes the deployed agent and the agent's
    container-side ReadFile tool surfaces the uploaded file content into the
    response.
    
    Test flow:
      1. agent.RunAsync(warmup) - platform provisions a per-session container.
      2. AgentAdministrationClient.GetSessionsAsync(latest) - resolve the
         just-provisioned agent_session_id.
      3. AgentSessionFiles.UploadSessionFileAsync - upload contoso file to
         that session, asserts BytesWritten + GetSessionFiles listing.
      4. agent.RunAsync(real prompt, options=PreviousResponseId chain) -
         chained to warmup so the platform routes back to the same container.
      5. Assert response contains '1,482.6' (deterministic token from file).
      6. Best-effort cleanup.
    
    The test is annotated with [Fact(Skip=...)] right now: the Foundry alpha
    service consistently returns HTTP 400 conflict on /responses requests
    that link to a prior session via previous_response_id, conversation_id,
    or agent_session_id pinning - verified across multiple retries with
    multiple chaining strategies. Without that link we cannot route the
    second invocation to the same container the file was uploaded to. When
    the platform regression is resolved, removing the Skip will exercise
    the full flow.
    
    Full Foundry.Hosting.IntegrationTests run with this change: 25 total,
    5 passed, 20 skipped (existing placeholders + this one), 0 failed.
    
    * .NET: SessionFilesHostedAgentTests - end-to-end upload-then-FoundryAgent.RunAsync now passes
    
    The blocker was a routing problem combined with a platform race:
    
    1. Routing two /responses calls to the same per-session container.
       - agent_session_id pin in body -> 400 (platform treats it as create)
       - conversation_id created at project root -> 404 at agent endpoint
       - previous_response_id chain -> different session
       The working answer is to create the conversation on a per-agent
       ProjectOpenAIClient (AgentName option, URL becomes
       /agents/{name}/endpoint/protocols/openai/conversations) and pass that
       conversation_id on both calls. Both then resolve to the SAME
       x-agent-session-id (verified by capturing the response header).
    
    2. Race after AgentSessionFiles upload. The upload mutates session/
       conversation revision; a /responses call issued immediately after
       400-conflicts with 'modified concurrently. Please retry.' Bounded
       exponential retry handles it (5 attempts, 2*attempt seconds).
    
    Test flow:
      1. Create per-agent OpenAI client + ProjectConversationsClient + ProjectResponsesClient.
      2. CreateProjectConversationAsync on the per-agent client.
      3. Warm-up agent.RunAsync(prompt, ChatOptions { ConversationId = ... })
         - captures x-agent-session-id from the response header via a custom pipeline policy.
      4. AgentSessionFiles.UploadSessionFileAsync to that session id.
      5. ProjectResponsesClient.CreateResponseAsync (raw, retry-on-conflict)
         with the same conversation_id -> routes back to the same container.
      6. Assert response contains '1,482.6' (deterministic token from file).
      7. Cleanup: delete file, leave session for TTL.
    
    Verified live against tao-foundry-prj:
      UploadedFile_IsReadByHostedAgentAsync passed in 24.9s.
      Full Foundry.Hosting.IntegrationTests run: 25 total, 6 passed, 19
      skipped (existing placeholders), 0 failed.
    
    * .NET: address Copilot PR review findings
    
    - agent.manifest.yaml: description + tags now reflect bundled-files agent (image-baked /app/resources), not the obsolete session-sandbox tools the prior shape claimed.
    - SessionFilesHostedAgentTests: wrap test body in try/finally to call DeleteConversationAsync on the conversation we created (matches HappyPathHostedAgentTests pattern; prevents conversation leakage across runs).
    - ResponseHeaderCapturePolicy: drop unused LastRequestBody capture left over from diagnosis.
    
    Test still passes live (40s).
    
    * .NET: Hosted-Files: split into bundled vs session-file tool pairs
    
    The previous Hosted-Files agent only exposed bundled (image-baked) file
    knowledge. The platform also surfaces session-uploaded files at \C:\Users\rbarreto
    inside the per-session container per container-image-spec.md line 172
    (verified live by SessionFilesHostedAgentTests). The sample now teaches
    both patterns.
    
    Two distinct tool pairs, each scoped to its own root:
    
      Bundled (image-baked):    ListBundledFiles, ReadBundledFile
                                -> /app/resources/ (BUNDLED_FILES_DIR override)
    
      Session-uploaded (\C:\Users\rbarreto): ListSessionFiles, ReadSessionFile
                                -> \C:\Users\rbarreto (default /home/session per container spec)
    
    Security model -- distinct tools, distinct sandboxes:
      - Tool input is a fileName, not a path. Schema-level: model cannot
        request directories or traversals.
      - Path.GetFileName(input) strips any directory components.
      - Path.GetFullPath + StartsWith(root) check rejects anything outside
        the tool's root, mirroring FileSystemAgentFileStore.ResolveSafePath.
      - Read-only, non-recursive listing. No glob, no '..'.
      - Failures non-revealing: 'File <name> not found in <scope>.'
    
    The two roots are physically isolated (image-baked vs platform-mounted
    per-session volume). A bundled-root tool can never reach a session file
    and vice-versa, even if the implementation has a bug.
    
    README updated to document both flows, the security pattern, and cite
    the container-image-spec.md line 172 contract for \C:\Users\rbarreto. Live IT
    SessionFilesHostedAgentTests.UploadedFile_IsReadByHostedAgentAsync
    re-passed in 42s after the change (TestContainer is unchanged; the
    sample-agent split does not affect the IT).
    
    * .NET: Hosted-Files README - fix broken relative link to IT (4..5 dots)
  • .NET: Foundry.Hosting IT - eliminate MSBuild parallel-output races (#5725)
    * .NET: Foundry.Hosted IT - fix MSBuild parallel-output races
    
    Two surgical changes inside the dotnet-foundry-hosted-it job:
    
    1. Replace dotnet build <slnx> -f net10.0 with dotnet build <test.csproj>. The test csproj pins TargetFrameworks=net10.0 and its ProjectReference closure gives MSBuild a single-rooted graph, eliminating the duplicate inner-builds that race on bin/obj. Drops the two New-FilteredSolution.ps1 steps.
    
    2. In it-build-image.ps1, drop the -UsePrebuiltProjectReferences switch and always pass --no-dependencies to dotnet publish. Publish now resolves TestContainer's framework refs by reading prebuilt DLLs and never re-touches them. Replaces the partial-mitigation in PR #5689 with a structural fix.
    
    Local validation confirmed published Foundry.dll has identical mtime and bytes as the prebuild output.
    
    * .NET: dotnet test - use --project flag for Microsoft Testing Platform
  • Re-enable Foundry OpenAPI server-side tool integration test
    Remove Skip="For manual testing only" from
    AsAIAgent_WithOpenAPITool_NativeSDKCreation_InvokesServerSideToolAsync.
    The test already uses RetryFact(3 retries, 5s delay) to handle
    transient failures from the external restcountries.com API.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Fix CheckSystem test case to expect 1 response
    The CheckSystem workflow sends a 'PASSED!' SendActivity when all system
    variables are populated, producing 1 AgentResponseEvent. The test case
    had min_response_count: 0 with no max, so the assertion defaulted max
    to 0 and failed with 'Response count greater than expected: 0 (Actual: 1)'.
    Updated to expect exactly 1 response, matching the SendActivity pattern.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Re-enable CheckSystem declarative integration tests
    The CheckSystem.yaml tests were temporarily skipped in PR #4270 during
    the Azure.AI.Projects 2.0.0-beta.1 SDK update. Since then, the system
    variable plumbing (SystemScope, SetLastMessageAsync, conversation
    initialization) has been significantly updated and stabilized. The
    other tests in these same files pass reliably using the same
    infrastructure.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Fix Anthropic unit test mocks for SDK 12.20.0 interface changes
    Add missing interface members: IAnthropicClient.WebhookKey,
    IBetaService.MemoryStores, IBetaService.Webhooks, IBetaService.UserProfiles
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Upgrade Anthropic SDK 12.13.0 -> 12.20.0 to fix M.E.AI incompatibility
    Fixes MissingMethodException on WebSearchToolResultContent.get_Results()
    caused by Anthropic 12.13.0 being compiled against an older
    Microsoft.Extensions.AI.Abstractions version.
    
    Suppress RT0003 in AI.Abstractions.csproj as the transitive reference
    from the upgraded Anthropic SDK conflicts with the explicit one.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • .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
  • .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.
  • Split DurableTask/AzureFunctions integration tests into dedicated CI job
    - Add -TestProjectNameExclude parameter to New-FilteredSolution.ps1
    - Add 'functions' and 'core' path filters to paths-filter job
    - Exclude DurableTask/AzureFunctions from main dotnet-test job
    - Remove emulator setup from dotnet-test (no longer needed)
    - Add new dotnet-test-functions job (ubuntu/net10.0 only, path-conditional)
    - Update merge gate and report job to include dotnet-test-functions
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • .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>
  • .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>
  • 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>
  • .NET: Python: Add dotnet integration test report to CI (#5515)
    * Add dotnet integration test report to CI
    
    - Add --report-junit flag to dotnet integration test step to generate
      JUnit XML alongside TRX, with explicit --results-directory to
      centralize output in IntegrationTestResults/
    - Upload JUnit XML artifacts from each matrix leg (net10.0/ubuntu,
      net472/windows) as dotnet-test-results-{framework}-{os}
    - Add dotnet-integration-test-report job that downloads artifacts,
      runs the existing aggregate.py script, posts markdown to Job Summary,
      and saves trend history via actions/cache
    - Refactor aggregate.py to discover JUnit XML files recursively,
      supporting both pytest (pytest.xml) and xunit (*.junit.xml) layouts
    - Handle provider name derivation for dotnet artifact naming convention
    - Fix nodeid collision when same test runs under multiple frameworks
      by qualifying keys with provider when collisions are detected
    - Improve module extraction for dotnet C# classnames (recognizes
      IntegrationTests/UnitTests namespace segments)
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * chore: trigger dotnet CI for report validation
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix: use .junit extension (not .junit.xml) for xunit v3 output
    
    xUnit v3 generates files with .junit extension, not .junit.xml.
    Update upload glob and aggregate.py discovery to match.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix: use deterministic provider-qualified keys for dotnet tests
    
    Always prefix dotnet test keys with provider (e.g. net10.0 (ubuntu)::TestName)
    to ensure stable, comparable counts across runs regardless of file parse order.
    Also show Executed (passed+failed) instead of Total in summary table.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix: match Python report summary format (Total, passed/total, etc.)
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * feat: split dotnet report into per-framework tables
    
    Dotnet tests run on multiple frameworks (net10.0, net472). Instead of
    one combined table with unstable totals, show separate sections per
    framework — each with its own summary row and per-test table. Python
    reports retain the original single-table format.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Re-enable 7 flaky dotnet integration tests with increased timeouts
    
    Increase timeouts to reduce timing-related flakiness in LLM-backed
    integration tests (issue #4971):
    
    - ExternalClientTests: 60s -> 120s default timeout
    - SamplesValidationBase: 60s -> 120s default timeout
    - ConsoleAppSamplesValidation: 90s -> 150s for long-running tests
    - AzureFunctions SamplesValidation: 2min -> 3min orchestration timeout,
      60s -> 90s per-step WaitForConditionAsync timeouts
    
    Remove all Skip=Flaky annotations and unused SkipFlakyTimingTest constants.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Re-skip LLM non-determinism flaky tests, keep timeout fixes
    
    Re-skip SingleAgentOrchestrationHITLSampleValidationAsync and
    LongRunningToolsSampleValidationAsync - these fail due to LLM producing
    extra review notifications, not timeouts. Updated skip reasons to
    accurately describe the root cause. Reverted unnecessary timeout change
    on the skipped LongRunningTools test.
    
    The remaining 5 re-enabled tests with timeout increases are stable.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Enable Anthropic integration tests in CI
    
    Replace hardcoded skip with conditional skip pattern (matching
    CopilotStudio approach): tests gracefully skip when ANTHROPIC_API_KEY
    is missing, and run when present.
    
    Changes:
    - AnthropicChatCompletionFixture: try/catch in InitializeAsync with
      Assert.Skip on missing config (replaces hardcoded SkipReason)
    - AnthropicSkillsIntegrationTests: same pattern per test method
    - dotnet-build-and-test.yml: wire up ANTHROPIC_API_KEY,
      ANTHROPIC_CHAT_MODEL_NAME, and ANTHROPIC_REASONING_MODEL_NAME
      env vars to the integration test step
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Fix missing System using in AnthropicSkillsIntegrationTests
    
    Add 'using System;' for InvalidOperationException in try/catch blocks.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Skip flaky SingleAgentOrchestrationChainingSampleValidationAsync
    
    LLM non-determinism causes Assert.NotNull failures on orchestration
    results. Skip until test logic is hardened against non-deterministic
    LLM responses.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Re-enable HITL and LongRunningTools tests with timeout and flexibility fixes
    
    - Remove Skip attribute from SingleAgentOrchestrationHITLSampleValidationAsync
    - Remove Skip attribute from LongRunningToolsSampleValidationAsync
    - Increase timeout from 120s/90s to 180s to accommodate 2+ LLM round-trips
    - Replace rigid 2-cycle assertion with flexible approval logic that handles
      extra review cycles from LLM non-determinism
    
    Fixes the two failure modes identified in #4971:
    1. Timeout: 120s/90s was insufficient for multiple LLM calls under CI load
    2. Extra notifications: Assert.Fail on 3rd+ review cycle was too rigid
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Increase AzureFunctions LongRunningTools test timeouts from 90s to 180s
    
    The LongRunningToolsSampleValidationAsync test in the AzureFunctions integration
    tests was failing in CI with TimeoutException at the 'Content published
    notification is logged' step. The 90-second timeouts are too tight for CI
    environments where LLM calls and orchestration overhead can be slow.
    
    Increased all three WaitForConditionAsync timeouts from 90s to 180s:
    - Waiting for human feedback notification
    - Waiting for publish notification (the step that was failing)
    - Waiting for orchestration completion
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Merge main and fix dotnet report path after flaky_report rename
    
    Merge upstream/main which renamed scripts/flaky_report/ to
    scripts/integration_test_report/ (from Python PR #5454). Update the
    dotnet-build-and-test workflow to reference the new path.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Add RetryFact to DurableTask and AzureFunctions integration tests
    
    These tests interact with LLMs via stdin/stdout (DurableTask) or HTTP
    (AzureFunctions) and are inherently non-deterministic. Unlike the Python
    side which uses pytest-retry, the dotnet tests had no retry mechanism
    and a single transient failure would fail the entire CI run.
    
    Changes:
    - Switch [Fact] to [RetryFact(2, 5000)] on all LLM-dependent tests
      across ConsoleAppSamplesValidation, ExternalClientTests,
      WorkflowConsoleAppSamplesValidation, and AzureFunctions SamplesValidation
    - Add re-prompt mechanism to LongRunningToolsSampleValidationAsync:
      if the LLM doesn't invoke the tool within 60s, re-send the prompt
      (up to 2 retries) instead of burning the full timeout
    - Reduce LongRunningTools timeout from 240s to 180s (re-prompt makes
      the extra buffer unnecessary)
    - Leave simple/deterministic tests as [Fact] (SingleAgent, unit tests)
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Add persist-credentials: false to Integration Test Report checkout step
    
    Matches the convention used by other checkout steps in this workflow
    to avoid leaving GITHUB_TOKEN credentials in the local git config.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * small fixes
    
    * disable anthropic failing tests
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • .NET: Foundry.Hosting IT: avoid MSB3026 in publish; fix telemetry UT flake (#5689)
    CI publish step: gate the BuildProjectReferences=false fast-path on an explicit -UsePrebuiltProjectReferences switch (passed by the workflow) instead of marker detection. Adds a preflight error when stale obj/Release/net10.0 outputs would cause CS0579, with actionable recovery instructions.
    
    Telemetry UT flake: AgentFrameworkResponseHandlerTelemetryTests was using a plain List<Activity> for OTel's InMemoryExporter. The exporter writes from background Activity completion callbacks while parallel tests on the same global ActivitySource feed every listener, racing against the assertion's enumeration and throwing 'Collection was modified'. Replaced with a small thread-safe ConcurrentActivityList that locks add/enumerate and returns a snapshot for assertions.
  • .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
  • .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>
  • .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>
  • .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>
  • .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>
  • .NET: Fix flaky declarative test (#5669)
    * Fix flaky declarative test
    
    * Addressed host gating and guid parsing concerns in test file.
  • .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.
  • .NET: Add hosted agent observability sample (#5660)
    * .Net: Add hosted agent observability sample
    
    Mirrors the Python sample added in #5608 for Foundry hosted agents. The
    .NET hosting library already wires OpenTelemetry automatically via
    Microsoft.Agents.AI.Foundry.Hosting (ApplyOpenTelemetry) plus
    Azure.AI.AgentServer.Core's AddAgentHostTelemetry, so no framework
    changes are needed. The sample is documentation plus a runnable artifact
    that produces an interesting span tree (invoke_agent / agent_invoke /
    chat / execute_tool).
    
    Adds Hosted-Observability under FoundryHostedAgents/responses with two
    small tools (GetCurrentLocation, GetWeather), agent.yaml /
    agent.manifest.yaml declaring OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
    (the .NET equivalent of Python's ENABLE_SENSITIVE_DATA), Dockerfile +
    Dockerfile.contributor, .env.example and README explaining the .NET vs
    Python defaults. Project added to agent-framework-dotnet.slnx.
    
    * Address PR feedback: use Random.Shared and add .dockerignore
  • .NET: Fix YAML block scalar parsing for file skills (#5610)
    * Fix YAML block scalar parsing for file skills
    
    * Address block scalar parsing review feedback
  • .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
  • 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.
  • .NET: Improve Todo multithreading and inject todos into message list (#5655)
    * Improve Todo multithreading and inject todos into message list
    
    * Address PR comments
  • .NET: Add allow listing for WebBrowsingTool (#5605)
    * Add allow listing for WebBrowsingTool
    
    * Address PR comments.
  • .NET: feat: Implement message filtering to exclude non-portable content typ… (#5410)
    * feat: Implement message filtering to exclude non-portable content types before forwarding
    
    Co-authored-by: Copilot <copilot@github.com>
    
    * Added unit tests to cover forwarded message filtering within AI Agent executors
    
    Co-authored-by: Copilot <copilot@github.com>
    
    * Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs
    
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
    
    * Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs
    
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
    
    * Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs
    
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
    
    * fix: Disable forwarding of incoming messages in AIAgentHostExecutor tests
    
    Co-authored-by: Copilot <copilot@github.com>
    
    ---------
    
    Co-authored-by: Copilot <copilot@github.com>
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
    Co-authored-by: Jacob Alber <jaalber@microsoft.com>