using System.Collections.Concurrent; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using ShiroBot.SDK.Abstractions; using ShiroBot.SDK.Config; using ShiroBot.SDK.Core; using ShiroBot.SDK.Models; using ShiroBot.SDK.Plugin; using MyAgent.Core; using MyAgent.Storage; using MyAgent.Tools; [assembly: ShiroBotApiCompatibility("0.9", "0.9")] namespace Shirobot.Plugin.MyAgent; [BotPlugin("Shirobot.Plugin.MyAgent", Name = "Shirobot.Plugin.MyAgent", Version = "0.1.0", Description = "QQ group AI agent")] public sealed class AgentPlugin : PluginBase { private IServiceProvider? _services; private WebApplication? _management; private bool _managementStarted; private CancellationTokenSource? _lifetime; private readonly ConcurrentDictionary _running = new(); protected override void ConfigureRoutes() { GroupCommands.MapAll(OnMessageAsync); Events.MapWhen(e => e.Platform == "qq" && e.Channel.Type == ChannelType.Group, OnDeletedAsync); } protected override async Task LoadAsync() { var settings = Context.Config.Load(); _lifetime = new CancellationTokenSource(); try { _management = AgentManagementServer.Build(Context, settings.ManagementListenUrl); _services = _management.Services; await _services.GetRequiredService().StartAsync(_lifetime.Token); await _services.GetRequiredService().StartAsync(_lifetime.Token); try { await _management.StartAsync(_lifetime.Token); _managementStarted = true; } catch (Exception ex) when (ex is not OperationCanceledException) { BotLog.Warning($"Agent management API did not start: {ex.Message}"); } } catch { if (_services is null) { _lifetime.Dispose(); _lifetime = null; if (_management is not null) await _management.DisposeAsync(); _management = null; } else { await OnUnloadAsync(); } throw; } BotLog.Info("Shirobot.Plugin.MyAgent loaded"); } private Task OnMessageAsync(MessageEvent message) { if (message.Platform != "qq" || message.Channel.Type != ChannelType.Group || message.Sender.Id == message.SelfId) return Task.CompletedTask; if (!long.TryParse(message.Channel.Id, out var groupId) || !long.TryParse(message.Sender.Id, out var userId)) return Task.CompletedTask; var services = _services; var lifetime = _lifetime; var context = Context; if (services is null || lifetime is null || lifetime.IsCancellationRequested) return Task.CompletedTask; var task = Task.Run(async () => { try { using var scope = context.UsePlatform(message.Platform); var configStore = services.GetRequiredService(); var cfg = await configStore.ReadAsync(lifetime.Token); if (cfg.Bot.AllowedGroupIds.Count > 0 && !cfg.Bot.AllowedGroupIds.Contains(groupId)) return; var changed = false; if (cfg.Bot.BotUserId <= 0 && long.TryParse(message.SelfId, out var selfId)) { cfg.Bot.BotUserId = selfId; changed = true; } if (cfg.Bot.DefaultGroupId <= 0) { cfg.Bot.DefaultGroupId = groupId; changed = true; } if (changed) await configStore.WriteAsync(cfg, lifetime.Token); var mentions = message.Segments.OfType().Select(m => m.UserId).ToList(); var mentioned = !string.IsNullOrWhiteSpace(message.SelfId) && mentions.Contains(message.SelfId); mentioned |= message.GetPlainText().Contains(cfg.Bot.BotDisplayName, StringComparison.OrdinalIgnoreCase); mentioned |= cfg.Bot.BotAliases.Any(alias => message.GetPlainText().Contains(alias, StringComparison.OrdinalIgnoreCase)); var request = new GroupChatUserMessage { GroupId = groupId, UserId = userId, DisplayName = message.Member?.DisplayName ?? message.Sender.Name ?? message.Sender.Id, Text = message.GetPlainText(), IsMentioned = mentioned, MentionedUserIds = mentions, Attachments = await ((ShirobotGroupPlatform)services.GetRequiredService()).ToAttachmentsAsync( message.Segments, groupId, services.GetRequiredService(), lifetime.Token), MessageId = message.MessageId, ReplyToMessageId = message.GetQuote()?.MessageId }; await foreach (var _ in services.GetRequiredService().RunGroupAsync( request, new AgentWorkflowOptions { PublishToHub = true, SendToPlatform = true, EmitUserEvent = true, EmitStatusEvents = true }, lifetime.Token)) { } } catch (OperationCanceledException) when (lifetime.IsCancellationRequested) { } catch (Exception ex) { BotLog.Error($"Agent message failed: {ex}"); } }, lifetime.Token); _running.TryAdd(task, 0); _ = task.ContinueWith(completed => _running.TryRemove(completed, out _), TaskScheduler.Default); return Task.CompletedTask; } private async Task OnDeletedAsync(MessageDeletedEvent e) { if (_services is null || _lifetime is null || _lifetime.IsCancellationRequested) return; if (!long.TryParse(e.Channel.Id, out var groupId)) return; await _services.GetRequiredService().HandleRecallAsync(new GroupChatRecallMessage { GroupId = groupId, UserId = long.TryParse(e.SenderId, out var senderId) ? senderId : 0, RecallMessageId = e.MessageId }, _lifetime.Token); } protected override async Task OnUnloadAsync() { var lifetime = _lifetime; var services = _services; if (lifetime is null || services is null) return; lifetime.Cancel(); try { await Task.WhenAll(_running.Keys).WaitAsync(TimeSpan.FromSeconds(3)); } catch (OperationCanceledException) { } catch (TimeoutException) { BotLog.Warning("Agent tasks are still stopping"); } if (_management is not null) { if (_managementStarted) await _management.StopAsync(CancellationToken.None); await services.GetRequiredService().StopAsync(CancellationToken.None); await services.GetRequiredService().StopAsync(CancellationToken.None); await _management.DisposeAsync(); _management = null; _managementStarted = false; } lifetime.Dispose(); _services = null; _lifetime = null; } } public sealed class AgentPluginSettings { [ConfigField("管理 API 监听地址")] public string ManagementListenUrl { get; set; } = "http://127.0.0.1:5087"; }