-
Notifications
You must be signed in to change notification settings - Fork 1.7k
.NET: Hosted-Files sample + AgentSessionFiles SDK companion + integration test #5698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rogerbarreto
wants to merge
10
commits into
microsoft:main
Choose a base branch
from
rogerbarreto:issues/5691-net-hosted-files
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,154
−1
Open
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
64a27fa
.NET: Add Hosted-Files sample + alpha AgentSessionFiles SDK companion…
rogerbarreto c86c2dc
.NET: Hosted-Files: REPL companion now demonstrates file-as-knowledge…
rogerbarreto e3a9b94
.NET: Reshape Hosted-Files sample - bake files into image, SessionFil…
rogerbarreto f439d31
.NET: Foundry.Hosting.IntegrationTests TestContainer - constrain sess…
rogerbarreto 244d8bd
.NET: SessionFilesHostedAgentTests - shrink to alpha SDK round-trip
rogerbarreto 9461d54
.NET: SessionFilesHostedAgentTests - rewrite as upload-then-FoundryAg…
rogerbarreto d54b232
.NET: SessionFilesHostedAgentTests - end-to-end upload-then-FoundryAg…
rogerbarreto a74d4cb
.NET: address Copilot PR review findings
rogerbarreto e4cd857
.NET: Hosted-Files: split into bundled vs session-file tool pairs
rogerbarreto 2114ed6
.NET: Hosted-Files README - fix broken relative link to IT (4..5 dots)
rogerbarreto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.dockerignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| **/bin | ||
| **/obj | ||
| **/.vs | ||
| **/.vscode | ||
| .env | ||
| *.user |
5 changes: 5 additions & 0 deletions
5
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/.env.example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| AZURE_AI_PROJECT_ENDPOINT=<your-azure-ai-project-endpoint> | ||
| ASPNETCORE_URLS=http://+:8088 | ||
| ASPNETCORE_ENVIRONMENT=Development | ||
| AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o | ||
| AZURE_BEARER_TOKEN=DefaultAzureCredential |
17 changes: 17 additions & 0 deletions
17
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Dockerfile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Use the official .NET 10.0 ASP.NET runtime as a parent image | ||
| FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base | ||
| WORKDIR /app | ||
|
|
||
| FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build | ||
| WORKDIR /src | ||
| COPY . . | ||
| RUN dotnet restore | ||
| RUN dotnet publish -c Release -o /app/publish | ||
|
|
||
| # Final stage | ||
| FROM base AS final | ||
| WORKDIR /app | ||
| COPY --from=build /app/publish . | ||
| EXPOSE 8088 | ||
| ENV ASPNETCORE_URLS=http://+:8088 | ||
| ENTRYPOINT ["dotnet", "HostedFiles.dll"] |
19 changes: 19 additions & 0 deletions
19
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Dockerfile.contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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-files . | ||
| # docker run --rm -p 8088:8088 -e AGENT_NAME=hosted-files -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN --env-file .env hosted-files | ||
| # | ||
| # 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", "HostedFiles.dll"] |
32 changes: 32 additions & 0 deletions
32
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/HostedFiles.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>net10.0</TargetFrameworks> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled> | ||
| <RootNamespace>HostedFiles</RootNamespace> | ||
| <AssemblyName>HostedFiles</AssemblyName> | ||
| <NoWarn>$(NoWarn);</NoWarn> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.AI.Projects" VersionOverride="2.1.0-beta.1" /> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| <PackageReference Include="DotNetEnv" /> | ||
| </ItemGroup> | ||
|
|
||
| <!-- For contributors: uses ProjectReference to build against local source --> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" /> | ||
| <ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above | ||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.0.0" /> | ||
| <PackageReference Include="Microsoft.Agents.AI.Foundry.Hosting" Version="1.0.0" /> | ||
| </ItemGroup> | ||
| --> | ||
|
|
||
| </Project> |
166 changes: 166 additions & 0 deletions
166
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| // Hosted Files Agent - A hosted agent that reads files from the per-session | ||
| // $HOME sandbox volume using local C# function tools. | ||
| // | ||
| // In Foundry hosted-agent mode, every session is backed by an isolated | ||
| // micro-VM with a persistent $HOME directory. Files uploaded to the session | ||
| // (via the AgentSessionFiles SDK or `azd ai agent files upload`) appear under | ||
| // $HOME and can be read by tools running inside the agent process. | ||
| // | ||
| // Required environment variables: | ||
| // AZURE_AI_PROJECT_ENDPOINT - Azure AI Foundry project endpoint | ||
| // AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-4o) | ||
| // | ||
| // Optional: | ||
| // AGENT_NAME - Agent name (default: hosted-files) | ||
|
|
||
| using System.ComponentModel; | ||
| using Azure.AI.Projects; | ||
| using Azure.Core; | ||
| using Azure.Identity; | ||
| using DotNetEnv; | ||
| using Microsoft.Agents.AI; | ||
| using Microsoft.Agents.AI.Foundry.Hosting; | ||
| using Microsoft.Extensions.AI; | ||
|
|
||
| // Load .env file if present (for local development) | ||
| Env.TraversePath().Load(); | ||
|
|
||
| string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") | ||
| ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set."); | ||
| string deploymentName = 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()); | ||
|
|
||
| // ── Tools: read files from the session $HOME volume ────────────────────────── | ||
|
|
||
| // $HOME resolves to the per-session sandbox volume on Foundry. Locally it | ||
| // resolves to the OS user profile, which lets the sample run unmodified | ||
| // during development. | ||
| string Home() => | ||
| Environment.GetEnvironmentVariable("HOME") | ||
| ?? Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile); | ||
|
|
||
| [Description("Get the absolute path of the session home directory ($HOME).")] | ||
| string GetHomeDirectory() => Home(); | ||
|
|
||
| [Description("List files and directories under the given path inside the session sandbox. Pass an empty string to list $HOME.")] | ||
| string[] ListFiles( | ||
| [Description("Path relative to $HOME (or absolute). Empty string means $HOME.")] string path) | ||
| { | ||
| try | ||
| { | ||
| string target = ResolveSessionPath(path); | ||
| return Directory.EnumerateFileSystemEntries(target).ToArray(); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return [$"Error listing '{path}': {ex.Message}"]; | ||
| } | ||
| } | ||
|
|
||
| [Description("Read the full text contents of a file inside the session sandbox.")] | ||
| string ReadFile( | ||
| [Description("Path relative to $HOME (or absolute) of the file to read.")] string path) | ||
| { | ||
| try | ||
| { | ||
| string target = ResolveSessionPath(path); | ||
| return File.ReadAllText(target); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return $"Error reading '{path}': {ex.Message}"; | ||
| } | ||
| } | ||
|
|
||
| string ResolveSessionPath(string path) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(path)) | ||
| { | ||
| return Home(); | ||
| } | ||
|
|
||
| return Path.IsPathRooted(path) ? path : Path.Combine(Home(), path); | ||
| } | ||
|
rogerbarreto marked this conversation as resolved.
Outdated
|
||
|
|
||
| // ── Create and host the agent ──────────────────────────────────────────────── | ||
|
|
||
| AIAgent agent = new AIProjectClient(new Uri(endpoint), credential) | ||
| .AsAIAgent( | ||
| model: deploymentName, | ||
| instructions: """ | ||
| You are a friendly assistant that helps users inspect and summarise | ||
| files stored in the session sandbox at $HOME. | ||
|
|
||
| Always answer file-related questions by calling the available tools | ||
| (GetHomeDirectory, ListFiles, ReadFile). Do not guess file paths or | ||
| contents — read the file before answering. | ||
|
|
||
| Quote numbers and figures verbatim from the file rather than | ||
| paraphrasing them. | ||
| """, | ||
| name: Environment.GetEnvironmentVariable("AGENT_NAME") ?? "hosted-files", | ||
| description: "Hosted agent that reads files from the per-session $HOME volume", | ||
| tools: | ||
| [ | ||
| AIFunctionFactory.Create(GetHomeDirectory), | ||
| AIFunctionFactory.Create(ListFiles), | ||
| AIFunctionFactory.Create(ReadFile), | ||
| ]); | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
| builder.Services.AddFoundryResponses(agent); | ||
|
|
||
| var app = builder.Build(); | ||
| app.MapFoundryResponses(); | ||
|
|
||
| if (app.Environment.IsDevelopment()) | ||
| { | ||
| app.MapFoundryResponses("openai/v1"); | ||
| } | ||
|
|
||
| app.Run(); | ||
|
|
||
| // ── DevTemporaryTokenCredential ─────────────────────────────────────────────── | ||
|
|
||
| /// <summary> | ||
| /// A <see cref="TokenCredential"/> for local Docker debugging only. | ||
| /// Reads a pre-fetched bearer token from the <c>AZURE_BEARER_TOKEN</c> environment variable | ||
| /// once at startup. This should NOT be used in production. | ||
| /// | ||
| /// 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 ... | ||
| /// </summary> | ||
| internal sealed class DevTemporaryTokenCredential : TokenCredential | ||
| { | ||
| private const string EnvironmentVariable = "AZURE_BEARER_TOKEN"; | ||
| private readonly string? _token; | ||
|
|
||
| public DevTemporaryTokenCredential() | ||
| { | ||
| this._token = Environment.GetEnvironmentVariable(EnvironmentVariable); | ||
| } | ||
|
|
||
| public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
| => this.GetAccessToken(); | ||
|
|
||
| public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) | ||
| => new(this.GetAccessToken()); | ||
|
|
||
| private AccessToken GetAccessToken() | ||
| { | ||
| if (string.IsNullOrEmpty(this._token) || this._token == "DefaultAzureCredential") | ||
| { | ||
| throw new CredentialUnavailableException($"{EnvironmentVariable} environment variable is not set."); | ||
| } | ||
|
|
||
| return new AccessToken(this._token, DateTimeOffset.UtcNow.AddHours(1)); | ||
| } | ||
| } | ||
106 changes: 106 additions & 0 deletions
106
dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # Hosted-Files | ||
|
|
||
| A hosted agent that reads files from the **per-session `$HOME` sandbox volume**. Each Foundry hosted-agent session is backed by an isolated micro-VM with its own persistent `$HOME`. Files uploaded to a session appear there and can be read by tools running inside the agent process. | ||
|
|
||
| The agent exposes three local C# function tools: | ||
|
|
||
| | Tool | Description | | ||
| |------|-------------| | ||
| | `GetHomeDirectory` | Returns the absolute path of `$HOME` for the current session. | | ||
| | `ListFiles` | Lists files and directories under a given path inside the sandbox. | | ||
| | `ReadFile` | Reads the full text contents of a file inside the sandbox. | | ||
|
|
||
| Companion sample: [`Using-Samples/SessionFilesClient`](../Using-Samples/SessionFilesClient/) — a REPL that uploads, lists, downloads, and deletes session files using the alpha `Azure.AI.Projects.AgentSessionFiles` SDK, then chats with this agent. | ||
|
|
||
| ## 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.example .env | ||
| ``` | ||
|
|
||
| Edit `.env`: | ||
|
|
||
| ```env | ||
| AZURE_AI_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project> | ||
| ASPNETCORE_URLS=http://+:8088 | ||
| ASPNETCORE_ENVIRONMENT=Development | ||
| AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o | ||
| ``` | ||
|
|
||
| > `.env` is gitignored. The `.env.example` template is checked in as a reference. | ||
|
|
||
| ## Running directly (contributors) | ||
|
|
||
| ```bash | ||
| cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files | ||
| AGENT_NAME=hosted-files dotnet run | ||
| ``` | ||
|
|
||
| The agent starts on `http://localhost:8088`. | ||
|
|
||
| ## Sending files to the agent | ||
|
|
||
| Files must be uploaded into the session's `$HOME` before the agent can read them. The bundled sample file is [`resources/contoso_q1_2026_report.txt`](./resources/contoso_q1_2026_report.txt). | ||
|
|
||
| ### Code-first (recommended for demos) | ||
|
|
||
| Use the companion REPL [`SessionFilesClient`](../Using-Samples/SessionFilesClient/), which exercises the alpha `Azure.AI.Projects.AgentSessionFiles` SDK directly: | ||
|
|
||
| ```bash | ||
| cd ../Using-Samples/SessionFilesClient | ||
| AGENT_NAME=hosted-files AGENT_ENDPOINT=http://localhost:8088 dotnet run | ||
|
rogerbarreto marked this conversation as resolved.
Outdated
|
||
|
|
||
| > upload ../../Hosted-Files/resources/contoso_q1_2026_report.txt | ||
| > ls | ||
| > What was Contoso's Q1 2026 total revenue? Quote the figure verbatim. | ||
| ``` | ||
|
|
||
| ### CLI-first (parity with Python sample) | ||
|
|
||
| Using the Azure Developer CLI: | ||
|
|
||
| ```bash | ||
| azd ai agent invoke "Hi!" # creates a session | ||
| azd ai agent files upload -f resources/contoso_q1_2026_report.txt | ||
| azd ai agent invoke "What was Contoso's Q1 2026 total revenue? Quote the figure verbatim." | ||
| ``` | ||
|
|
||
| The `--session-id` flag selects a specific session; without it the CLI uploads to the most recently active session. Run `azd ai agent files upload -h` for the full set of options. | ||
|
|
||
| ## Running with Docker | ||
|
|
||
| This project uses `ProjectReference`, so use `Dockerfile.contributor` which takes a pre-published output: | ||
|
|
||
| ```bash | ||
| dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out | ||
| docker build -f Dockerfile.contributor -t hosted-files . | ||
|
|
||
| export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv) | ||
| docker run --rm -p 8088:8088 \ | ||
| -e AGENT_NAME=hosted-files \ | ||
| -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \ | ||
| --env-file .env \ | ||
| hosted-files | ||
| ``` | ||
|
|
||
| ## NuGet package users | ||
|
|
||
| If consuming the Agent Framework as a NuGet package, use the standard `Dockerfile` instead of `Dockerfile.contributor` and switch the `ProjectReference` entries in `HostedFiles.csproj` to `PackageReference` (commented section in the csproj). | ||
|
|
||
| ## How session files work | ||
|
|
||
| | Layer | Lifetime | Notes | | ||
| |-------|----------|-------| | ||
| | `$HOME` | Lifetime of the session (TTL: 30 days) | Persists across invocations within a session. | | ||
| | `/tmp` | Container process | Use for non-persistent scratch space. | | ||
| | Conversation | Indefinite | Stored separately from `$HOME`. | | ||
|
|
||
| Each Foundry hosted-agent session = one container = one `$HOME`. Files uploaded with `AgentSessionFiles.UploadSessionFileAsync(agentName, sessionId, sessionStoragePath, localPath)` land at `$HOME/<sessionStoragePath>`. The agent's tools resolve relative paths against `$HOME`. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.