Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
70 changes: 68 additions & 2 deletions samples/AgentServer/EchoAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ namespace AgentServer;

public class EchoAgent
{
public void Attach(ITaskManager taskManager)
public void Attach(IAgentCardProvider agentCardProvider, ITaskManager taskManager)
{
taskManager.OnMessageReceived = ProcessMessageAsync;
taskManager.OnAgentCardQuery = GetAgentCardAsync;
agentCardProvider.OnAgentCardQuery = GetAgentCardAsync;
agentCardProvider.OnAuthenticatedAgentCardQuery = GetAuthenticatedAgentCardAsync;
}

private Task<A2AResponse> ProcessMessageAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken)
Expand Down Expand Up @@ -57,6 +58,71 @@ private Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken can
DefaultOutputModes = ["text"],
Capabilities = capabilities,
Skills = [],
SupportsAuthenticatedExtendedCard = true, // Indicate support for authenticated extended cards
});
}

private Task<AgentCard> GetAuthenticatedAgentCardAsync(string agentUrl, AuthenticationContext? authContext, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<AgentCard>(cancellationToken);
}

var capabilities = new AgentCapabilities()
{
Streaming = true,
PushNotifications = false,
};

// Base skills available to all users
var skills = new List<AgentSkill>
{
new()
{
Id = "echo",
Name = "Echo Messages",
Description = "Echoes back any message sent to the agent"
}
};

// Add additional skills for authenticated users
if (authContext?.IsAuthenticated == true)
{
skills.Add(new AgentSkill
{
Id = "admin-echo",
Name = "Admin Echo",
Description = "Enhanced echo functionality with user information (requires authentication)",
Examples = ["Show my user info", "Echo with authentication details"]
});

// Add special admin skills for users with admin role
if (authContext.HasClaim("role", "admin"))
{
skills.Add(new AgentSkill
{
Id = "system-info",
Name = "System Information",
Description = "Provides system information and diagnostics (admin only)",
Examples = ["Show system status", "Get server information"]
});
}
}

return Task.FromResult(new AgentCard()
{
Name = "Echo Agent (Extended)",
Description = authContext?.IsAuthenticated == true
? $"Enhanced echo agent with extended capabilities for authenticated user: {authContext.UserName}"
: "Agent which will echo every message it receives.",
Url = agentUrl,
Version = "1.0.0",
DefaultInputModes = ["text"],
DefaultOutputModes = ["text"],
Capabilities = capabilities,
Skills = skills,
SupportsAuthenticatedExtendedCard = true,
});
}
}
4 changes: 2 additions & 2 deletions samples/AgentServer/EchoAgentWithTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ public class EchoAgentWithTasks
{
private ITaskManager? _taskManager;

public void Attach(ITaskManager taskManager)
public void Attach(ITaskManager taskManager, IAgentCardProvider agentCardProvider)
{
_taskManager = taskManager;
taskManager.OnTaskCreated = ProcessMessageAsync;
taskManager.OnTaskUpdated = ProcessMessageAsync;
taskManager.OnAgentCardQuery = GetAgentCardAsync;
agentCardProvider.OnAgentCardQuery = GetAgentCardAsync;
}

private async Task ProcessMessageAsync(AgentTask task, CancellationToken cancellationToken)
Expand Down
27 changes: 13 additions & 14 deletions samples/AgentServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,36 @@

// Create and register the specified agent
var taskManager = new TaskManager();
var agentCardProvider = new AgentCardProvider();

switch (agentType.ToLowerInvariant())
{
case "echo":
var echoAgent = new EchoAgent();
echoAgent.Attach(taskManager);
app.MapA2A(taskManager, "/echo");
app.MapWellKnownAgentCard(taskManager, "/echo");
app.MapHttpA2A(taskManager, "/echo");
echoAgent.Attach(agentCardProvider, taskManager);
app.MapA2A(taskManager, agentCardProvider, "/echo");
app.MapHttpA2A(taskManager, agentCardProvider, "/echo");
break;

case "echotasks":
var echoAgentWithTasks = new EchoAgentWithTasks();
echoAgentWithTasks.Attach(taskManager);
app.MapA2A(taskManager, "/echotasks");
app.MapWellKnownAgentCard(taskManager, "/echotasks");
app.MapHttpA2A(taskManager, "/echotasks");
echoAgentWithTasks.Attach(taskManager, agentCardProvider);
app.MapA2A(taskManager, agentCardProvider, "/echotasks");
app.MapHttpA2A(taskManager, agentCardProvider, "/echotasks");
break;

case "researcher":
var researcherAgent = new ResearcherAgent();
researcherAgent.Attach(taskManager);
app.MapA2A(taskManager, "/researcher");
app.MapWellKnownAgentCard(taskManager, "/researcher");
researcherAgent.Attach(taskManager, agentCardProvider);
app.MapA2A(taskManager, agentCardProvider, "/researcher");
app.MapHttpA2A(taskManager, agentCardProvider, "/researcher");
break;

case "speccompliance":
var specComplianceAgent = new SpecComplianceAgent();
specComplianceAgent.Attach(taskManager);
app.MapA2A(taskManager, "/speccompliance");
app.MapWellKnownAgentCard(taskManager, "/speccompliance");
specComplianceAgent.Attach(taskManager, agentCardProvider);
app.MapA2A(taskManager, agentCardProvider, "/speccompliance");
app.MapHttpA2A(taskManager, agentCardProvider, "/speccompliance");
break;

default:
Expand Down
4 changes: 2 additions & 2 deletions samples/AgentServer/ResearcherAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private enum AgentState
Researching
}

public void Attach(ITaskManager taskManager)
public void Attach(ITaskManager taskManager, IAgentCardProvider agentCardProvider)
{
_taskManager = taskManager;
_taskManager.OnTaskCreated = async (task, cancellationToken) =>
Expand All @@ -32,7 +32,7 @@ public void Attach(ITaskManager taskManager)
var message = ((TextPart?)task.History?.Last()?.Parts?.FirstOrDefault())?.Text ?? string.Empty;
await InvokeAsync(task.Id, message, cancellationToken);
};
_taskManager.OnAgentCardQuery = GetAgentCardAsync;
agentCardProvider.OnAgentCardQuery = GetAgentCardAsync;
}

// This is the main entry point for the agent. It is called when a task is created or updated.
Expand Down
4 changes: 2 additions & 2 deletions samples/AgentServer/SpecComplianceAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public class SpecComplianceAgent
{
private ITaskManager? _taskManager;

public void Attach(ITaskManager taskManager)
public void Attach(ITaskManager taskManager, IAgentCardProvider agentCardProvider)
{
taskManager.OnAgentCardQuery = GetAgentCard;
agentCardProvider.OnAgentCardQuery = GetAgentCard;
taskManager.OnTaskCreated = OnTaskCreatedAsync;
taskManager.OnTaskUpdated = OnTaskUpdatedAsync;
_taskManager = taskManager;
Expand Down
7 changes: 4 additions & 3 deletions samples/SemanticKernelAgent/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@

var agent = new SemanticKernelTravelAgent(configuration, httpClient, logger);
var taskManager = new TaskManager();
agent.Attach(taskManager);
app.MapA2A(taskManager, string.Empty);
app.MapWellKnownAgentCard(taskManager, string.Empty);
var agentCardProvider = new AgentCardProvider();
agent.Attach(agentCardProvider, taskManager);
app.MapA2A(taskManager, agentCardProvider, string.Empty);
app.MapHttpA2A(taskManager, agentCardProvider, string.Empty);

await app.RunAsync();
4 changes: 2 additions & 2 deletions samples/SemanticKernelAgent/SemanticKernelTravelAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ public void Dispose()
GC.SuppressFinalize(this);
}

public void Attach(ITaskManager taskManager)
public void Attach(IAgentCardProvider agentCardProvider, ITaskManager taskManager)
{
_taskManager = taskManager;
taskManager.OnTaskCreated = ExecuteAgentTaskAsync;
taskManager.OnTaskUpdated = ExecuteAgentTaskAsync;
taskManager.OnAgentCardQuery = GetAgentCardAsync;
agentCardProvider.OnAgentCardQuery = GetAgentCardAsync;
}

public async Task ExecuteAgentTaskAsync(AgentTask task, CancellationToken cancellationToken)
Expand Down
26 changes: 17 additions & 9 deletions src/A2A.AspNetCore/A2AEndpointRouteBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,22 @@ public static class A2ARouteBuilderExtensions
/// </summary>
/// <param name="endpoints">The endpoint route builder to configure.</param>
/// <param name="taskManager">The task manager for handling A2A operations.</param>
/// <param name="agentCardProvider">The agent card provider for handling agent card queries.</param>
/// <param name="path">The base path for the A2A endpoints.</param>
/// <returns>An endpoint convention builder for further configuration.</returns>
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, ITaskManager taskManager, [StringSyntax("Route")] string path)
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, ITaskManager taskManager, IAgentCardProvider agentCardProvider, [StringSyntax("Route")] string path)
{
ArgumentNullException.ThrowIfNull(endpoints);
ArgumentNullException.ThrowIfNull(taskManager);
ArgumentNullException.ThrowIfNull(agentCardProvider);
ArgumentException.ThrowIfNullOrEmpty(path);

var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<IEndpointRouteBuilder>();

var routeGroup = endpoints.MapGroup("");

routeGroup.MapPost(path, (HttpRequest request, CancellationToken cancellationToken) => A2AJsonRpcProcessor.ProcessRequestAsync(taskManager, request, cancellationToken));
routeGroup.MapPost(path, (HttpRequest request, CancellationToken cancellationToken) => A2AJsonRpcProcessor.ProcessRequestAsync(taskManager, agentCardProvider, request, cancellationToken));

return routeGroup;
}
Expand All @@ -46,21 +48,21 @@ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpo
/// Enables the well-known agent card endpoint for agent discovery.
/// </summary>
/// <param name="endpoints">The endpoint route builder to configure.</param>
/// <param name="taskManager">The task manager for handling A2A operations.</param>
/// <param name="agentCardProvider">The agent card provider for handling agent card queries.</param>
/// <param name="agentPath">The base path where the A2A agent is hosted.</param>
/// <returns>An endpoint convention builder for further configuration.</returns>
public static IEndpointConventionBuilder MapWellKnownAgentCard(this IEndpointRouteBuilder endpoints, ITaskManager taskManager, [StringSyntax("Route")] string agentPath)
public static IEndpointConventionBuilder MapWellKnownAgentCard(this IEndpointRouteBuilder endpoints, IAgentCardProvider agentCardProvider, [StringSyntax("Route")] string agentPath)
{
ArgumentNullException.ThrowIfNull(endpoints);
ArgumentNullException.ThrowIfNull(taskManager);
ArgumentNullException.ThrowIfNull(agentCardProvider);
ArgumentException.ThrowIfNullOrEmpty(agentPath);

var routeGroup = endpoints.MapGroup("");

routeGroup.MapGet(".well-known/agent-card.json", async (HttpRequest request, CancellationToken cancellationToken) =>
{
var agentUrl = $"{request.Scheme}://{request.Host}{agentPath}";
var agentCard = await taskManager.OnAgentCardQuery(agentUrl, cancellationToken);
var agentCard = await agentCardProvider.OnAgentCardQuery(agentUrl, cancellationToken);
return Results.Ok(agentCard);
});

Expand All @@ -72,22 +74,28 @@ public static IEndpointConventionBuilder MapWellKnownAgentCard(this IEndpointRou
/// </summary>
/// <param name="endpoints">The endpoint route builder to configure.</param>
/// <param name="taskManager">The task manager for handling A2A operations.</param>
/// <param name="agentCardProvider">The agent card provider for handling agent card queries.</param>
/// <param name="path">The base path for the HTTP A2A endpoints.</param>
/// <returns>An endpoint convention builder for further configuration.</returns>
public static IEndpointConventionBuilder MapHttpA2A(this IEndpointRouteBuilder endpoints, ITaskManager taskManager, [StringSyntax("Route")] string path)
public static IEndpointConventionBuilder MapHttpA2A(this IEndpointRouteBuilder endpoints, ITaskManager taskManager, IAgentCardProvider agentCardProvider, [StringSyntax("Route")] string path)
{
ArgumentNullException.ThrowIfNull(endpoints);
ArgumentNullException.ThrowIfNull(taskManager);
ArgumentNullException.ThrowIfNull(agentCardProvider);
ArgumentException.ThrowIfNullOrEmpty(path);

var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<IEndpointRouteBuilder>();

var routeGroup = endpoints.MapGroup(path);

// /v1/card endpoint - Agent discovery
// /v1/card endpoint - Agent discovery (supports both authenticated and unauthenticated requests)
routeGroup.MapGet("/v1/card", async (HttpRequest request, CancellationToken cancellationToken) =>
await A2AHttpProcessor.GetAgentCardAsync(taskManager, logger, $"{request.Scheme}://{request.Host}{path}", cancellationToken).ConfigureAwait(false));
{
var agentUrl = $"{request.Scheme}://{request.Host}{path}";
var authContext = A2AHttpProcessor.ExtractAuthenticationContext(request);
return await A2AHttpProcessor.GetAuthenticatedAgentCardAsync(agentCardProvider, logger, agentUrl, authContext, cancellationToken).ConfigureAwait(false);
});

// /v1/tasks/{id} endpoint
routeGroup.MapGet("/v1/tasks/{id}", (string id, [FromQuery] int? historyLength, [FromQuery] string? metadata, CancellationToken cancellationToken) =>
Expand Down
Loading