Skip to content
Merged
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
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", "HostedInvocationsEchoAgent.dll"]
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.Abstractions 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-invocations-echo-agent .
# docker run --rm -p 8088:8088 hosted-invocations-echo-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", "HostedInvocationsEchoAgent.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Extensions.AI;

namespace Microsoft.Agents.AI;

/// <summary>
/// A minimal <see cref="AIAgent"/> that echoes the user's input text back as the response.
/// No LLM or external service is required.
/// </summary>
public sealed class EchoAIAgent : AIAgent
{
/// <inheritdoc/>
public override string Name => "echo-agent";

/// <inheritdoc/>
public override string Description => "An agent that echoes back the input message.";

/// <inheritdoc/>
protected override Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var inputText = GetInputText(messages);
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, $"Echo: {inputText}"));
return Task.FromResult(response);
}

/// <inheritdoc/>
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var inputText = GetInputText(messages);
yield return new AgentResponseUpdate
{
Role = ChatRole.Assistant,
Contents = [new TextContent($"Echo: {inputText}")],
};

await Task.CompletedTask;
}

/// <inheritdoc/>
protected override ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default)
=> new(new EchoAgentSession());

/// <inheritdoc/>
protected override ValueTask<JsonElement> SerializeSessionCoreAsync(
AgentSession session,
JsonSerializerOptions? jsonSerializerOptions = null,
CancellationToken cancellationToken = default)
=> new(JsonSerializer.SerializeToElement(new { }, jsonSerializerOptions));

/// <inheritdoc/>
protected override ValueTask<AgentSession> DeserializeSessionCoreAsync(
JsonElement serializedState,
JsonSerializerOptions? jsonSerializerOptions = null,
CancellationToken cancellationToken = default)
=> new(new EchoAgentSession());

private static string GetInputText(IEnumerable<ChatMessage> messages)
{
foreach (var message in messages)
{
if (message.Role == ChatRole.User)
{
return message.Text ?? string.Empty;
}
}

return string.Empty;
}

/// <summary>
/// Minimal session for the echo agent. No state is persisted.
/// </summary>
private sealed class EchoAgentSession : AgentSession;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All rights reserved.

using Azure.AI.AgentServer.Invocations;
using Microsoft.Agents.AI;
using Microsoft.AspNetCore.Http;

namespace HostedInvocationsEchoAgent;

/// <summary>
/// An <see cref="InvocationHandler"/> that reads the request body as plain text,
/// passes it to the <see cref="EchoAIAgent"/>, and writes the response back.
/// </summary>
public sealed class EchoInvocationHandler(EchoAIAgent agent) : InvocationHandler
{
/// <inheritdoc/>
public override async Task HandleAsync(
HttpRequest request,
HttpResponse response,
InvocationContext context,
CancellationToken cancellationToken)
{
// Read the raw text from the request body.
using var reader = new StreamReader(request.Body);
var input = await reader.ReadToEndAsync(cancellationToken);

// Run the echo agent with the input text.
var agentResponse = await agent.RunAsync(input, cancellationToken: cancellationToken);

// Write the agent response text back to the HTTP response.
response.ContentType = "text/plain";
await response.WriteAsync(agentResponse.Text, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<RootNamespace>HostedInvocationsEchoAgent</RootNamespace>
<AssemblyName>HostedInvocationsEchoAgent</AssemblyName>
<NoWarn>$(NoWarn);</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.Invocations" />
</ItemGroup>

<!-- For contributors: uses ProjectReference to build against local source -->
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
</ItemGroup>

<!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Abstractions" Version="1.0.0" />
<PackageReference Include="Azure.AI.AgentServer.Invocations" />
</ItemGroup>
-->

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.

using Azure.AI.AgentServer.Invocations;
using HostedInvocationsEchoAgent;
using Microsoft.Agents.AI;

var builder = WebApplication.CreateBuilder(args);

// Register the echo agent as a singleton (no LLM needed).
builder.Services.AddSingleton<EchoAIAgent>();

// Register the Invocations SDK services and wire the handler.
builder.Services.AddInvocationsServer();
builder.Services.AddScoped<InvocationHandler, EchoInvocationHandler>();

var app = builder.Build();

// Map the Invocations protocol endpoints:
// POST /invocations — invoke the agent
// GET /invocations/{id} — get result (not used by this sample)
// POST /invocations/{id}/cancel — cancel (not used by this sample)
app.MapInvocationsServer();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"Hosted-Invocations-EchoAgent": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8088"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Hosted-Invocations-EchoAgent

A minimal echo agent hosted as a Foundry Hosted Agent using the **Invocations protocol**. The agent reads the request body as plain text, passes it through a custom `EchoAIAgent`, and writes the echoed text back in the response. No LLM or Azure credentials are required.

## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)

## Running directly (contributors)

This project uses `ProjectReference` to build against the local Agent Framework source.

```bash
cd dotnet/samples/04-hosting/FoundryHostedAgents/HostedAgentsV2/Hosted-Invocations-EchoAgent
dotnet run
```

The agent will start on `http://localhost:8088`.

### Test it

```bash
curl -X POST http://localhost:8088/invocations \
-H "Content-Type: text/plain" \
-d "Hello, world!"
```

Expected response:

```
Echo: Hello, world!
```

## 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-invocations-echo-agent .
```

### 3. Run the container

```bash
docker run --rm -p 8088:8088 hosted-invocations-echo-agent
```

### 4. Test it

```bash
curl -X POST http://localhost:8088/invocations \
-H "Content-Type: text/plain" \
-d "Hello from Docker!"
```

## 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`. See the commented section in `Hosted-Invocations-EchoAgent.csproj` for the `PackageReference` alternative.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
name: hosted-invocations-echo-agent
displayName: "Hosted Invocations Echo Agent"

description: >
A minimal echo agent hosted as a Foundry Hosted Agent using the Invocations
protocol. Reads the request body as plain text, echoes it back in the response.
metadata:
tags:
- AI Agent Hosting
- Azure AI AgentServer
- Invocations Protocol
- Agent Framework

template:
name: hosted-invocations-echo-agent
kind: hosted
protocols:
- protocol: invocations
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
parameters:
properties: []
resources: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
kind: hosted
name: hosted-invocations-echo-agent
protocols:
- protocol: invocations
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
Loading