Skip to content

Commit be1e9ba

Browse files
committed
Fix structural replay for current main
- Point declarative workflow sample entry to InvokeHttpRequest\n- Add Hosted_Shared_Contributor_Setup under foundry-hosted-agents responses for restored project references\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b9c4a7b commit be1e9ba

6 files changed

Lines changed: 231 additions & 1 deletion

File tree

dotnet/agent-framework-dotnet.slnx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
<Project Path="samples/03-workflows/declarative/ExecuteCode/ExecuteCode.csproj" />
219219
<Project Path="samples/03-workflows/declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
220220
<Project Path="samples/03-workflows/declarative/FunctionTools/FunctionTools.csproj" />
221-
<Project Path="samples/03-workflows/declarative/GenerateCode/GenerateCode.csproj" />
221+
<Project Path="samples/03-workflows/declarative/InvokeHttpRequest/InvokeHttpRequest.csproj" />
222222
<Project Path="samples/03-workflows/declarative/HostedWorkflow/HostedWorkflow.csproj" />
223223
<Project Path="samples/03-workflows/declarative/InputArguments/InputArguments.csproj" />
224224
<Project Path="samples/03-workflows/declarative/InvokeFunctionTool/InvokeFunctionTool.csproj" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Azure.AI.AgentServer.Responses;
4+
using Azure.AI.AgentServer.Responses.Models;
5+
using Microsoft.Agents.AI.Foundry.Hosting;
6+
7+
namespace Hosted_Shared_Contributor_Setup;
8+
9+
/// <summary>
10+
/// A <see cref="HostedSessionIsolationKeyProvider"/> for local Docker debugging only.
11+
///
12+
/// When the Foundry platform's <c>x-agent-user-isolation-key</c> and
13+
/// <c>x-agent-chat-isolation-key</c> headers are absent (i.e., when the container is running
14+
/// outside the Foundry platform), the hosting layer rejects every request with a 500 because the
15+
/// default <see cref="HostedSessionIsolationKeyProvider"/> returns null. This provider supplies
16+
/// fallback values from the <c>HOSTED_USER_ISOLATION_KEY</c> and <c>HOSTED_CHAT_ISOLATION_KEY</c>
17+
/// environment variables, defaulting to the constants below when neither is set.
18+
///
19+
/// This should NOT be used in production. The Foundry platform sets the isolation keys for every
20+
/// inbound request and forging them client-side defeats the per-user partitioning. The dev
21+
/// fallback exists solely so a contributor can <c>docker run</c> the sample on their laptop and
22+
/// drive a few requests end to end.
23+
/// </summary>
24+
public sealed class DevTemporaryLocalSessionIsolationKeyProvider : HostedSessionIsolationKeyProvider
25+
{
26+
/// <summary>
27+
/// Environment variable that supplies the user isolation key when the platform header is absent.
28+
/// </summary>
29+
public const string UserIsolationKeyEnvironmentVariable = "HOSTED_USER_ISOLATION_KEY";
30+
31+
/// <summary>
32+
/// Environment variable that supplies the chat isolation key when the platform header is absent.
33+
/// </summary>
34+
public const string ChatIsolationKeyEnvironmentVariable = "HOSTED_CHAT_ISOLATION_KEY";
35+
36+
/// <summary>
37+
/// Default user isolation key used when neither the platform header nor the environment variable
38+
/// supplies a value. All local requests collapse onto this single bucket unless overridden.
39+
/// </summary>
40+
public const string DefaultLocalUserIsolationKey = "local-dev-user";
41+
42+
/// <summary>
43+
/// Default chat isolation key used when neither the platform header nor the environment variable
44+
/// supplies a value.
45+
/// </summary>
46+
public const string DefaultLocalChatIsolationKey = "local-dev-chat";
47+
48+
/// <inheritdoc />
49+
public override ValueTask<HostedSessionContext?> GetKeysAsync(
50+
ResponseContext context,
51+
CreateResponse request,
52+
CancellationToken cancellationToken)
53+
{
54+
var userKey = !string.IsNullOrWhiteSpace(context?.Isolation?.UserIsolationKey)
55+
? context!.Isolation!.UserIsolationKey
56+
: Environment.GetEnvironmentVariable(UserIsolationKeyEnvironmentVariable);
57+
if (string.IsNullOrWhiteSpace(userKey))
58+
{
59+
userKey = DefaultLocalUserIsolationKey;
60+
}
61+
62+
var chatKey = !string.IsNullOrWhiteSpace(context?.Isolation?.ChatIsolationKey)
63+
? context!.Isolation!.ChatIsolationKey
64+
: Environment.GetEnvironmentVariable(ChatIsolationKeyEnvironmentVariable);
65+
if (string.IsNullOrWhiteSpace(chatKey))
66+
{
67+
chatKey = DefaultLocalChatIsolationKey;
68+
}
69+
70+
return new ValueTask<HostedSessionContext?>(new HostedSessionContext(userKey!, chatKey!));
71+
}
72+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Azure.Core;
4+
using Azure.Identity;
5+
6+
namespace Hosted_Shared_Contributor_Setup;
7+
8+
/// <summary>
9+
/// A <see cref="TokenCredential"/> for local Docker debugging only.
10+
///
11+
/// When debugging and testing a hosted agent in a local Docker container, Azure CLI
12+
/// and other interactive credentials are not available. This credential reads a
13+
/// pre-fetched bearer token from the <c>AZURE_BEARER_TOKEN</c> environment variable.
14+
///
15+
/// This should NOT be used in production. Tokens expire (around one hour) and cannot be refreshed.
16+
/// In production, the Foundry platform injects a managed identity automatically.
17+
///
18+
/// Generate a token on your host and pass it to the container:
19+
/// export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
20+
/// docker run -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN ...
21+
/// </summary>
22+
public sealed class DevTemporaryTokenCredential : TokenCredential
23+
{
24+
private const string EnvironmentVariable = "AZURE_BEARER_TOKEN";
25+
private readonly string? _token;
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="DevTemporaryTokenCredential"/> class.
29+
/// Reads the bearer token from the <c>AZURE_BEARER_TOKEN</c> environment variable when present.
30+
/// </summary>
31+
public DevTemporaryTokenCredential()
32+
{
33+
this._token = Environment.GetEnvironmentVariable(EnvironmentVariable);
34+
}
35+
36+
/// <inheritdoc />
37+
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
38+
{
39+
return this.GetAccessToken();
40+
}
41+
42+
/// <inheritdoc />
43+
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
44+
{
45+
return new ValueTask<AccessToken>(this.GetAccessToken());
46+
}
47+
48+
private AccessToken GetAccessToken()
49+
{
50+
if (string.IsNullOrEmpty(this._token) || this._token == "DefaultAzureCredential")
51+
{
52+
throw new CredentialUnavailableException($"{EnvironmentVariable} environment variable is not set.");
53+
}
54+
55+
return new AccessToken(this._token, DateTimeOffset.UtcNow.AddHours(1));
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.Agents.AI.Foundry.Hosting;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.Extensions.Hosting;
6+
7+
namespace Hosted_Shared_Contributor_Setup;
8+
9+
/// <summary>
10+
/// Routing helpers for contributor samples that host a Foundry-managed agent locally.
11+
/// </summary>
12+
public static class HostedContributorRouteExtensions
13+
{
14+
/// <summary>
15+
/// In Development, maps the per-agent OpenAI route shape that live Foundry uses
16+
/// (<c>/api/projects/{project}/agents/{agentName}/endpoint/protocols/openai/responses</c>) on top
17+
/// of the default <c>MapFoundryResponses()</c> so a local REPL client can reach the agent through
18+
/// <c>AIProjectClient.AsAIAgent(Uri agentEndpoint)</c>, which is the only supported consumption path
19+
/// for Foundry-hosted agents.
20+
///
21+
/// <para>
22+
/// The <c>{project}</c> and <c>{agentName}</c> segments are route-parameter wildcards on the server
23+
/// side; the handler does not consume them, so any value sent by the client is accepted.
24+
/// </para>
25+
///
26+
/// <para><b>For local contributor debugging only and should not be used in production.</b></para>
27+
/// </summary>
28+
/// <param name="app">The <see cref="WebApplication"/> to attach the routes to.</param>
29+
/// <returns>The same <see cref="WebApplication"/> for chaining.</returns>
30+
public static WebApplication MapDevTemporaryLocalAgentEndpoint(this WebApplication app)
31+
{
32+
ArgumentNullException.ThrowIfNull(app);
33+
34+
if (app.Environment.IsDevelopment())
35+
{
36+
app.MapFoundryResponses("api/projects/{project}/agents/{agentName}/endpoint/protocols/openai");
37+
}
38+
39+
return app;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.Agents.AI.Foundry.Hosting;
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Hosted_Shared_Contributor_Setup;
7+
8+
/// <summary>
9+
/// Registration helpers for the developer-only utilities shipped in this sample-shared project.
10+
/// </summary>
11+
public static class HostedContributorSetupExtensions
12+
{
13+
/// <summary>
14+
/// Registers developer-only services that allow a hosted Foundry agent to run outside the
15+
/// Foundry platform (e.g., inside a Docker container during contributor debugging).
16+
///
17+
/// <para><b>For local Docker debugging only and should not be used in production.</b></para>
18+
///
19+
/// Currently this method registers a <see cref="DevTemporaryLocalSessionIsolationKeyProvider"/>
20+
/// so that requests succeed when the platform's <c>x-agent-user-isolation-key</c> and
21+
/// <c>x-agent-chat-isolation-key</c> headers are absent. In production those headers are
22+
/// always present and the default platform isolation key provider (registered automatically by
23+
/// the hosting layer) is used instead.
24+
/// </summary>
25+
/// <param name="services">The service collection to register the developer-only services into.</param>
26+
/// <returns>The same <see cref="IServiceCollection"/> for chaining.</returns>
27+
public static IServiceCollection AddDevTemporaryLocalContributorSetup(this IServiceCollection services)
28+
{
29+
ArgumentNullException.ThrowIfNull(services);
30+
services.AddSingleton<HostedSessionIsolationKeyProvider, DevTemporaryLocalSessionIsolationKeyProvider>();
31+
return services;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net10.0</TargetFrameworks>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
8+
<RootNamespace>Hosted_Shared_Contributor_Setup</RootNamespace>
9+
<AssemblyName>Hosted_Shared_Contributor_Setup</AssemblyName>
10+
<NoWarn>$(NoWarn);</NoWarn>
11+
<IsPackable>false</IsPackable>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Azure.Identity" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
24+
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" />
25+
</ItemGroup>
26+
27+
</Project>

0 commit comments

Comments
 (0)