From e260200a7b4c902178a5c013a744970462e69cb5 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:09:27 +0100 Subject: [PATCH 1/4] .NET: Add Foundry hosted session client samples --- dotnet/agent-framework-dotnet.slnx | 2 + .../Agent_Step25_FoundryToolboxMcp/Program.cs | 30 +--- .../Program.cs | 4 - .../FoundrySkills.cs | 4 - .../ClawAgent/FoundrySkills.cs | 4 - .../InvokeFoundryToolboxMcp.yaml | 6 - .../InvokeFoundryToolboxMcp/Program.cs | 24 +-- .../responses/Hosted-AgentSkills/Program.cs | 28 +--- .../responses/Hosted-AgentSkills/README.md | 6 +- .../Hosted-ToolboxMcpSkills/Program.cs | 4 - .../FoundryHostedSession/.env.example | 2 + .../FoundryHostedSession.csproj | 24 +++ .../FoundryHostedSession/Program.cs | 119 +++++++++++++++ .../FoundryHostedSession/README.md | 86 +++++++++++ .../responses/Using-Samples/README.md | 2 + .../UserIsolationAgent/.env.example | 2 + .../UserIsolationAgent/Program.cs | 141 ++++++++++++++++++ .../UserIsolationAgent/README.md | 122 +++++++++++++++ .../UserIsolationAgent.csproj | 24 +++ 19 files changed, 530 insertions(+), 104 deletions(-) create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/.env.example create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/FoundryHostedSession.csproj create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/README.md create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/.env.example create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/README.md create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/UserIsolationAgent.csproj diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 6f5fa9d46a6..638d6096225 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -388,9 +388,11 @@ + + diff --git a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs index 8dd3f55a8a1..dce06cf7cb9 100644 --- a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs +++ b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs @@ -6,7 +6,6 @@ // discovers the toolbox's tools at runtime and invokes them locally. using System.ClientModel; -using System.ClientModel.Primitives; using System.Net.Http.Headers; using Azure.AI.Projects; using Azure.AI.Projects.Agents; @@ -48,10 +47,6 @@ Endpoint = new Uri(toolboxEndpoint), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, - AdditionalHeaders = new Dictionary - { - ["Foundry-Features"] = "Toolboxes=V1Preview", - }, }, httpClient)); @@ -81,10 +76,7 @@ static async Task CreateSampleToolboxAsync(string name, string endpoint, // script, not the application itself. This helper exists so the sample can // be run end-to-end without first setting a toolbox up by hand. - // The Foundry-Features header is currently required for toolbox CRUD operations. - var options = new AgentAdministrationClientOptions(); - options.AddPolicy(new FoundryFeaturesPolicy("Toolboxes=V1Preview"), PipelinePosition.PerCall); - var adminClient = new AgentAdministrationClient(new Uri(endpoint), credential, options); + var adminClient = new AgentAdministrationClient(new Uri(endpoint), credential); var toolboxClient = adminClient.GetAgentToolboxes(); // Delete existing toolbox if present (ignore 404). @@ -114,26 +106,6 @@ static async Task CreateSampleToolboxAsync(string name, string endpoint, return $"{endpoint}/toolboxes/{created.Name}/mcp?api-version=v{created.Version}"; } -// --------------------------------------------------------------------------- -// Pipeline policy: adds the Foundry-Features header for toolbox CRUD calls -// --------------------------------------------------------------------------- -internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy -{ - private const string FeatureHeader = "Foundry-Features"; - - public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - ProcessNext(message, pipeline, currentIndex); - } - - public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - return ProcessNextAsync(message, pipeline, currentIndex); - } -} - // --------------------------------------------------------------------------- // DelegatingHandler: attaches a fresh Azure AI bearer token to every request // --------------------------------------------------------------------------- diff --git a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs index d8fff025c56..8205e0565a8 100644 --- a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs +++ b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs @@ -38,10 +38,6 @@ Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, - AdditionalHeaders = new Dictionary - { - ["Foundry-Features"] = "Toolboxes=V1Preview", - }, }, httpClient)); diff --git a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs index aea7da4088f..f5ae6aa183d 100644 --- a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs +++ b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs @@ -38,10 +38,6 @@ internal static class FoundrySkills Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, - AdditionalHeaders = new Dictionary - { - ["Foundry-Features"] = "Toolboxes=V1Preview", - }, }, httpClient)); diff --git a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs index b1748c10a39..07e7366fe01 100644 --- a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs +++ b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs @@ -34,10 +34,6 @@ internal static class FoundrySkills Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, - AdditionalHeaders = new Dictionary - { - ["Foundry-Features"] = "Toolboxes=V1Preview", - }, }, httpClient), cancellationToken: cancellationToken).ConfigureAwait(false); diff --git a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml index b5f6f39316e..8d8b8b9dc2a 100644 --- a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml +++ b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml @@ -36,8 +36,6 @@ trigger: serverLabel: foundry_toolbox toolName: tools/list conversationId: =System.ConversationId - headers: - Foundry-Features: Toolboxes=V1Preview output: autoSend: true result: Local.ToolboxTools @@ -49,8 +47,6 @@ trigger: serverLabel: foundry_toolbox toolName: =Env.FOUNDRY_TOOLBOX_DOCS_SERVER_LABEL & "___microsoft_docs_search" conversationId: =System.ConversationId - headers: - Foundry-Features: Toolboxes=V1Preview arguments: query: =Local.SearchQuery output: @@ -66,8 +62,6 @@ trigger: serverLabel: foundry_toolbox toolName: =Env.FOUNDRY_TOOLBOX_WEB_SEARCH_TOOL_NAME conversationId: =System.ConversationId - headers: - Foundry-Features: Toolboxes=V1Preview arguments: search_query: =Local.SearchQuery output: diff --git a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/Program.cs b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/Program.cs index 812d13cf7a2..7c3ecbc7e96 100644 --- a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/Program.cs +++ b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/Program.cs @@ -5,7 +5,6 @@ // through the reserved tools/list operation, then calls microsoft_docs_search from the workflow. using System.ClientModel; -using System.ClientModel.Primitives; using System.Collections.Concurrent; using System.Net.Http.Headers; using Azure.AI.Projects; @@ -19,8 +18,6 @@ using Shared.Workflows; #pragma warning disable OPENAI001 // Experimental API -#pragma warning disable AAIP001 // AgentToolboxes is experimental - namespace Demo.Workflows.Declarative.InvokeFoundryToolboxMcp; /// @@ -148,9 +145,7 @@ Be concise. private static async Task CreateSampleToolboxAsync(string name, string serverLabel, Uri foundryEndpoint, TokenCredential credential) { - AgentAdministrationClientOptions options = new(); - options.AddPolicy(new FoundryFeaturesPolicy("Toolboxes=V1Preview"), PipelinePosition.PerCall); - AgentAdministrationClient adminClient = new(foundryEndpoint, credential, options); + AgentAdministrationClient adminClient = new(foundryEndpoint, credential); AgentToolboxes toolboxClient = adminClient.GetAgentToolboxes(); try @@ -199,21 +194,4 @@ protected override async Task SendAsync( return await base.SendAsync(request, cancellationToken); } } - - private sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy - { - private const string FeatureHeader = "Foundry-Features"; - - public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - ProcessNext(message, pipeline, currentIndex); - } - - public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - return ProcessNextAsync(message, pipeline, currentIndex); - } - } } diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs index 30ac2904a5f..9af0e7b81f0 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/Program.cs @@ -20,7 +20,6 @@ #pragma warning disable AAIP001 // ProjectAgentSkills is experimental using System.ClientModel; -using System.ClientModel.Primitives; using Azure.AI.Projects; using Azure.AI.Projects.Agents; using Azure.Identity; @@ -65,10 +64,7 @@ var credential = new DefaultAzureCredential(); AIProjectClient projectClient = new(new Uri(endpoint), credential); -var adminOptions = new AgentAdministrationClientOptions(); -adminOptions.AddPolicy(new FoundryFeaturesPolicy("Skills=V1Preview"), PipelinePosition.PerCall); -var adminClient = new AgentAdministrationClient(new Uri(endpoint), credential, adminOptions); -ProjectAgentSkills skillsClient = adminClient.GetAgentSkills(); +ProjectAgentSkills skillsClient = projectClient.AgentAdministrationClient.GetAgentSkills(); // ── Provision skills (sample convenience only — NOT a production pattern) ───── // In production, skills are provisioned externally (e.g., via CI/CD or a management script). @@ -153,7 +149,6 @@ static async Task DownloadSkillsAsync(ProjectAgentSkills skillsClient, string[] $"Downloaded skill '{name}' did not contain a SKILL.md at the root."); } } - } // Ensures each requested skill is provisioned in Foundry. For each skill name, checks whether @@ -183,24 +178,3 @@ static async Task EnsureSkillsProvisionedAsync(ProjectAgentSkills skillsClient, } } } - -// Skills is a preview data-plane surface and requires an explicit feature opt-in on every call. -internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy -{ - private const string FeatureHeader = "Foundry-Features"; - - public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - ProcessNext(message, pipeline, currentIndex); - } - - public override ValueTask ProcessAsync( - PipelineMessage message, - IReadOnlyList pipeline, - int currentIndex) - { - message.Request.Headers.Add(FeatureHeader, feature); - return ProcessNextAsync(message, pipeline, currentIndex); - } -} diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md index a658e4f5a8f..40536036de6 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/README.md @@ -165,9 +165,9 @@ Do not substitute `Azure AI Developer`; Microsoft documents that role as insuffi hosted agents. Recreate the role assignment when the agent is deleted and created again, because the new agent receives a new identity. -The Skills API is a preview surface. `Program.cs` adds the required -`Foundry-Features: Skills=V1Preview` header and downloads skills into the writable temporary -directory. The source-deploy application directory (`/app`) is read-only. +The Skills API is a preview surface. `Azure.AI.Projects` applies its required feature opt-in +internally. `Program.cs` downloads skills into the writable temporary directory. The source-deploy +application directory (`/app`) is read-only. ### Step 4: clean up diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs index 00dc5330122..81b7fc54638 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs @@ -63,10 +63,6 @@ Endpoint = new Uri(toolboxMcpServerUrl), Name = toolboxName, TransportMode = HttpTransportMode.StreamableHttp, - AdditionalHeaders = new Dictionary - { - ["Foundry-Features"] = "Toolboxes=V1Preview", - }, }, httpClient)); diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/.env.example new file mode 100644 index 00000000000..45ba6ac976f --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/.env.example @@ -0,0 +1,2 @@ +FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +AZURE_AI_AGENT_NAME= diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/FoundryHostedSession.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/FoundryHostedSession.csproj new file mode 100644 index 00000000000..0c68e461ed4 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/FoundryHostedSession.csproj @@ -0,0 +1,24 @@ + + + + Exe + net10.0 + enable + enable + false + FoundryHostedSessionClient + foundry-hosted-session-client + $(NoWarn);MEAI001;NU1605;OPENAI001 + + + + + + + + + + + + + diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs new file mode 100644 index 00000000000..141ad64f9d4 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable MEAI001 // Foundry hosted session helpers are experimental. + +using Azure.AI.Projects; +using Azure.AI.Projects.Agents; +using Azure.Identity; +using DotNetEnv; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Foundry; + +Env.TraversePath().Load(); + +Uri projectEndpoint = new(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") + ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.")); +string agentName = Environment.GetEnvironmentVariable("AZURE_AI_AGENT_NAME") + ?? throw new InvalidOperationException("AZURE_AI_AGENT_NAME is not set."); +Uri agentEndpoint = new($"{projectEndpoint.ToString().TrimEnd('/')}/agents/{agentName}/endpoint/protocols/openai"); + +var credential = new AzureCliCredential(); +var projectClient = new AIProjectClient(projectEndpoint, credential); +FoundryAgent agent = projectClient.AsAIAgent(agentEndpoint); +AgentAdministrationClient adminClient = projectClient.AgentAdministrationClient; + +Console.WriteLine("Creating a Foundry hosted session..."); +ProjectsAgentRecord agentRecord = await adminClient.GetAgentAsync(agentName); +string agentVersion = agentRecord.GetLatestVersion().Version; +ProjectAgentSession hostedSession = await adminClient.CreateSessionAsync( + agentName, + new VersionRefIndicator(agentVersion)); +string hostedSessionId = hostedSession.AgentSessionId; + +try +{ + await WaitForSessionActiveAsync(adminClient, agentName, hostedSessionId); + + ChatClientAgentSession session = await agent.CreateFoundryHostedAgentSessionAsync( + hostedSessionId: hostedSessionId); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($""" + ============================================================ + Foundry Hosted Session Sample + Agent: {agentName} + Hosted session: {session.FoundryHostedAgentSessionId} + + Every turn uses the same Foundry sandbox. Conversation history + remains a separate concern and is carried by this AgentSession. + Type a message or 'quit' to exit. + ============================================================ + """); + Console.ResetColor(); + + while (true) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("You> "); + Console.ResetColor(); + + string? input = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(input)) + { + continue; + } + + if (input.Equals("quit", StringComparison.OrdinalIgnoreCase)) + { + break; + } + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("Agent> "); + Console.ResetColor(); + + await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, session)) + { + Console.Write(update); + } + + Console.WriteLine(); + Console.WriteLine(); + } +} +finally +{ + Console.WriteLine($"Deleting hosted session {hostedSessionId}..."); + await adminClient.DeleteSessionAsync(agentName, hostedSessionId); +} + +static async Task WaitForSessionActiveAsync( + AgentAdministrationClient adminClient, + string agentName, + string sessionId, + CancellationToken cancellationToken = default) +{ + TimeSpan timeout = TimeSpan.FromMinutes(3); + DateTimeOffset deadline = DateTimeOffset.UtcNow + timeout; + ProjectAgentSession session = await adminClient.GetSessionAsync(agentName, sessionId, cancellationToken); + + while (session.Status != AgentSessionStatus.Active) + { + if (session.Status == AgentSessionStatus.Failed + || session.Status == AgentSessionStatus.Deleted + || session.Status == AgentSessionStatus.Expired) + { + throw new InvalidOperationException( + $"Hosted session '{sessionId}' entered terminal status '{session.Status}'."); + } + + if (DateTimeOffset.UtcNow >= deadline) + { + throw new TimeoutException( + $"Hosted session '{sessionId}' did not become active within {timeout.TotalSeconds:F0} seconds. Last status: {session.Status}."); + } + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + session = await adminClient.GetSessionAsync(agentName, sessionId, cancellationToken); + } +} diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/README.md new file mode 100644 index 00000000000..86a9a417fe6 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/README.md @@ -0,0 +1,86 @@ +# Foundry Hosted Session + +This client creates a Foundry hosted session explicitly, attaches its identifier to an Agent Framework +`AgentSession`, and sends every turn to the same hosted sandbox. It deletes the hosted session when the +client exits. + +Use this pattern when the application must know the hosted session identifier before the first agent +invocation. Common reasons include uploading files before the first turn and assigning several logical +users to a caller managed session pool. + +## Sessions and conversations + +A hosted session and a conversation solve different problems. + +| Value | Purpose | +| --- | --- | +| Foundry `agent_session_id` | Selects the hosted sandbox, its compute, persisted `$HOME`, and session files. | +| Agent Framework `AgentSession` | Keeps the hosted session identifier and conversation continuation state together for the client. | +| Conversation or previous response identifier | Preserves model conversation history. Reusing only `agent_session_id` does not restore message history. | + +The sample creates the platform session with `AgentAdministrationClient.CreateSessionAsync`. It then +calls `CreateFoundryHostedAgentSessionAsync(hostedSessionId: ...)`. Agent Framework stores the identifier +on the returned `AgentSession` and sends `agent_session_id` on every subsequent Responses request. + +## Session lifetime + +The platform owns the hosted session lifecycle. When the idle timeout is reached, Foundry can remove +the active compute while preserving the session filesystem. Referencing the session again causes Foundry +to provision compute and restore the saved state. A hosted session identifies a logical sandbox. It does +not reserve one physical virtual machine for its complete lifetime. + +This sample deletes the session in a `finally` block. A production service can retain session identifiers +when it needs a longer lived pool, but it must eventually delete unused sessions or let the configured +expiration reclaim them. + +## Session pools + +Applications that serve many users usually should not create one hosted session for every registered user. +Instead, a middle tier can maintain a bounded pool: + +1. Create a small number of hosted sessions. +2. Assign each user to one session. +3. Keep that assignment stable while the user is active. +4. Send the selected `agent_session_id` on every call. +5. Grow the pool according to observed concurrent work rather than total user count. + +Sharing a hosted session does not automatically partition files or application data written by the +container. When users share a sandbox, container owned data must be stored under a per user partition. +The sibling [`UserIsolationAgent`](../UserIsolationAgent/) sample shows how to identify each user while +sharing one hosted session. + +## Prerequisites + +* [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +* A hosted agent deployed to Foundry +* Azure CLI authenticated with `az login` +* Permission to invoke the agent and create, read, and delete its hosted sessions + +This sample targets a deployed agent because the Foundry session administration API is not available +from a local `dotnet run` agent server. + +## Configuration + +Copy `.env.example` to `.env` and set: + +```env +FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +AZURE_AI_AGENT_NAME= +``` + +## Run + +```powershell +cd dotnet\samples\04-hosting\FoundryHostedAgents\responses\Using-Samples\FoundryHostedSession +dotnet run +``` + +The startup output prints the explicit hosted session identifier. All REPL turns reuse that identifier. +Entering `quit` or encountering an exception causes the `finally` block to request session deletion. +An abrupt process termination can prevent cleanup, so production systems should also reclaim abandoned +sessions outside the request process. + +## Related documentation + +* [Manage hosted agent sessions](https://learn.microsoft.com/azure/foundry/agents/how-to/manage-hosted-sessions) +* [Multiplex multiple users in one hosted agent session](https://learn.microsoft.com/azure/foundry/agents/how-to/multiplex-session-users) diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/README.md index 78d518670bb..7de3f5e4639 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/README.md +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/README.md @@ -47,6 +47,8 @@ never hits the TLS check. | [`SimpleAgent/`](./SimpleAgent/) | Any hosted agent | Generic, agent-agnostic REPL. Point it at any `Hosted-*` server via `AZURE_AI_AGENT_NAME`. Used by `Hosted-Toolbox`, `Hosted-Toolbox-AuthPaths`, and `Hosted-McpTools`. | | [`Hosted-Toolbox-AuthPaths-Client/`](./Hosted-Toolbox-AuthPaths-Client/) | Hosted toolbox agents | Handles OAuth consent, function-tool approvals, and native MCP approvals. Use it with `Hosted-Toolbox-AuthPaths` or `Hosted-ToolboxMcpSkills`. | | [`SessionFilesClient/`](./SessionFilesClient/) | [`Hosted-Files`](../Hosted-Files/) | Same shape as `SimpleAgent`, framed around the bundled-files demo. | +| [`FoundryHostedSession/`](./FoundryHostedSession/) | Any deployed hosted agent | Creates a hosted session explicitly, pins an `AgentSession` to it, explains session pools, and deletes the session on exit. | +| [`UserIsolationAgent/`](./UserIsolationAgent/) | Any deployed hosted agent | Shares one hosted session across several application users while keeping an independent conversation and delegated identity for each user. | For a self-contained demonstration that covers both crash and shutdown recovery, see [`Using-E2E-Resilience`](../Using-E2E-Resilience/). diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/.env.example b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/.env.example new file mode 100644 index 00000000000..45ba6ac976f --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/.env.example @@ -0,0 +1,2 @@ +FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +AZURE_AI_AGENT_NAME= diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs new file mode 100644 index 00000000000..bccadd60cd5 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable MEAI001 // Foundry hosted session and user identity helpers are experimental. + +using Azure.AI.Projects; +using Azure.AI.Projects.Agents; +using Azure.Identity; +using DotNetEnv; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Foundry; +using Microsoft.Extensions.AI; + +Env.TraversePath().Load(); + +Uri projectEndpoint = new(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") + ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.")); +string agentName = Environment.GetEnvironmentVariable("AZURE_AI_AGENT_NAME") + ?? throw new InvalidOperationException("AZURE_AI_AGENT_NAME is not set."); +Uri agentEndpoint = new($"{projectEndpoint.ToString().TrimEnd('/')}/agents/{agentName}/endpoint/protocols/openai"); + +var credential = new AzureCliCredential(); +var projectClient = new AIProjectClient(projectEndpoint, credential); +FoundryAgent agent = projectClient.AsAIAgent(agentEndpoint); +AgentAdministrationClient adminClient = projectClient.AgentAdministrationClient; + +Console.WriteLine("Creating one shared Foundry hosted session..."); +ProjectsAgentRecord agentRecord = await adminClient.GetAgentAsync(agentName); +string agentVersion = agentRecord.GetLatestVersion().Version; +ProjectAgentSession hostedSession = await adminClient.CreateSessionAsync( + agentName, + new VersionRefIndicator(agentVersion)); +string hostedSessionId = hostedSession.AgentSessionId; + +var userSessions = new Dictionary(StringComparer.Ordinal); + +try +{ + await WaitForSessionActiveAsync(adminClient, agentName, hostedSessionId); + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($""" + ============================================================ + User Isolation Agent Sample + Agent: {agentName} + Shared hosted session: {hostedSessionId} + + Enter a stable application user id for each message. + Each user gets an independent AgentSession and conversation, + while every user shares the same Foundry hosted sandbox. + Enter 'quit' as the user id to exit. + ============================================================ + """); + Console.ResetColor(); + + while (true) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("User id> "); + Console.ResetColor(); + + string? userId = Console.ReadLine()?.Trim(); + if (string.IsNullOrWhiteSpace(userId)) + { + continue; + } + + if (userId.Equals("quit", StringComparison.OrdinalIgnoreCase)) + { + break; + } + + Console.Write("Message> "); + string? input = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(input)) + { + continue; + } + + if (!userSessions.TryGetValue(userId, out ChatClientAgentSession? userSession)) + { + userSession = await agent.CreateFoundryHostedAgentSessionAsync( + hostedSessionId: hostedSessionId); + userSessions.Add(userId, userSession); + Console.WriteLine($"Created an independent conversation for '{userId}'."); + } + + var runOptions = new ChatClientAgentRunOptions( + new ChatOptions().WithFoundryHostedAgentUserIdentity(userId)); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write($"Agent for {userId}> "); + Console.ResetColor(); + + await foreach (AgentResponseUpdate update in agent.RunStreamingAsync( + input, + userSession, + runOptions)) + { + Console.Write(update); + } + + Console.WriteLine(); + Console.WriteLine(); + } +} +finally +{ + Console.WriteLine($"Deleting shared hosted session {hostedSessionId}..."); + await adminClient.DeleteSessionAsync(agentName, hostedSessionId); +} + +static async Task WaitForSessionActiveAsync( + AgentAdministrationClient adminClient, + string agentName, + string sessionId, + CancellationToken cancellationToken = default) +{ + TimeSpan timeout = TimeSpan.FromMinutes(3); + DateTimeOffset deadline = DateTimeOffset.UtcNow + timeout; + ProjectAgentSession session = await adminClient.GetSessionAsync(agentName, sessionId, cancellationToken); + + while (session.Status != AgentSessionStatus.Active) + { + if (session.Status == AgentSessionStatus.Failed + || session.Status == AgentSessionStatus.Deleted + || session.Status == AgentSessionStatus.Expired) + { + throw new InvalidOperationException( + $"Hosted session '{sessionId}' entered terminal status '{session.Status}'."); + } + + if (DateTimeOffset.UtcNow >= deadline) + { + throw new TimeoutException( + $"Hosted session '{sessionId}' did not become active within {timeout.TotalSeconds:F0} seconds. Last status: {session.Status}."); + } + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + session = await adminClient.GetSessionAsync(agentName, sessionId, cancellationToken); + } +} diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/README.md new file mode 100644 index 00000000000..8788c3ce7db --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/README.md @@ -0,0 +1,122 @@ +# User Isolation Agent + +This client demonstrates delegated user identity for a middle tier that serves several application +users through one Foundry hosted session. + +The sample creates one shared hosted session. Each user receives a separate Agent Framework +`AgentSession`, and every invocation sends that user's stable identifier through +`ChatOptions.WithFoundryHostedAgentUserIdentity`. Agent Framework places the value in the +`x-ms-user-identity` request header. + +## What Foundry isolates + +The middle tier authenticates Alice and Bob before calling Foundry. It then sends a stable application +identifier for the authenticated user: + +```text +Alice AgentSession + shared agent_session_id + x-ms-user-identity: alice +Bob AgentSession + shared agent_session_id + x-ms-user-identity: bob +``` + +Foundry resolves that delegated identity and isolates platform managed conversation history. Bob cannot +continue Alice's response chain, even though both users share the same hosted sandbox. + +The sample deliberately creates one `AgentSession` per user. Reusing one `AgentSession` for different +users would also reuse its conversation continuation identifier. Foundry rejects that cross user +continuation rather than exposing the first user's history. + +## What the application must isolate + +Foundry isolates the conversation history it manages. It does not automatically partition arbitrary +files, database rows, or cache entries written by the container. If the hosted agent stores its own +user data, partition that data by both the hosted session identifier and the resolved user identifier: + +```text +(agent_session_id, user_id) +``` + +Do not trust a user identifier copied directly from an untrusted client request. The middle tier must +authenticate the user, authorize the requested action, and choose the stable identifier that it sends +to Foundry. + +## Configure the calling principal + +The identity represented by `AzureCliCredential` in this sample is acting as the trusted middle tier. +In production this is normally a managed identity or service principal. That principal needs both: + +1. Permission to invoke the agent endpoint. `Foundry Agent Consumer` is the least privilege built in role. +2. Permission to send `x-ms-user-identity`. This permission is not included in a built in role and must + be granted through a custom role. + +Create `custom-impersonation-role.json`: + +```json +{ + "Name": "Foundry Agent User Identity Impersonation", + "IsCustom": true, + "Description": "Lets a trusted middle-tier service delegate an end-user identity to a hosted agent.", + "Actions": [], + "NotActions": [], + "DataActions": [ + "Microsoft.CognitiveServices/accounts/AIServices/agents/endpoints/UserIdentityImpersonation/action" + ], + "NotDataActions": [], + "AssignableScopes": [ + "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/" + ] +} +``` + +Create and assign the role: + +```powershell +az role definition create --role-definition custom-impersonation-role.json + +az role assignment create ` + --assignee ` + --role "Foundry Agent User Identity Impersonation" ` + --scope "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/" +``` + +The role can be assigned at the Foundry project or specific agent scope when your deployment model +supports that narrower scope. A caller that sends `x-ms-user-identity` without this data action receives +HTTP 403. + +The delegated identifier is not an access token and this flow is not OAuth on behalf of token exchange. +The middle tier authenticates the user itself, then Foundry accepts the identifier only because the +calling principal has the explicit impersonation data action. + +## Prerequisites + +* [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +* A hosted agent deployed with Responses protocol version 2.0.0 +* Azure CLI authenticated with `az login` +* Session administration permission for the sample's create and delete operations +* The invocation and impersonation permissions described above + +User isolation is a platform behavior. It is not enforced by a local `dotnet run` agent server, so this +sample targets a deployed Foundry agent. + +## Configuration + +Copy `.env.example` to `.env` and set: + +```env +FOUNDRY_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/ +AZURE_AI_AGENT_NAME= +``` + +## Run + +```powershell +cd dotnet\samples\04-hosting\FoundryHostedAgents\responses\Using-Samples\UserIsolationAgent +dotnet run +``` + +Enter `alice`, send several messages, then enter `bob`. Returning to `alice` reuses Alice's own +`AgentSession`. Both users remain attached to the one hosted session printed at startup. + +## Related documentation + +* [Multiplex multiple users in one hosted agent session](https://learn.microsoft.com/azure/foundry/agents/how-to/multiplex-session-users) +* [Hosted agent permissions reference](https://learn.microsoft.com/azure/foundry/agents/concepts/hosted-agent-permissions#delegate-the-end-user-identity) diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/UserIsolationAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/UserIsolationAgent.csproj new file mode 100644 index 00000000000..aa7f03ca1c1 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/UserIsolationAgent.csproj @@ -0,0 +1,24 @@ + + + + Exe + net10.0 + enable + enable + false + UserIsolationAgentClient + user-isolation-agent-client + $(NoWarn);MEAI001;NU1605;OPENAI001 + + + + + + + + + + + + + From 5ec86885b23cfa10728d34103ea1063f3e151db8 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:54:52 +0100 Subject: [PATCH 2/4] .NET: Handle closed input in hosted clients --- .../FoundryHostedSession/Program.cs | 8 ++++++++ .../Using-Samples/UserIsolationAgent/Program.cs | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs index 141ad64f9d4..31291c53878 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/Program.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +// Creates a Foundry hosted session explicitly, pins an Agent Framework session to it, +// reuses the hosted sandbox across turns, and deletes the hosted session on exit. + #pragma warning disable MEAI001 // Foundry hosted session helpers are experimental. using Azure.AI.Projects; @@ -58,6 +61,11 @@ Type a message or 'quit' to exit. Console.ResetColor(); string? input = Console.ReadLine(); + if (input is null) + { + break; + } + if (string.IsNullOrWhiteSpace(input)) { continue; diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs index bccadd60cd5..a528648fb26 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +// Shares one Foundry hosted session across application users while preserving an +// independent Agent Framework session and delegated identity for each user. + #pragma warning disable MEAI001 // Foundry hosted session and user identity helpers are experimental. using Azure.AI.Projects; @@ -58,7 +61,13 @@ User Isolation Agent Sample Console.Write("User id> "); Console.ResetColor(); - string? userId = Console.ReadLine()?.Trim(); + string? userId = Console.ReadLine(); + if (userId is null) + { + break; + } + + userId = userId.Trim(); if (string.IsNullOrWhiteSpace(userId)) { continue; @@ -71,6 +80,11 @@ User Isolation Agent Sample Console.Write("Message> "); string? input = Console.ReadLine(); + if (input is null) + { + break; + } + if (string.IsNullOrWhiteSpace(input)) { continue; From 98e7151223371f5ad27f3fac7af633525f37638a Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:24:22 +0100 Subject: [PATCH 3/4] .NET: Clarify user isolation sample trust boundary --- .../responses/Using-Samples/UserIsolationAgent/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs index a528648fb26..b26b02f01dd 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/Program.cs @@ -34,6 +34,9 @@ new VersionRefIndicator(agentVersion)); string hostedSessionId = hostedSession.AgentSessionId; +// Demonstration only: this console accepts arbitrary user IDs to make isolation visible. +// Production code must derive the user ID from authenticated request context, authorize the +// operation, and use a managed session pool instead of trusting caller-provided identity text. var userSessions = new Dictionary(StringComparer.Ordinal); try From 56fc228bbbbf2a3a49faeb044583ae69a529c5bc Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:42:06 +0100 Subject: [PATCH 4/4] .NET: Keep preview flag on direct Toolbox MCP calls --- .../foundry/Agent_Step25_FoundryToolboxMcp/Program.cs | 4 ++++ .../foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs | 4 ++++ .../Claw_Step03_ScalingCapabilities/FoundrySkills.cs | 4 ++++ .../Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs | 4 ++++ .../InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml | 6 ++++++ .../responses/Hosted-ToolboxMcpSkills/Program.cs | 4 ++++ 6 files changed, 26 insertions(+) diff --git a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs index dce06cf7cb9..5a04a8c7048 100644 --- a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs +++ b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step25_FoundryToolboxMcp/Program.cs @@ -47,6 +47,10 @@ Endpoint = new Uri(toolboxEndpoint), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, + AdditionalHeaders = new Dictionary + { + ["Foundry-Features"] = "Toolboxes=V1Preview", + }, }, httpClient)); diff --git a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs index 8205e0565a8..d8fff025c56 100644 --- a/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs +++ b/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step26_FoundryToolboxMcpSkills/Program.cs @@ -38,6 +38,10 @@ Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, + AdditionalHeaders = new Dictionary + { + ["Foundry-Features"] = "Toolboxes=V1Preview", + }, }, httpClient)); diff --git a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs index f5ae6aa183d..aea7da4088f 100644 --- a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs +++ b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step03_ScalingCapabilities/FoundrySkills.cs @@ -38,6 +38,10 @@ internal static class FoundrySkills Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, + AdditionalHeaders = new Dictionary + { + ["Foundry-Features"] = "Toolboxes=V1Preview", + }, }, httpClient)); diff --git a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs index 07e7366fe01..b1748c10a39 100644 --- a/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs +++ b/dotnet/samples/02-agents/Harness/BuildYourOwnClaw/Claw_Step04_ProductionReady/ClawAgent/FoundrySkills.cs @@ -34,6 +34,10 @@ internal static class FoundrySkills Endpoint = new Uri(toolboxMcpServerUrl), Name = "foundry_toolbox", TransportMode = HttpTransportMode.StreamableHttp, + AdditionalHeaders = new Dictionary + { + ["Foundry-Features"] = "Toolboxes=V1Preview", + }, }, httpClient), cancellationToken: cancellationToken).ConfigureAwait(false); diff --git a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml index 8d8b8b9dc2a..b5f6f39316e 100644 --- a/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml +++ b/dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/InvokeFoundryToolboxMcp.yaml @@ -36,6 +36,8 @@ trigger: serverLabel: foundry_toolbox toolName: tools/list conversationId: =System.ConversationId + headers: + Foundry-Features: Toolboxes=V1Preview output: autoSend: true result: Local.ToolboxTools @@ -47,6 +49,8 @@ trigger: serverLabel: foundry_toolbox toolName: =Env.FOUNDRY_TOOLBOX_DOCS_SERVER_LABEL & "___microsoft_docs_search" conversationId: =System.ConversationId + headers: + Foundry-Features: Toolboxes=V1Preview arguments: query: =Local.SearchQuery output: @@ -62,6 +66,8 @@ trigger: serverLabel: foundry_toolbox toolName: =Env.FOUNDRY_TOOLBOX_WEB_SEARCH_TOOL_NAME conversationId: =System.ConversationId + headers: + Foundry-Features: Toolboxes=V1Preview arguments: search_query: =Local.SearchQuery output: diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs index 81b7fc54638..00dc5330122 100644 --- a/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs +++ b/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/Program.cs @@ -63,6 +63,10 @@ Endpoint = new Uri(toolboxMcpServerUrl), Name = toolboxName, TransportMode = HttpTransportMode.StreamableHttp, + AdditionalHeaders = new Dictionary + { + ["Foundry-Features"] = "Toolboxes=V1Preview", + }, }, httpClient));