Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,11 @@
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Steering/HostedSteering.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/FoundryHostedSession/FoundryHostedSession.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/Hosted-Toolbox-AuthPaths-Client/Hosted-Toolbox-AuthPaths-Client.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SessionFilesClient/SessionFilesClient.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/SimpleAgent/SimpleAgent.csproj" />
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-Samples/UserIsolationAgent/UserIsolationAgent.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Using-E2E-Resilience/">
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Using-E2E-Resilience/Using-E2E-Resilience.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,10 +80,7 @@ static async Task<string> 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).
Expand Down Expand Up @@ -114,26 +110,6 @@ static async Task<string> 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<PipelinePolicy> pipeline, int currentIndex)
{
message.Request.Headers.Add(FeatureHeader, feature);
ProcessNext(message, pipeline, currentIndex);
}

public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> 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
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/// <summary>
Expand Down Expand Up @@ -148,9 +145,7 @@ Be concise.

private static async Task<string> 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
Expand Down Expand Up @@ -199,21 +194,4 @@ protected override async Task<HttpResponseMessage> 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<PipelinePolicy> pipeline, int currentIndex)
{
message.Request.Headers.Add(FeatureHeader, feature);
ProcessNext(message, pipeline, currentIndex);
}

public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
message.Request.Headers.Add(FeatureHeader, feature);
return ProcessNextAsync(message, pipeline, currentIndex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<PipelinePolicy> pipeline, int currentIndex)
{
message.Request.Headers.Add(FeatureHeader, feature);
ProcessNext(message, pipeline, currentIndex);
}

public override ValueTask ProcessAsync(
PipelineMessage message,
IReadOnlyList<PipelinePolicy> pipeline,
int currentIndex)
{
message.Request.Headers.Add(FeatureHeader, feature);
return ProcessNextAsync(message, pipeline, currentIndex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
AZURE_AI_AGENT_NAME=<deployed-hosted-agent-name>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<RootNamespace>FoundryHostedSessionClient</RootNamespace>
<AssemblyName>foundry-hosted-session-client</AssemblyName>
<NoWarn>$(NoWarn);MEAI001;NU1605;OPENAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="DotNetEnv" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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;
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 (input is null)
{
break;
}

if (string.IsNullOrWhiteSpace(input))
Comment thread
rogerbarreto marked this conversation as resolved.
{
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);
}
}
Loading
Loading