Files
Playa/hosts/Volcanic.AgenticCore.Telegram/A2A/TelegramA2ASetup.cs

69 lines
3.3 KiB
C#

using System.Globalization;
using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volcanic.AgenticCore.A2A.Server;
using Volcanic.AgenticCore.A2A.Server.Callers;
using Volcanic.AgenticCore.A2A.Server.Hosting;
using Volcanic.AgenticCore.Telegram.Chats;
namespace Volcanic.AgenticCore.Telegram.A2A;
/// <summary>Serves A2A from the bot: callers live in the allowed chats' workspaces, <c>notify_owner</c> goes to the chat.</summary>
public static class TelegramA2ASetup
{
/// <summary>Allowed chats' workspaces: only they can hold callers.</summary>
public static Func<IEnumerable<A2AWorkspace>> Workspaces(TelegramOptions options) => () =>
options.AllowedUserIds.Select(id => new A2AWorkspace(
ChatRegistry.WorkspaceFor(options, id), ChatRegistry.ScopeFor(id), id.ToString(CultureInfo.InvariantCulture)));
/// <summary>
/// Registers <c>IOptions&lt;A2AHostOptions&gt;</c> from <paramref name="a2a"/> (the one instance the server and
/// <c>/a2a</c> read). Off: <see cref="NoHttpServer"/> replaces Kestrel and nothing else is registered.
/// </summary>
public static void Register(IServiceCollection services, TelegramOptions options, A2AHostOptions a2a)
{
if (!a2a.Enabled)
{
services.AddOptions<A2AHostOptions>(); // Enabled = false
services.AddSingleton<IServer, NoHttpServer>();
return;
}
services.AddSingleton<IA2ACallerDirectory>(sp => new WorkspaceCallerDirectory(Workspaces(options), sp.GetRequiredService<ILogger<WorkspaceCallerDirectory>>()));
services.AddSingleton<IA2AOwnerNotifier>(sp => new TelegramOwnerNotifier(() => sp.GetRequiredService<ChatRegistry>()));
services.AddA2AServer(o =>
{
o.Enabled = true;
o.PublicUrl = a2a.PublicUrl;
o.MaxHops = a2a.MaxHops;
o.SessionIdleTimeout = a2a.SessionIdleTimeout;
o.MaxFileBytes = a2a.MaxFileBytes;
o.AllowPrivatePushUrls = a2a.AllowPrivatePushUrls;
o.Card = a2a.Card;
o.Model = options.Model;
o.OnPush = a2a.OnPush;
});
// Each session's zone is its owner's chat setting (read when the session opens).
services.AddOptions<A2AHostOptions>().Configure<IServiceProvider>((o, sp) =>
o.TimeZoneFor = caller => sp.GetService<ChatSettingsStore>()?.TimeZoneFor(long.Parse(caller.OwnerId, CultureInfo.InvariantCulture)));
}
public static void Map(IEndpointRouteBuilder app, A2AHostOptions a2a)
{
if (a2a.Enabled) app.MapA2AServer();
}
public static string Describe(A2AHostOptions a2a) =>
a2a.Enabled ? $"A2A: on at {a2a.PublicUrl ?? "(request host)"}/a2a, callers in each chat's .volcanic/a2a/callers.json" : "A2A: off";
/// <summary>
/// Kestrel could not open its listener: a port in use surfaces as an IOException wrapping
/// <see cref="AddressInUseException"/>; other bind errors (no permission, unknown address) as a raw SocketException.
/// </summary>
public static bool IsBindFailure(Exception ex) =>
ex is IOException { InnerException: AddressInUseException } or SocketException;
}