forked from admin/VolcanicAgenticCore
69 lines
3.3 KiB
C#
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<A2AHostOptions></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;
|
|
}
|