// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using A2A; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; using Microsoft.Agents.AI.Hosting.A2A; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Extensions.DependencyInjection; /// /// Provides extension methods for registering A2A server instances in the dependency injection container. /// [Experimental(DiagnosticIds.Experiments.AIResponseContinuations)] public static class A2AServerServiceCollectionExtensions { /// /// Registers an in the dependency injection container, keyed by the agent name /// specified in the . This method only registers the server; to expose it /// as an HTTP endpoint, call one of the MapA2AHttpJson or MapA2AJsonRpc endpoint mapping /// methods during application startup. /// /// The agent builder whose name identifies the agent. /// An optional callback to configure . /// The for chaining. public static IHostedAgentBuilder AddA2AServer(this IHostedAgentBuilder agentBuilder, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(agentBuilder); agentBuilder.ServiceCollection.AddA2AServer(agentBuilder.Name, configureOptions); return agentBuilder; } /// /// Registers an in the dependency injection container, keyed by the specified /// agent name. This method only registers the server; to expose it as an HTTP endpoint, call one of the /// MapA2AHttpJson or MapA2AJsonRpc endpoint mapping methods during application startup. /// /// The host application builder to configure. /// The name of the agent to create an A2A server for. /// An optional callback to configure . /// The for chaining. public static IHostApplicationBuilder AddA2AServer(this IHostApplicationBuilder builder, string agentName, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(builder); builder.Services.AddA2AServer(agentName, configureOptions); return builder; } /// /// Registers an in the dependency injection container for the specified /// instance, keyed by the agent's . This method only /// registers the server; to expose it as an HTTP endpoint, call one of the MapA2AHttpJson or /// MapA2AJsonRpc endpoint mapping methods during application startup. /// /// The host application builder to configure. /// The agent instance to create an A2A server for. /// An optional callback to configure . /// The for chaining. public static IHostApplicationBuilder AddA2AServer(this IHostApplicationBuilder builder, AIAgent agent, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(builder); builder.Services.AddA2AServer(agent, configureOptions); return builder; } /// /// Registers an in the dependency injection container, keyed by the specified /// agent name. This method only registers the server; to expose it as an HTTP endpoint, call one of the /// MapA2AHttpJson or MapA2AJsonRpc endpoint mapping methods during application startup. /// /// The service collection to add the A2A server to. /// The name of the agent to create an A2A server for. /// An optional callback to configure . /// The for chaining. public static IServiceCollection AddA2AServer(this IServiceCollection services, string agentName, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(services); ArgumentException.ThrowIfNullOrWhiteSpace(agentName); A2AServerRegistrationOptions? options = null; if (configureOptions is not null) { options = new A2AServerRegistrationOptions(); configureOptions(options); } services.AddKeyedSingleton(agentName, (sp, _) => { var agent = sp.GetRequiredKeyedService(agentName); return CreateA2AServer(sp, agent, options); }); return services; } /// /// Registers an in the dependency injection container for the specified /// instance, keyed by the agent's . This method only /// registers the server; to expose it as an HTTP endpoint, call one of the MapA2AHttpJson or /// MapA2AJsonRpc endpoint mapping methods during application startup. /// /// The service collection to add the A2A server to. /// The agent instance to create an A2A server for. /// An optional callback to configure . /// The for chaining. public static IServiceCollection AddA2AServer(this IServiceCollection services, AIAgent agent, Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(agent); ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent) + "." + nameof(agent.Name)); A2AServerRegistrationOptions? options = null; if (configureOptions is not null) { options = new A2AServerRegistrationOptions(); configureOptions(options); } services.AddKeyedSingleton(agent.Name, (sp, _) => CreateA2AServer(sp, agent, options)); return services; } private static A2AServer CreateA2AServer(IServiceProvider serviceProvider, AIAgent agent, A2AServerRegistrationOptions? options) { var agentHandler = serviceProvider.GetKeyedService(agent.Name); if (agentHandler is null) { var agentSessionStore = serviceProvider.GetKeyedService(agent.Name); var runMode = options?.AgentRunMode ?? AgentRunMode.DisallowBackground; // Ensure that we have an IsolationKeyScopedAgentSessionStore registered. var isolationKeyProvider = serviceProvider.GetService(); if (agentSessionStore?.GetService() is null) { agentSessionStore ??= new InMemoryAgentSessionStore(); agentSessionStore = new IsolationKeyScopedAgentSessionStore(agentSessionStore, isolationKeyProvider, new()); } var hostAgent = new AIHostAgent( innerAgent: agent, sessionStore: agentSessionStore); agentHandler = new A2AAgentHandler(hostAgent, runMode); } var loggerFactory = serviceProvider.GetService() ?? NullLoggerFactory.Instance; var taskStore = serviceProvider.GetKeyedService(agent.Name) ?? new InMemoryTaskStore(); return new A2AServer( agentHandler, taskStore, new ChannelEventNotifier(), loggerFactory.CreateLogger(), options?.ServerOptions); } }