From 9e842e18214c4d5dfa03bfdfbac2e12316f9bb8f Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Mon, 13 Apr 2026 15:11:26 +0100
Subject: [PATCH] Update chat client agent for contributor and devs
---
.gitignore | 4 +
dotnet/.gitignore | 9 +-
dotnet/agent-framework-dotnet.slnx | 4 +-
.../Hosted-ChatClientAgent/.env.local | 4 +
.../Dockerfile.contributor | 19 +++
.../HostedChatClientAgent.csproj | 9 ++
.../Hosted-ChatClientAgent/Program.cs | 49 +++++++-
.../Hosted-ChatClientAgent/README.md | 109 ++++++++++++++++++
.../agent.manifest.yaml | 28 +++++
.../Hosted-ChatClientAgent/agent.yaml | 29 ++---
.../Hosted-FoundryAgent/agent.manifest.yaml | 28 +++++
.../Hosted-FoundryAgent/agent.yaml | 29 ++---
.../SimpleAgent}/Program.cs | 0
.../SimpleAgent}/SimpleAgent.csproj | 2 +-
.../Hosting/AgentSessionStore.cs | 2 +-
.../Hosting/ServiceCollectionExtensions.cs | 10 +-
16 files changed, 284 insertions(+), 51 deletions(-)
create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/.env.local
create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Dockerfile.contributor
create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/README.md
create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.manifest.yaml
create mode 100644 dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.manifest.yaml
rename dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/{UsingHostedAgent => Using-Samples/SimpleAgent}/Program.cs (100%)
rename dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/{UsingHostedAgent => Using-Samples/SimpleAgent}/SimpleAgent.csproj (85%)
diff --git a/.gitignore b/.gitignore
index 089abb5395..28d753c87b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,6 +134,10 @@ celerybeat.pid
.venv
env/
venv/
+
+# Foundry agent CLI (contains secrets, auto-generated)
+.foundry-agent.json
+.foundry-agent-build.log
ENV/
env.bak/
venv.bak/
diff --git a/dotnet/.gitignore b/dotnet/.gitignore
index ce1409abe9..572680831e 100644
--- a/dotnet/.gitignore
+++ b/dotnet/.gitignore
@@ -402,4 +402,11 @@ FodyWeavers.xsd
*.msp
# JetBrains Rider
-*.sln.iml
\ No newline at end of file
+*.sln.iml
+
+# Foundry agent CLI config (contains secrets, auto-generated)
+.foundry-agent.json
+.foundry-agent-build.log
+
+# Pre-published output for Docker builds
+out/
\ No newline at end of file
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 969a5c16d8..5ffb5692fd 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -280,8 +280,8 @@
-
-
+
+
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/.env.local b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/.env.local
new file mode 100644
index 0000000000..6d7831229d
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/.env.local
@@ -0,0 +1,4 @@
+AZURE_AI_PROJECT_ENDPOINT=
+ASPNETCORE_URLS=http://+:8088
+ASPNETCORE_ENVIRONMENT=Development
+AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Dockerfile.contributor b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Dockerfile.contributor
new file mode 100644
index 0000000000..200f674bdd
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Dockerfile.contributor
@@ -0,0 +1,19 @@
+# Dockerfile for contributors building from the agent-framework repository source.
+#
+# This project uses ProjectReference to the local Microsoft.Agents.AI.Foundry source,
+# which means a standard multi-stage Docker build cannot resolve dependencies outside
+# this folder. Instead, pre-publish the app targeting the container runtime and copy
+# the output into the container:
+#
+# dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
+# docker build -f Dockerfile.contributor -t hosted-chat-client-agent .
+# docker run --rm -p 8088:8088 -e AGENT_NAME=hosted-chat-client-agent --env-file .env hosted-chat-client-agent
+#
+# For end-users consuming the NuGet package (not ProjectReference), use the standard
+# Dockerfile which performs a full dotnet restore + publish inside the container.
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
+WORKDIR /app
+COPY out/ .
+EXPOSE 8088
+ENV ASPNETCORE_URLS=http://+:8088
+ENTRYPOINT ["dotnet", "HostedChatClientAgent.dll"]
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/HostedChatClientAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/HostedChatClientAgent.csproj
index 096681d505..b1fe8d3e3c 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/HostedChatClientAgent.csproj
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/HostedChatClientAgent.csproj
@@ -14,8 +14,17 @@
+
+
+
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs
index 448f0f16af..b4d76bbc52 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/Program.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
+using Azure.Core;
using Azure.Identity;
using DotNetEnv;
using Microsoft.Agents.AI;
@@ -17,8 +18,14 @@ var agentName = Environment.GetEnvironmentVariable("AGENT_NAME")
var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
+// Use a chained credential: try a temporary dev token first (for local Docker debugging),
+// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
+TokenCredential credential = new ChainedTokenCredential(
+ new DevTemporaryTokenCredential(),
+ new DefaultAzureCredential());
+
// Create the agent via the AI project client using the Responses API.
-AIAgent agent = new AIProjectClient(projectEndpoint, new DefaultAzureCredential())
+AIAgent agent = new AIProjectClient(projectEndpoint, credential)
.AsAIAgent(
model: deployment,
instructions: """
@@ -44,3 +51,43 @@ if (app.Environment.IsDevelopment())
}
app.Run();
+
+///
+/// A for local Docker debugging only.
+///
+/// When debugging and testing a hosted agent in a local Docker container, Azure CLI
+/// and other interactive credentials are not available. This credential reads a
+/// pre-fetched bearer token from the AZURE_BEARER_TOKEN environment variable.
+///
+/// This should NOT be used in production — tokens expire (~1 hour) and cannot be refreshed.
+/// In production, the Foundry platform injects a managed identity automatically.
+///
+/// Generate a token on your host and pass it to the container:
+/// export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
+/// docker run -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN ...
+///
+internal sealed class DevTemporaryTokenCredential : TokenCredential
+{
+ private const string EnvironmentVariable = "AZURE_BEARER_TOKEN";
+
+ public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ return GetAccessToken();
+ }
+
+ public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ return new ValueTask(GetAccessToken());
+ }
+
+ private static AccessToken GetAccessToken()
+ {
+ var token = Environment.GetEnvironmentVariable(EnvironmentVariable);
+ if (string.IsNullOrEmpty(token))
+ {
+ throw new CredentialUnavailableException($"{EnvironmentVariable} environment variable is not set.");
+ }
+
+ return new AccessToken(token, DateTimeOffset.UtcNow.AddHours(1));
+ }
+}
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/README.md b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/README.md
new file mode 100644
index 0000000000..0c5ce36cfe
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/README.md
@@ -0,0 +1,109 @@
+# Hosted-ChatClientAgent
+
+A simple general-purpose AI assistant hosted as a Foundry Hosted Agent using the Agent Framework instance hosting pattern. The agent is created inline via `AIProjectClient.AsAIAgent(model, instructions)` and served using the Responses protocol.
+
+## Prerequisites
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+- An Azure AI Foundry project with a deployed model (e.g., `gpt-4o`)
+- Azure CLI logged in (`az login`)
+
+## Configuration
+
+Copy the template and fill in your project endpoint:
+
+```bash
+cp .env.local .env
+```
+
+Edit `.env` and set your Azure AI Foundry project endpoint:
+
+```env
+AZURE_AI_PROJECT_ENDPOINT=https://.services.ai.azure.com/api/projects/
+ASPNETCORE_URLS=http://+:8088
+ASPNETCORE_ENVIRONMENT=Development
+AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
+```
+
+> **Note:** `.env` is gitignored. The `.env.local` template is checked in as a reference.
+
+## Running directly (contributors)
+
+This project uses `ProjectReference` to build against the local Agent Framework source.
+
+```bash
+cd dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent
+dotnet run
+```
+
+The agent will start on `http://localhost:8088`.
+
+### Test it
+
+Using the Azure Developer CLI:
+
+```bash
+azd ai agent invoke --local "Hello!"
+```
+
+Or with curl (specifying the agent name explicitly):
+
+```bash
+curl -X POST http://localhost:8088/responses \
+ -H "Content-Type: application/json" \
+ -d '{"input": "Hello!", "model": "hosted-chat-client-agent"}'
+```
+
+## Running with Docker
+
+Since this project uses `ProjectReference`, the standard `Dockerfile` cannot resolve dependencies outside this folder. Use `Dockerfile.contributor` which takes a pre-published output.
+
+### 1. Publish for the container runtime (Linux Alpine)
+
+```bash
+dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
+```
+
+### 2. Build the Docker image
+
+```bash
+docker build -f Dockerfile.contributor -t hosted-chat-client-agent .
+```
+
+### 3. Run the container
+
+Generate a bearer token on your host and pass it to the container:
+
+```bash
+# Generate token (expires in ~1 hour)
+export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
+
+# Run with token
+docker run --rm -p 8088:8088 \
+ -e AGENT_NAME=hosted-chat-client-agent \
+ -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
+ --env-file .env \
+ hosted-chat-client-agent
+```
+
+> **Note:** `AGENT_NAME` is passed via `-e` to simulate the platform injection. `AZURE_BEARER_TOKEN` provides Azure credentials to the container (tokens expire after ~1 hour). The `.env` file provides the remaining configuration.
+
+### 4. Test it
+
+Using the Azure Developer CLI:
+
+```bash
+azd ai agent invoke --local "Hello!"
+```
+
+Or with curl (specifying the agent name explicitly):
+
+```bash
+curl -X POST http://localhost:8088/responses \
+ -H "Content-Type: application/json" \
+ -d '{"input": "Hello!", "model": "hosted-chat-client-agent"}'
+```
+
+## NuGet package users
+
+If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor` — it performs a full `dotnet restore` and `dotnet publish` inside the container. See the commented section in `HostedChatClientAgent.csproj` for the `PackageReference` alternative.
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.manifest.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.manifest.yaml
new file mode 100644
index 0000000000..58a07d8bb3
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.manifest.yaml
@@ -0,0 +1,28 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
+name: hosted-chat-client-agent
+displayName: "Hosted Chat Client Agent"
+
+description: >
+ A simple general-purpose AI assistant hosted as a Foundry Hosted Agent
+ using the Agent Framework instance hosting pattern.
+
+metadata:
+ tags:
+ - AI Agent Hosting
+ - Azure AI AgentServer
+ - Responses Protocol
+ - Streaming
+ - Agent Framework
+
+template:
+ name: hosted-chat-client-agent
+ kind: hosted
+ protocols:
+ - protocol: responses
+ version: 1.0.0
+ resources:
+ cpu: "0.25"
+ memory: 0.5Gi
+parameters:
+ properties: []
+resources: []
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.yaml
index dfab24e712..0a97abc35a 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-ChatClientAgent/agent.yaml
@@ -1,20 +1,9 @@
-name: simple-agent
-description: >
- A simple general-purpose AI assistant hosted as a Foundry Hosted Agent.
-metadata:
- tags:
- - AI Agent Hosting
- - Simple Agent
-template:
- name: simple-agent
- kind: hosted
- protocols:
- - protocol: responses
- version: v1
- environment_variables:
- - name: AZURE_AI_PROJECT_ENDPOINT
- value: ${AZURE_AI_PROJECT_ENDPOINT}
- - name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
- - name: AGENT_NAME
- value: ${AGENT_NAME}
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
+kind: hosted
+name: hosted-chat-client-agent
+protocols:
+ - protocol: responses
+ version: 1.0.0
+resources:
+ cpu: "0.25"
+ memory: 0.5Gi
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.manifest.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.manifest.yaml
new file mode 100644
index 0000000000..9b33646c8a
--- /dev/null
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.manifest.yaml
@@ -0,0 +1,28 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
+name: hosted-foundry-agent
+displayName: "Hosted Foundry Agent"
+
+description: >
+ A simple general-purpose AI assistant hosted as a Foundry Hosted Agent,
+ backed by a Foundry-managed agent definition.
+
+metadata:
+ tags:
+ - AI Agent Hosting
+ - Azure AI AgentServer
+ - Responses Protocol
+ - Streaming
+ - Agent Framework
+
+template:
+ name: hosted-foundry-agent
+ kind: hosted
+ protocols:
+ - protocol: responses
+ version: 1.0.0
+ resources:
+ cpu: "0.25"
+ memory: 0.5Gi
+parameters:
+ properties: []
+resources: []
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.yaml b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.yaml
index a0fe89f966..74223e72fe 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.yaml
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-FoundryAgent/agent.yaml
@@ -1,20 +1,9 @@
-name: simple-agent
-description: >
- A simple general-purpose AI assistant hosted as a Foundry Hosted Agent,
- backed by a Foundry-managed agent definition.
-metadata:
- tags:
- - AI Agent Hosting
- - Simple Agent
- - Foundry Agent
-template:
- name: simple-agent
- kind: hosted
- protocols:
- - protocol: responses
- version: v1
- environment_variables:
- - name: AZURE_AI_PROJECT_ENDPOINT
- value: ${AZURE_AI_PROJECT_ENDPOINT}
- - name: AGENT_NAME
- value: ${AGENT_NAME}
+# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
+kind: hosted
+name: hosted-foundry-agent
+protocols:
+ - protocol: responses
+ version: 1.0.0
+resources:
+ cpu: "0.25"
+ memory: 0.5Gi
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleAgent/Program.cs
similarity index 100%
rename from dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/Program.cs
rename to dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleAgent/Program.cs
diff --git a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleAgent/SimpleAgent.csproj
similarity index 85%
rename from dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj
rename to dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleAgent/SimpleAgent.csproj
index 814b5dba2d..05e150880e 100644
--- a/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/UsingHostedAgent/SimpleAgent.csproj
+++ b/dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Using-Samples/SimpleAgent/SimpleAgent.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/AgentSessionStore.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/AgentSessionStore.cs
index 6aef3269b4..c61584e9e0 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/AgentSessionStore.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/AgentSessionStore.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/ServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/ServiceCollectionExtensions.cs
index bf3b3421f6..fe3b07c023 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/ServiceCollectionExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/ServiceCollectionExtensions.cs
@@ -87,11 +87,11 @@ public static class FoundryHostingExtensions
services.TryAddKeyedSingleton(agent.Name, agent);
services.TryAddKeyedSingleton(agent.Name, agentSessionStore);
}
- else
- {
- services.TryAddSingleton(agent);
- services.TryAddSingleton(agentSessionStore);
- }
+
+ // Also register as the default (non-keyed) agent so requests
+ // without an agent name can resolve it (e.g., local dev tooling).
+ services.TryAddSingleton(agent);
+ services.TryAddSingleton(agentSessionStore);
services.TryAddSingleton();
return services;