From 372e4ea099026f4c10d0a6cc253bac34d43bef99 Mon Sep 17 00:00:00 2001 From: Jafar Mirzaie Date: Thu, 2 Apr 2026 12:13:11 +0200 Subject: [PATCH 01/21] fix MultiMessageProgressModal --- .../RemoteEmails/MultiMessageProgressModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Extensions/Signum.Mailing.MicrosoftGraph/RemoteEmails/MultiMessageProgressModal.tsx b/Extensions/Signum.Mailing.MicrosoftGraph/RemoteEmails/MultiMessageProgressModal.tsx index c941b63bb6..d9cb20ef54 100644 --- a/Extensions/Signum.Mailing.MicrosoftGraph/RemoteEmails/MultiMessageProgressModal.tsx +++ b/Extensions/Signum.Mailing.MicrosoftGraph/RemoteEmails/MultiMessageProgressModal.tsx @@ -55,7 +55,7 @@ export function MultiMessageProgressModal(p: MultiMessageProgressModalProps): Re } function handleOnExited() { - p.onExited!({ errors: messageResultRef.current.toObject(a => a.id, a => a.error) }); + p.onExited!({ errors: messageResultRef.current.toObject(a => a.id, a => a.error ?? null) }); } var errors = messageResultRef.current.filter(a => a.error != null); @@ -93,7 +93,7 @@ export namespace MultiMessageProgressModal { } else { return makeRequest().then(r => r.json()).then(obj => { var a = obj as EmailResult; - return softCast({ errors: { [a.id]: a.error } }); + return softCast({ errors: { [a.id]: a.error ?? null } }); }); } } From 5093a9cd1bfbbfed299b23130a759fb562b0946c Mon Sep 17 00:00:00 2001 From: Jafar Mirzaie Date: Thu, 2 Apr 2026 13:00:38 +0200 Subject: [PATCH 02/21] fix Upgrade_20260321_SeleniumToPlaywright --- .../Upgrades/Upgrade_20260321_SeleniumToPlaywright.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Signum.Upgrade/Upgrades/Upgrade_20260321_SeleniumToPlaywright.cs b/Signum.Upgrade/Upgrades/Upgrade_20260321_SeleniumToPlaywright.cs index afee91a7cc..c4ee848364 100644 --- a/Signum.Upgrade/Upgrades/Upgrade_20260321_SeleniumToPlaywright.cs +++ b/Signum.Upgrade/Upgrades/Upgrade_20260321_SeleniumToPlaywright.cs @@ -95,7 +95,8 @@ private static void AssertClean200(HttpResponseMessage response) + content ); } - + + const int DebugChromePort = 9222; private static readonly Lazy> DefaultBrowser = new(async () => { var playwright = await Microsoft.Playwright.Playwright.CreateAsync(); From 3f9c7dcb2fa012b53906fd9803c4f1810e27427f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 12:28:14 +0000 Subject: [PATCH 03/21] Add AgentSkillEntity for runtime skill tree control without deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename AgentSkill → AgentSkillCode (pure code-side definition, no tree) - Add AgentSkillEntity: DB-backed tree nodes with Name, SkillCode, Active, UseCase (AgentUseCaseSymbol), ShortDescription, Instructions overrides, PropertyOverrides (MList), and SubSkills (MList) defining the hierarchy - Add AgentUseCaseSymbol with DefaultChatbot and Summarizer built-in values - Add AgentSkillPropertyAttribute / AgentSkillProperty_QueryListAttribute for marking overridable skill properties with custom string converters - Add ResolvedSkillNode: runtime combination of entity + code + sub-tree, built from DB and cached via GlobalLazy (invalidated on entity changes) - Circular reference validation on save (DirectedGraph.FeedbackEdgeSet) - AgentSkillLogic.RegisterCode() replaces hard-coded tree API - ChatbotController + ChatbotLogic updated to use ResolvedSkillNode via GetRootForUseCase(AgentUseCase.DefaultChatbot) - CurrentMcpRoot thread variable for IntroductionSkill.Describe/ListSkillNames - API endpoints: /api/agentSkill/registeredCodes, skillCodeDefaults, skillCodeProperties - AgentSkillClient.tsx registry: registerPropertyValueControl(attributeName, factory) - Templates/AgentSkill.tsx: MarkdownLine + LinkButton diff toggle vs code default, dynamic property value controls by attribute name, sub-skills table https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillClient.tsx | 55 +++ Extensions/Signum.Agent/AgentSkillEntity.cs | 77 ++++ Extensions/Signum.Agent/AgentSkillLogic.cs | 375 ++++++++++++++---- Extensions/Signum.Agent/ChatbotClient.tsx | 3 +- Extensions/Signum.Agent/ChatbotController.cs | 24 +- Extensions/Signum.Agent/ChatbotLogic.cs | 23 +- Extensions/Signum.Agent/Signum.Agent.d.ts | 48 +++ .../Signum.Agent/Skills/AutocompleteSkill.cs | 2 +- Extensions/Signum.Agent/Skills/ChartSkill.cs | 2 +- .../Signum.Agent/Skills/ConfirmUISkill.cs | 2 +- .../Skills/ConversationSumarizerSkill.cs | 2 +- .../Skills/CurrentServerContextSkill.cs | 2 +- .../Signum.Agent/Skills/EntityUrlSkill.cs | 2 +- .../Signum.Agent/Skills/GetUIContextSkill.cs | 2 +- .../Signum.Agent/Skills/IntroductionSkill.cs | 17 +- .../Signum.Agent/Skills/OperationSkill.cs | 2 +- .../Skills/QuestionSumarizerSkill.cs | 2 +- .../Signum.Agent/Skills/RetrieveSkill.cs | 2 +- Extensions/Signum.Agent/Skills/SearchSkill.cs | 2 +- .../Signum.Agent/Templates/AgentSkill.tsx | 145 +++++++ 20 files changed, 688 insertions(+), 101 deletions(-) create mode 100644 Extensions/Signum.Agent/AgentSkillClient.tsx create mode 100644 Extensions/Signum.Agent/AgentSkillEntity.cs create mode 100644 Extensions/Signum.Agent/Templates/AgentSkill.tsx diff --git a/Extensions/Signum.Agent/AgentSkillClient.tsx b/Extensions/Signum.Agent/AgentSkillClient.tsx new file mode 100644 index 0000000000..10a67ad7ef --- /dev/null +++ b/Extensions/Signum.Agent/AgentSkillClient.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import { ajaxGet } from '@framework/Services'; +import { Navigator, EntitySettings } from '@framework/Navigator'; +import * as AppContext from '@framework/AppContext'; +import { TypeContext } from '@framework/TypeContext'; +import { AgentSkillEntity, SkillPropertyMeta, SkillCodeDefaults } from './Signum.Agent'; + +export namespace AgentSkillClient { + + export function start(options: { routes: unknown[] }): void { + Navigator.addSettings(new EntitySettings(AgentSkillEntity, e => import('./Templates/AgentSkill'))); + AppContext.clearSettingsActions.push(() => propertyValueRegistry.clear()); + } + + // ─── Property value control registry ───────────────────────────────────── + + export type PropertyValueFactory = ( + ctx: TypeContext, + meta: SkillPropertyMeta + ) => React.ReactElement; + + const propertyValueRegistry = new Map(); + + /** + * Register a custom control for editing AgentSkillPropertyOverride.value, + * keyed by the C# attribute name without "Attribute" + * (e.g. "AgentSkillProperty_QueryList", "AgentSkillProperty"). + */ + export function registerPropertyValueControl( + attributeName: string, + factory: PropertyValueFactory + ): void { + propertyValueRegistry.set(attributeName, factory); + } + + export function getPropertyValueControl(attributeName: string): PropertyValueFactory | undefined { + return propertyValueRegistry.get(attributeName); + } + + // ─── API ────────────────────────────────────────────────────────────────── + + export namespace API { + export function getSkillCodeDefaults(skillCode: string): Promise { + return ajaxGet({ url: `/api/agentSkill/skillCodeDefaults/${encodeURIComponent(skillCode)}` }); + } + + export function getSkillCodeProperties(skillCode: string): Promise { + return ajaxGet({ url: `/api/agentSkill/skillCodeProperties/${encodeURIComponent(skillCode)}` }); + } + + export function getRegisteredCodes(): Promise { + return ajaxGet({ url: '/api/agentSkill/registeredCodes' }); + } + } +} diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs new file mode 100644 index 0000000000..16d098f7f4 --- /dev/null +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -0,0 +1,77 @@ +namespace Signum.Agent; + +public class AgentUseCaseSymbol : Symbol +{ + private AgentUseCaseSymbol() { } +} + +[AutoInit] +public static class AgentUseCase +{ + public static AgentUseCaseSymbol DefaultChatbot = null!; + public static AgentUseCaseSymbol Summarizer = null!; +} + +[EntityKind(EntityKind.Main, EntityData.Master)] +public class AgentSkillEntity : Entity +{ + [UniqueIndex] + [StringLengthValidator(Min = 1, Max = 200)] + public string Name { get; set; } + + [StringLengthValidator(Min = 1, Max = 200)] + public string SkillCode { get; set; } + + public bool Active { get; set; } = true; + + public AgentUseCaseSymbol? UseCase { get; set; } + + [StringLengthValidator(Min = 1, Max = 500)] + public string? ShortDescription { get; set; } + + [StringLengthValidator(MultiLine = true)] + public string? Instructions { get; set; } + + [BindParent] + public MList PropertyOverrides { get; set; } = new MList(); + + [BindParent] + public MList SubSkills { get; set; } = new MList(); + + [AutoExpressionField] + public override string ToString() => As.Expression(() => Name); + + protected override string? PropertyValidation(PropertyInfo pi) + { + if (pi.Name == nameof(SkillCode) && SkillCode.HasText() && AgentSkillLogic.RegisteredCodes.Any()) + { + if (!AgentSkillLogic.RegisteredCodes.ContainsKey(SkillCode)) + return $"SkillCode '{SkillCode}' is not registered. Available: {AgentSkillLogic.RegisteredCodes.Keys.ToString(", ")}"; + } + + return base.PropertyValidation(pi); + } +} + +public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity +{ + [StringLengthValidator(Min = 1, Max = 200)] + public string PropertyName { get; set; } + + [StringLengthValidator(MultiLine = true)] + public string? Value { get; set; } +} + +public class AgentSkillSubSkillEmbedded : EmbeddedEntity +{ + public Lite Skill { get; set; } + + public SkillActivation Activation { get; set; } +} + +[AutoInit] +public static class AgentSkillOperation +{ + public static ExecuteSymbol Save = null!; + public static DeleteSymbol Delete = null!; +} diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index e9a67945a2..9bd22c7299 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -16,37 +16,199 @@ namespace Signum.Agent; public static class AgentSkillLogic { public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); + internal static readonly AsyncThreadVariable CurrentMcpRoot = Statics.ThreadVariable("CurrentMcpRoot"); - public static AgentSkill? IntroductionSkill; + // Registered code definitions: key = AgentSkillCode.Name (e.g. "Search", "Retrieve") + public static Dictionary? Factory)> RegisteredCodes = new(); - public static ConversationSumarizerSkill ConversationSumarizerSkill = new ConversationSumarizerSkill(); - public static QuestionSumarizerSkill QuestionSumarizerSkill = new QuestionSumarizerSkill(); + // Legacy instances kept for ChatbotLogic internal use (summarization) + public static ConversationSumarizerSkill ConversationSumarizerSkill = null!; + public static QuestionSumarizerSkill QuestionSumarizerSkill = null!; - public static void Start(SchemaBuilder sb, AgentSkill? introductionSkill = null) + public static ResetLazy> RootsByUseCase = null!; + + /// Registers an AgentSkillCode singleton (no cloning on entity resolve). + public static T RegisterCode(T instance) where T : AgentSkillCode + { + RegisteredCodes[instance.Name] = (instance, null); + return instance; + } + + /// Registers an AgentSkillCode with a factory so each entity gets a fresh instance with property overrides applied. + public static T RegisterCode(Func factory) where T : AgentSkillCode + { + var defaultInstance = factory(); + RegisteredCodes[defaultInstance.Name] = (defaultInstance, () => factory()); + return defaultInstance; + } + + public static void Start(SchemaBuilder sb, AgentSkillCode? introductionSkill = null) { if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) return; + ConversationSumarizerSkill = RegisterCode(new ConversationSumarizerSkill()); + QuestionSumarizerSkill = RegisterCode(new QuestionSumarizerSkill()); + if (introductionSkill != null) - IntroductionSkill = introductionSkill; + RegisterCode(introductionSkill); + + SymbolLogic.Start(sb, () => [AgentUseCase.DefaultChatbot, AgentUseCase.Summarizer]); + + sb.Include() + .WithSave(AgentSkillOperation.Save) + .WithDelete(AgentSkillOperation.Delete) + .WithQuery(() => e => new + { + Entity = e, + e.Id, + e.Name, + e.SkillCode, + e.Active, + e.UseCase, + e.ShortDescription, + }); + + sb.Schema.EntityEvents().Saving += entity => + { + if (!entity.IsNew && entity.SubSkills.IsGraphModified) + ValidateNoCircularReferences(entity); + }; + + RootsByUseCase = sb.GlobalLazy(() => + { + var allEntities = Database.Query() + .ToList() + .ToDictionary(e => e.ToLite()); + + return allEntities.Values + .Where(e => e.UseCase != null && e.Active) + .GroupBy(e => e.UseCase!) + .ToDictionary( + g => g.Key, + g => ResolvedSkillNode.Resolve(g.SingleEx(), allEntities) + ); + }, new InvalidateWith(typeof(AgentSkillEntity))); } - public static AgentSkill WithSubSkill(this AgentSkill parent, SkillActivation activation, AgentSkill child) + static void ValidateNoCircularReferences(AgentSkillEntity entity) { - parent.SubSkills[child] = activation; - return parent; + using (new EntityCache(EntityCacheType.ForceNew)) + { + EntityCache.AddFullGraph(entity); + var allEntities = Database.RetrieveAll(); + + var graph = DirectedGraph.Generate( + allEntities, + e => e.Is(entity) + ? entity.SubSkills.Select(s => s.Skill.RetrieveAndRemember()).ToList() + : e.SubSkills.Select(s => s.Skill.RetrieveAndRemember()).ToList() + ); + + var problems = graph.FeedbackEdgeSet().Edges.ToList(); + if (problems.Count > 0) + throw new ApplicationException( + $"{problems.Count} cycle(s) found in AgentSkill graph:\n" + + problems.ToString(e => $" {e.From.Name} → {e.To.Name}", "\n")); + } } + + public static ResolvedSkillNode? GetRootForUseCase(AgentUseCaseSymbol symbol) => + RootsByUseCase.Value.TryGetC(symbol); + + /// Returns skill code metadata (properties marked [AgentSkillProperty]) for the frontend. + public static List GetSkillCodeProperties(string skillCode) + { + if (!RegisteredCodes.TryGetValue(skillCode, out var entry)) + return new List(); + + return entry.Default.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(pi => new { pi, attr = pi.GetCustomAttribute() }) + .Where(x => x.attr != null) + .Select(x => new SkillPropertyMeta + { + PropertyName = x.pi.Name, + AttributeName = x.attr!.GetType().Name.Before("Attribute"), + ValueHint = x.attr.ValueHint, + PropertyType = x.pi.PropertyType.TypeName(), + }) + .ToList(); + } + + /// Returns the default short description and instructions for a registered SkillCode. + public static SkillCodeDefaults GetSkillCodeDefaults(string skillCode) + { + if (!RegisteredCodes.TryGetValue(skillCode, out var entry)) + throw new KeyNotFoundException($"SkillCode '{skillCode}' is not registered."); + + return new SkillCodeDefaults + { + DefaultShortDescription = entry.Default.ShortDescription, + DefaultInstructions = entry.Default.OriginalInstructions, + }; + } +} + +public class SkillPropertyMeta +{ + public string PropertyName { get; set; } = null!; + public string AttributeName { get; set; } = null!; + public string? ValueHint { get; set; } + public string PropertyType { get; set; } = null!; +} + +public class SkillCodeDefaults +{ + public string DefaultShortDescription { get; set; } = null!; + public string DefaultInstructions { get; set; } = null!; +} + +// ─── AgentSkillPropertyAttribute ────────────────────────────────────────────── + +[AttributeUsage(AttributeTargets.Property)] +public class AgentSkillPropertyAttribute : Attribute +{ + /// Converts a string stored in the DB to the typed property value. Default uses ReflectionTools.Convert. + public virtual object? ConvertFromString(string? value, Type targetType) + { + if (value == null) + return null; + + return ReflectionTools.Convert(value, targetType); + } + + public virtual string? ValueHint => null; } +[AttributeUsage(AttributeTargets.Property)] +public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute +{ + /// Splits a comma-separated list of query keys and converts each via QueryLogic.ToQueryName. + public override object? ConvertFromString(string? value, Type targetType) + { + if (value == null) + return null; + + return value + .Split(',') + .Select(k => QueryLogic.ToQueryName(k.Trim())) + .ToHashSet(); + } -public abstract class AgentSkill + public override string? ValueHint => "Comma-separated query keys"; +} + +// ─── AgentSkillCode ─────────────────────────────────────────────────────────── + +public abstract class AgentSkillCode { public string Name => this.GetType().Name.Before("Skill"); - public string ShortDescription; - public Func IsAllowed; + public string ShortDescription { get; set; } = ""; + public Func IsAllowed { get; set; } = () => true; public Dictionary>? Replacements; - public static string SkillsDirectory = Path.Combine(Path.GetDirectoryName(typeof(AgentSkill).Assembly.Location)!, "Skills"); + public static string SkillsDirectory = Path.Combine(Path.GetDirectoryName(typeof(AgentSkillCode).Assembly.Location)!, "Skills"); string? originalInstructions; public string OriginalInstructions @@ -57,45 +219,33 @@ public string OriginalInstructions public string GetInstruction(object? context) { - StringBuilder sb = new StringBuilder(); - if (Replacements.IsNullOrEmpty()) - sb.AppendLineLF(OriginalInstructions); - else - sb.AppendLineLF(OriginalInstructions.Replace(Replacements.SelectDictionary(k => k, v => v(context)))); - - FillSubInstructions(sb); - - return sb.ToString(); - } - - public string FillSubInstructions() - { - var sb = new StringBuilder(); - FillSubInstructions(sb); - return sb.ToString(); + var text = OriginalInstructions; + if (!Replacements.IsNullOrEmpty()) + text = text.Replace(Replacements.SelectDictionary(k => k, v => v(context))); + return text; } - private void FillSubInstructions(StringBuilder sb) + public void ApplyPropertyOverrides(AgentSkillEntity entity) { - foreach (var (skill, activation) in SubSkills) + foreach (var po in entity.PropertyOverrides) { - sb.AppendLineLF("# Skill " + skill.Name); - sb.AppendLineLF("**Summary**: " + skill.ShortDescription); - sb.AppendLineLF(); + var pi = this.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(p => p.Name == po.PropertyName && p.GetCustomAttribute() != null); - if (activation == SkillActivation.Eager) - sb.AppendLineLF(skill.GetInstruction(null)); - else - sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); + if (pi == null) + continue; + + var attr = pi.GetCustomAttribute()!; + var value = attr.ConvertFromString(po.Value, pi.PropertyType); + pi.SetValue(this, value); } } - public Dictionary SubSkills = new Dictionary(); - - IEnumerable? chatbotTools; + IEnumerable? cachedTools; internal IEnumerable GetTools() { - return (chatbotTools ??= this.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) + return (cachedTools ??= this.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) .Where(m => m.GetCustomAttribute() != null) .Select(m => { @@ -124,61 +274,125 @@ internal IEnumerable GetMcpServerTools() => }.AddSignumJsonConverters(); public virtual JsonSerializerOptions GetJsonSerializerOptions() => JsonSerializationOptions; +} - public IEnumerable GetToolsRecursive() +// ─── ResolvedSkillNode ──────────────────────────────────────────────────────── + +/// A runtime node combining an AgentSkillEntity with its resolved AgentSkillCode and sub-skill tree. +public class ResolvedSkillNode +{ + public AgentSkillEntity Entity { get; } + public AgentSkillCode Code { get; } + public List<(ResolvedSkillNode Node, SkillActivation Activation)> SubSkills { get; } + + public string Name => Entity.Name; + public string ShortDescription => Entity.ShortDescription ?? Code.ShortDescription; + + ResolvedSkillNode(AgentSkillEntity entity, AgentSkillCode code, List<(ResolvedSkillNode, SkillActivation)> subSkills) { - var list = GetTools().ToList(); + Entity = entity; + Code = code; + SubSkills = subSkills; + } - foreach (var (skill, activation) in SubSkills) + public string GetInstruction(object? context) + { + var text = Entity.Instructions ?? Code.OriginalInstructions; + if (!Code.Replacements.IsNullOrEmpty()) + text = text.Replace(Code.Replacements.SelectDictionary(k => k, v => v(context))); + + var sb = new StringBuilder(text); + foreach (var (node, activation) in SubSkills) { + sb.AppendLineLF("# Skill " + node.Name); + sb.AppendLineLF("**Summary**: " + node.ShortDescription); + sb.AppendLineLF(); if (activation == SkillActivation.Eager) - list.AddRange(skill.GetToolsRecursive()); + sb.AppendLineLF(node.GetInstruction(null)); + else + sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); } + return sb.ToString(); + } + + public IEnumerable GetTools() => Code.GetTools(); + + public IEnumerable GetToolsRecursive() + { + var list = GetTools().ToList(); + foreach (var (node, activation) in SubSkills) + { + if (activation == SkillActivation.Eager) + list.AddRange(node.GetToolsRecursive()); + } return list; } - public AgentSkill? FindSkill(string name) + public ResolvedSkillNode? FindSkill(string name) { if (this.Name == name) return this; - foreach (var (skill, _) in SubSkills) + foreach (var (node, _) in SubSkills) { - var found = skill.FindSkill(name); + var found = node.FindSkill(name); if (found != null) return found; } return null; } - public AITool? FindTool(string name) + public AITool? FindTool(string toolName) { - var tool = GetTools().FirstOrDefault(t => t.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + var tool = GetTools().FirstOrDefault(t => t.Name.Equals(toolName, StringComparison.InvariantCultureIgnoreCase)); if (tool != null) return tool; - foreach (var (skill, _) in SubSkills) + foreach (var (node, _) in SubSkills) { - var found = skill.FindTool(name); + var found = node.FindTool(toolName); if (found != null) return found; } return null; } - public IEnumerable GetSkillsRecursive() + public IEnumerable GetSkillsRecursive() { yield return this; - foreach (var (skill, _) in SubSkills) - foreach (var s in skill.GetSkillsRecursive()) + foreach (var (node, _) in SubSkills) + foreach (var s in node.GetSkillsRecursive()) yield return s; } - public IEnumerable GetEagerSkillsRecursive() + public IEnumerable GetEagerSkillsRecursive() { yield return this; - foreach (var (skill, activation) in SubSkills) + foreach (var (node, activation) in SubSkills) { if (activation == SkillActivation.Eager) - foreach (var s in skill.GetEagerSkillsRecursive()) + foreach (var s in node.GetEagerSkillsRecursive()) yield return s; } } + + public static ResolvedSkillNode Resolve(AgentSkillEntity entity, Dictionary, AgentSkillEntity> allEntities) + { + var (defaultInstance, factory) = AgentSkillLogic.RegisteredCodes.GetOrThrow(entity.SkillCode, + $"SkillCode '{entity.SkillCode}' not registered. Entity: '{entity.Name}'"); + + AgentSkillCode code; + if (factory != null && entity.PropertyOverrides.Any()) + { + code = factory(); + code.ApplyPropertyOverrides(entity); + } + else + { + code = defaultInstance; + } + + var subSkills = entity.SubSkills + .Select(ss => (Resolve(allEntities.GetOrThrow(ss.Skill), allEntities), ss.Activation)) + .ToList(); + + return new ResolvedSkillNode(entity, code, subSkills); + } } public enum SkillActivation @@ -187,6 +401,8 @@ public enum SkillActivation Lazy, } +// ─── UIToolAttribute ────────────────────────────────────────────────────────── + /// /// Marks a [McpServerTool] method as a UI tool: the server never invokes its body. /// Instead the controller detects this attribute before calling InvokeAsync and routes @@ -196,23 +412,38 @@ public enum SkillActivation [AttributeUsage(AttributeTargets.Method)] public class UIToolAttribute : Attribute { } +// ─── MCP server builder extension ──────────────────────────────────────────── + public static partial class SignumMcpServerBuilderExtensions { - public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, AgentSkill rootSkill) + public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, AgentUseCaseSymbol? useCase = null) { - var allSkillTools = rootSkill.GetSkillsRecursive() - .ToDictionary(s => s.Name, s => s.GetMcpServerTools().ToList()); + useCase ??= AgentUseCase.DefaultChatbot; var sessionActivated = new ConcurrentDictionary>(); + ResolvedSkillNode GetRoot() => + AgentSkillLogic.GetRootForUseCase(useCase) + ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}"); + HashSet InitialActivated() => - rootSkill.GetEagerSkillsRecursive().Select(s => s.Name).ToHashSet(); + GetRoot().GetEagerSkillsRecursive().Select(s => s.Name).ToHashSet(); IEnumerable GetActivated(string? sessionId) => sessionId != null && sessionActivated.TryGetValue(sessionId, out var s) ? s : InitialActivated(); + McpServerTool? FindMcpTool(string toolName, IEnumerable activatedNames) + { + var root = GetRoot(); + return activatedNames + .Select(name => root.FindSkill(name)) + .OfType() + .SelectMany(n => n.Code.GetMcpServerTools()) + .FirstOrDefault(t => t.ProtocolTool.Name == toolName); + } + return builder .WithHttpTransport(options => { @@ -232,9 +463,12 @@ IEnumerable GetActivated(string? sessionId) => }) .WithListToolsHandler(async (ctx, ct) => { - var tools = GetActivated(ctx.Server.SessionId) - .Where(allSkillTools.ContainsKey) - .SelectMany(n => allSkillTools[n]) + var activated = GetActivated(ctx.Server.SessionId); + var root = GetRoot(); + var tools = activated + .Select(name => root.FindSkill(name)) + .OfType() + .SelectMany(n => n.Code.GetMcpServerTools()) .Select(t => t.ProtocolTool) .ToList(); @@ -243,24 +477,23 @@ IEnumerable GetActivated(string? sessionId) => .WithCallToolHandler(async (ctx, ct) => { var toolName = ctx.Params!.Name; - var tool = GetActivated(ctx.Server.SessionId) - .Where(allSkillTools.ContainsKey) - .SelectMany(n => allSkillTools[n]) - .FirstOrDefault(t => t.ProtocolTool.Name == toolName) + var activated = GetActivated(ctx.Server.SessionId); + var tool = FindMcpTool(toolName, activated) ?? throw new McpException($"Tool '{toolName}' not found"); CallToolResult result; + using (AgentSkillLogic.CurrentMcpRoot.Override(GetRoot())) using (AgentSkillLogic.IsMCP.Override(true)) result = await tool.InvokeAsync(ctx, ct); - // When Describe is called for a Lazy skill, activate it for this session if (toolName == nameof(IntroductionSkill.Describe) && ctx.Params.Arguments?.TryGetValue("skillName", out var je) == true && je.GetString() is { } skillName && ctx.Server.SessionId is { } sessionId) { - var newSkill = rootSkill.FindSkill(skillName); + var root = GetRoot(); + var newSkill = root.FindSkill(skillName); if (newSkill != null && sessionActivated.TryGetValue(sessionId, out var skills)) { foreach (var s in newSkill.GetEagerSkillsRecursive()) diff --git a/Extensions/Signum.Agent/ChatbotClient.tsx b/Extensions/Signum.Agent/ChatbotClient.tsx index 6d18f6aecd..c9d0a9d184 100644 --- a/Extensions/Signum.Agent/ChatbotClient.tsx +++ b/Extensions/Signum.Agent/ChatbotClient.tsx @@ -3,7 +3,7 @@ import { RouteObject } from 'react-router' import { ajaxGet, ajaxPost, wrapRequest, AjaxOptions } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' -import { ChatbotLanguageModelEntity, ChatSessionEntity, ChatMessageEntity, LanguageModelProviderSymbol, EmbeddingsLanguageModelEntity, ToolCallEmbedded, UserFeedback } from './Signum.Agent' +import { ChatbotLanguageModelEntity, ChatSessionEntity, ChatMessageEntity, LanguageModelProviderSymbol, EmbeddingsLanguageModelEntity, ToolCallEmbedded, UserFeedback, AgentSkillEntity } from './Signum.Agent' import { toAbsoluteUrl } from '../../Signum/React/AppContext'; import { Dic } from '@framework/Globals'; import { Finder } from '@framework/Finder'; @@ -17,6 +17,7 @@ export namespace ChatbotClient { //export let renderMarkdown = (markdown: string): React.JSX.Element => {markdown}; export function start(options: { routes: RouteObject[] }): void { + Navigator.addSettings(new EntitySettings(AgentSkillEntity, e => import('./Templates/AgentSkill'))); Navigator.addSettings(new EntitySettings(ChatbotLanguageModelEntity, e => import('./Templates/ChatbotLanguageModel'))); Navigator.addSettings(new EntitySettings(EmbeddingsLanguageModelEntity, e => import('./Templates/EmbeddingsLanguageModel'))); Navigator.addSettings(new EntitySettings(ChatSessionEntity, a => import('./Templates/ChatSession'))); diff --git a/Extensions/Signum.Agent/ChatbotController.cs b/Extensions/Signum.Agent/ChatbotController.cs index b99511e12d..8b100e38fb 100644 --- a/Extensions/Signum.Agent/ChatbotController.cs +++ b/Extensions/Signum.Agent/ChatbotController.cs @@ -14,6 +14,18 @@ namespace Signum.Agent; public class ChatbotController : Controller { + [HttpGet("api/agentSkill/skillCodeDefaults/{skillCode}")] + public SkillCodeDefaults GetSkillCodeDefaults(string skillCode) => + AgentSkillLogic.GetSkillCodeDefaults(skillCode); + + [HttpGet("api/agentSkill/skillCodeProperties/{skillCode}")] + public List GetSkillCodeProperties(string skillCode) => + AgentSkillLogic.GetSkillCodeProperties(skillCode); + + [HttpGet("api/agentSkill/registeredCodes")] + public List GetRegisteredCodes() => + AgentSkillLogic.RegisteredCodes.Keys.Order().ToList(); + [HttpGet("api/chatbot/provider/{providerKey}/models")] public async Task> GetModels(string providerKey, CancellationToken token) { @@ -111,6 +123,7 @@ public async Task AskQuestionAsync(CancellationToken ct) Session = session.ToLite(), SessionTitle = session.Title, LanguageModel = session.LanguageModel.RetrieveFromCache(), + RootSkill = AgentSkillLogic.GetRootForUseCase(AgentUseCase.DefaultChatbot), Messages = systemAndSummaries.Concat(remainingMessages).ToList(), }; } @@ -242,7 +255,7 @@ public async Task AskQuestionAsync(CancellationToken ct) // Detect UITool calls — the server never invokes their bodies var uiToolCalls = toolCalls.Where(fc => { - var tool = AgentSkillLogic.IntroductionSkill?.FindTool(fc.Name) + var tool = history.RootSkill?.FindTool(fc.Name) ?? throw new InvalidOperationException($"Tool '{fc.Name}' not found"); return ((AIFunction)tool).UnderlyingMethod?.GetCustomAttribute() != null; }).ToList(); @@ -343,7 +356,7 @@ async Task ExecuteToolAsync(ConversationHistory history, string toolId, string c var toolSw = Stopwatch.StartNew(); try { - AITool tool = AgentSkillLogic.IntroductionSkill?.FindTool(toolId) + AITool tool = history.RootSkill?.FindTool(toolId) ?? throw new InvalidOperationException($"Tool '{toolId}' not found"); var obj = await ((AIFunction)tool).InvokeAsync(new AIFunctionArguments(arguments), ct); toolSw.Stop(); @@ -433,21 +446,22 @@ ChatSessionEntity GetOrCreateSession(string? sessionID) ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) { - var intro = AgentSkillLogic.IntroductionSkill - ?? throw new InvalidOperationException("IntroductionSkill not configured"); + var rootSkill = AgentSkillLogic.GetRootForUseCase(AgentUseCase.DefaultChatbot) + ?? throw new InvalidOperationException("No active AgentSkillEntity with UseCase = DefaultChatbot"); var history = new ConversationHistory { Session = session.ToLite(), SessionTitle = session.Title, LanguageModel = session.LanguageModel.RetrieveFromCache(), + RootSkill = rootSkill, Messages = new List { new ChatMessageEntity { Role = ChatMessageRole.System, ChatSession = session.ToLite(), - Content = intro.GetInstruction(null), + Content = rootSkill.GetInstruction(null), }.Save() } }; diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index bd063348a9..bebb9282ee 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -373,6 +373,8 @@ public class ConversationHistory public string? SessionTitle { get; internal set; } + public ResolvedSkillNode? RootSkill { get; set; } + public List GetMessages() { return Messages.Select(m => ToChatMessage(m)).ToList(); @@ -428,10 +430,11 @@ ChatMessage ToChatMessage(ChatMessageEntity c) public List GetTools() { - var activatedSkills = new HashSet(); + if (RootSkill == null) + return new List(); - if (AgentSkillLogic.IntroductionSkill != null) - activatedSkills.Add(AgentSkillLogic.IntroductionSkill.Name); + var activatedSkills = new HashSet( + RootSkill.GetEagerSkillsRecursive().Select(s => s.Name)); foreach (var m in Messages) { @@ -445,7 +448,13 @@ public List GetTools() { var args = JsonSerializer.Deserialize>(tc.Arguments); if (args != null && args.TryGetValue("skillName", out var sn)) - activatedSkills.Add(sn.GetString()!); + { + var skillName = sn.GetString()!; + var newSkill = RootSkill.FindSkill(skillName); + if (newSkill != null) + foreach (var s in newSkill.GetEagerSkillsRecursive()) + activatedSkills.Add(s.Name); + } } catch { } } @@ -454,9 +463,9 @@ public List GetTools() } return activatedSkills - .Select(skillName => AgentSkillLogic.IntroductionSkill?.FindSkill(skillName)) - .OfType() - .SelectMany(skill => skill.GetToolsRecursive()) + .Select(name => RootSkill.FindSkill(name)) + .OfType() + .SelectMany(skill => skill.GetTools()) .ToList(); } diff --git a/Extensions/Signum.Agent/Signum.Agent.d.ts b/Extensions/Signum.Agent/Signum.Agent.d.ts index e77b297c0c..1c118ca4de 100644 --- a/Extensions/Signum.Agent/Signum.Agent.d.ts +++ b/Extensions/Signum.Agent/Signum.Agent.d.ts @@ -3,6 +3,54 @@ import * as Entities from '../../Signum/React/Signum.Entities'; import * as Basics from '../../Signum/React/Signum.Basics'; import * as Operations from '../../Signum/React/Signum.Operations'; import * as Authorization from '../Signum.Authorization/Signum.Authorization'; +export declare const AgentUseCaseSymbol: Type; +export interface AgentUseCaseSymbol extends Basics.Symbol { + Type: "AgentUseCase"; +} +export declare namespace AgentUseCase { + const DefaultChatbot: AgentUseCaseSymbol; + const Summarizer: AgentUseCaseSymbol; +} +export declare const AgentSkillEntity: Type; +export interface AgentSkillEntity extends Entities.Entity { + Type: "AgentSkill"; + name: string; + skillCode: string; + active: boolean; + useCase: AgentUseCaseSymbol | null; + shortDescription: string | null; + instructions: string | null; + propertyOverrides: Entities.MList; + subSkills: Entities.MList; +} +export declare namespace AgentSkillOperation { + const Save: Operations.ExecuteSymbol; + const Delete: Operations.DeleteSymbol; +} +export declare const AgentSkillPropertyOverrideEmbedded: Type; +export interface AgentSkillPropertyOverrideEmbedded extends Entities.EmbeddedEntity { + Type: "AgentSkillPropertyOverrideEmbedded"; + propertyName: string; + value: string | null; +} +export declare const AgentSkillSubSkillEmbedded: Type; +export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { + Type: "AgentSkillSubSkillEmbedded"; + skill: Entities.Lite; + activation: SkillActivation; +} +export declare const SkillActivation: EnumType; +export type SkillActivation = "Eager" | "Lazy"; +export interface SkillPropertyMeta { + propertyName: string; + attributeName: string; + valueHint: string | null; + propertyType: string; +} +export interface SkillCodeDefaults { + defaultShortDescription: string; + defaultInstructions: string; +} export interface ToolCallEmbedded { _response?: ChatMessageEntity; } diff --git a/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs b/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs index 52344d5d0d..16f167c5cd 100644 --- a/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs +++ b/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class AutocompleteSkill : AgentSkill +public class AutocompleteSkill : AgentSkillCode { public AutocompleteSkill() { diff --git a/Extensions/Signum.Agent/Skills/ChartSkill.cs b/Extensions/Signum.Agent/Skills/ChartSkill.cs index be99eaff2a..539c42ccd3 100644 --- a/Extensions/Signum.Agent/Skills/ChartSkill.cs +++ b/Extensions/Signum.Agent/Skills/ChartSkill.cs @@ -11,7 +11,7 @@ namespace Signum.Agent.Skills; -public class ChartSkill : AgentSkill +public class ChartSkill : AgentSkillCode { public ChartSkill() { diff --git a/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs b/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs index 4acc63a7b0..894f6fc4c1 100644 --- a/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs +++ b/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class ConfirmUISkill : AgentSkill +public class ConfirmUISkill : AgentSkillCode { public ConfirmUISkill() { diff --git a/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs b/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs index 13abd46271..3334017950 100644 --- a/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs +++ b/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs @@ -2,7 +2,7 @@ namespace Signum.Agent.Skills; -public class ConversationSumarizerSkill : AgentSkill +public class ConversationSumarizerSkill : AgentSkillCode { public ConversationSumarizerSkill() { diff --git a/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs b/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs index 3f38b540a5..032c729966 100644 --- a/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs +++ b/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs @@ -5,7 +5,7 @@ namespace Signum.Agent.Skills; -public class CurrentServerContextSkill : AgentSkill +public class CurrentServerContextSkill : AgentSkillCode { public static Func? UrlLeft; diff --git a/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs b/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs index de4d19837a..5b3af43424 100644 --- a/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs +++ b/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs @@ -1,6 +1,6 @@ namespace Signum.Agent.Skills; -public class EntityUrlSkill : AgentSkill +public class EntityUrlSkill : AgentSkillCode { public EntityUrlSkill() { diff --git a/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs b/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs index 374610b53c..ceed091cc8 100644 --- a/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs +++ b/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class GetUIContextSkill : AgentSkill +public class GetUIContextSkill : AgentSkillCode { public GetUIContextSkill() { diff --git a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs index a9a56f40b2..c48569aace 100644 --- a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs +++ b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs @@ -6,7 +6,7 @@ namespace Signum.Agent.Skills; -public class IntroductionSkill : AgentSkill +public class IntroductionSkill : AgentSkillCode { public IntroductionSkill() { @@ -18,15 +18,16 @@ public IntroductionSkill() }; } - [McpServerTool, Description("Gets the introduction for an skill and discorver new tools")] + [McpServerTool, Description("Gets the instructions for a skill and discovers its tools")] public string Describe(string skillName) { - //throw new InvalidOperationException("bla"); - if (skillName.Contains("error")) throw new Exception(skillName + " has an error"); - var skill = this.FindSkill(skillName) + var root = AgentSkillLogic.CurrentMcpRoot.Value + ?? throw new InvalidOperationException("Describe can only be called from an MCP context"); + + var skill = root.FindSkill(skillName) ?? throw new KeyNotFoundException($"Skill '{skillName}' not found"); return skill.GetInstruction(null); @@ -35,7 +36,11 @@ public string Describe(string skillName) [McpServerTool, Description("List available skills with a short description, start here to discover new tools.")] public Dictionary ListSkillNames() { - return this.GetSkillsRecursive().ToDictionary(a => a.Name, a => a.ShortDescription); + var root = AgentSkillLogic.CurrentMcpRoot.Value + ?? throw new InvalidOperationException("ListSkillNames can only be called from an MCP context"); + + return root.GetSkillsRecursive().ToDictionary(a => a.Name, a => a.ShortDescription); } } + diff --git a/Extensions/Signum.Agent/Skills/OperationSkill.cs b/Extensions/Signum.Agent/Skills/OperationSkill.cs index db4c6f9a01..d00b9d1410 100644 --- a/Extensions/Signum.Agent/Skills/OperationSkill.cs +++ b/Extensions/Signum.Agent/Skills/OperationSkill.cs @@ -6,7 +6,7 @@ namespace Signum.Agent.Skills; -public class OperationSkill : AgentSkill +public class OperationSkill : AgentSkillCode { public OperationSkill() { diff --git a/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs b/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs index 0bfd987b78..d9e627b9c8 100644 --- a/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs +++ b/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class QuestionSumarizerSkill : AgentSkill +public class QuestionSumarizerSkill : AgentSkillCode { public QuestionSumarizerSkill() { diff --git a/Extensions/Signum.Agent/Skills/RetrieveSkill.cs b/Extensions/Signum.Agent/Skills/RetrieveSkill.cs index 7cecfc3ab3..0ad1174d79 100644 --- a/Extensions/Signum.Agent/Skills/RetrieveSkill.cs +++ b/Extensions/Signum.Agent/Skills/RetrieveSkill.cs @@ -4,7 +4,7 @@ namespace Signum.Agent.Skills; -public class RetrieveSkill : AgentSkill +public class RetrieveSkill : AgentSkillCode { public RetrieveSkill() { diff --git a/Extensions/Signum.Agent/Skills/SearchSkill.cs b/Extensions/Signum.Agent/Skills/SearchSkill.cs index 6af7bc6570..aec9a94f22 100644 --- a/Extensions/Signum.Agent/Skills/SearchSkill.cs +++ b/Extensions/Signum.Agent/Skills/SearchSkill.cs @@ -14,7 +14,7 @@ namespace Signum.Agent.Skills; -public class SearchSkill : AgentSkill +public class SearchSkill : AgentSkillCode { public Func InlineQueryName = q => false; diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/AgentSkill.tsx new file mode 100644 index 0000000000..18fe04ef92 --- /dev/null +++ b/Extensions/Signum.Agent/Templates/AgentSkill.tsx @@ -0,0 +1,145 @@ +import * as React from 'react' +import { AutoLine, CheckboxLine, EntityCombo, EntityLine, EntityTable, EnumLine, TextBoxLine } from '@framework/Lines' +import { TypeContext } from '@framework/TypeContext' +import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded, SkillPropertyMeta } from '../Signum.Agent' +import { useAPI, useForceUpdate } from '@framework/Hooks' +import { AgentSkillClient } from '../AgentSkillClient' +import { MarkdownLine } from '@framework/Lines/MarkdownLine' +import { DiffDocument } from '../../Signum.DiffLog/Templates/DiffDocument' +import { LinkButton } from '@framework/Basics/LinkButton' + +export default function AgentSkill(p: { ctx: TypeContext }): React.JSX.Element { + const ctx = p.ctx; + const ctx4 = ctx.subCtx({ labelColumns: 4 }); + const forceUpdate = useForceUpdate(); + + const skillCode = ctx.value.skillCode; + + const skillCodeDefaults = useAPI( + () => skillCode ? AgentSkillClient.API.getSkillCodeDefaults(skillCode) : Promise.resolve(null), + [skillCode] + ); + + const skillCodeProperties = useAPI( + () => skillCode ? AgentSkillClient.API.getSkillCodeProperties(skillCode) : Promise.resolve([]), + [skillCode] + ); + + const registeredCodes = useAPI(() => AgentSkillClient.API.getRegisteredCodes(), []); + + return ( +
+
+
+ e.name)} /> + e.skillCode)} readOnly={registeredCodes == null} optionItems={registeredCodes ?? []} + onChange={() => { + ctx.value.shortDescription = null; + ctx.value.instructions = null; + ctx.value.propertyOverrides = []; + forceUpdate(); + }} /> + e.active)} inlineCheckbox /> + e.useCase)} /> +
+
+ e.shortDescription)} + helpText={skillCodeDefaults && ctx.value.shortDescription == null + ? `Default: ${skillCodeDefaults.defaultShortDescription}` + : undefined} /> +
+
+ + + +
+ Sub-Skills + e.subSkills)} columns={EntityTable.typedColumns([ + { property: e => e.skill, template: ectx => e.skill)} /> }, + { property: e => e.activation, template: ectx => e.activation)} /> }, + ])} /> +
+ + {skillCodeProperties && skillCodeProperties.length > 0 && ( +
+ Property Overrides + e.propertyOverrides)} columns={EntityTable.typedColumns([ + { + property: e => e.propertyName, + template: ectx => ( + e.propertyName)} + optionItems={skillCodeProperties.map(m => m.propertyName)} + onChange={() => { ectx.value.value = null; forceUpdate(); }} /> + ) + }, + { + property: e => e.value, + template: ectx => e.value)} properties={skillCodeProperties} propertyName={ectx.value.propertyName} /> + }, + ])} /> +
+ )} +
+ ); +} + +function InstructionsField(p: { + ctx: TypeContext, + defaults: { defaultInstructions: string } | null | undefined +}): React.JSX.Element { + const [showDiff, setShowDiff] = React.useState(false); + const ctx = p.ctx; + + const label = ( + + Instructions + {p.defaults && ( + setShowDiff(v => !v)}> + {showDiff ? "Show Editor" : "Show Diff"} + + )} + + ); + + if (showDiff && p.defaults) { + return ( +
+ + +
+ ); + } + + return ( + e.instructions)} + helpText={p.defaults && ctx.value.instructions == null ? "Using default from code (.md file)" : undefined} + label={label as any} + /> + ); +} + +function PropertyValueControl(p: { + ctx: TypeContext, + properties: SkillPropertyMeta[], + propertyName: string +}): React.JSX.Element { + const meta = p.properties.find(m => m.propertyName === p.propertyName); + + if (!meta) { + return ; + } + + const factory = AgentSkillClient.getPropertyValueControl(meta.attributeName); + if (factory) { + return factory(p.ctx, meta); + } + + return ( + + ); +} From 8e3682ed348ea8bcf7f921ddf65e5120b580f1a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 13:30:40 +0000 Subject: [PATCH 04/21] Extract agent loop into ChatbotLogic with IAgentOutput for headless execution - Add IAgentOutput interface with default no-op implementations for all events: OnSystemMessage, OnUserQuestion, OnSummarization, OnAssistantStarted, OnTextChunk, OnAssistantMessage, OnToolStart, OnToolFinished, OnTitleUpdated - Add NullAgentOutput singleton (no-op implementation) - Move agent loop (while-true LLM call + tool execution) from controller to ChatbotLogic.RunAgentLoopAsync(ConversationHistory, IAgentOutput, ct) - Move ExecuteToolAsync and FormatToolError to ChatbotLogic (static) - Add ChatbotLogic.RunHeadlessAsync(prompt, useCase, languageModel, output, ct) creates session, system msg, user msg and runs the loop without HTTP - Add HttpAgentOutput class in controller implementing IAgentOutput via UINotification streaming writes to HttpResponse - Simplify ChatbotController.AskQuestionAsync to HTTP setup + delegate to logic https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/ChatbotController.cs | 411 ++++++------------- Extensions/Signum.Agent/ChatbotLogic.cs | 310 ++++++++++++++ 2 files changed, 428 insertions(+), 293 deletions(-) diff --git a/Extensions/Signum.Agent/ChatbotController.cs b/Extensions/Signum.Agent/ChatbotController.cs index 8b100e38fb..6ecf3967a4 100644 --- a/Extensions/Signum.Agent/ChatbotController.cs +++ b/Extensions/Signum.Agent/ChatbotController.cs @@ -30,51 +30,41 @@ public List GetRegisteredCodes() => public async Task> GetModels(string providerKey, CancellationToken token) { var symbol = SymbolLogic.ToSymbol(providerKey); - - var list = await ChatbotLogic.GetModelNamesAsync(symbol, token); - - return list.Order().ToList(); + return (await ChatbotLogic.GetModelNamesAsync(symbol, token)).Order().ToList(); } [HttpGet("api/chatbot/provider/{providerKey}/embeddingModels")] public async Task> GetEmbeddingModels(string providerKey, CancellationToken token) { var symbol = SymbolLogic.ToSymbol(providerKey); - - var list = await ChatbotLogic.GetEmbeddingModelNamesAsync(symbol, token); - - return list.Order().ToList(); + return (await ChatbotLogic.GetEmbeddingModelNamesAsync(symbol, token)).Order().ToList(); } [HttpPost("api/chatbot/feedback/{messageId}")] public void SetFeedback(int messageId, [FromBody] SetFeedbackRequest request) { var message = Database.Retrieve(messageId); - if (message.Role != ChatMessageRole.Assistant) throw new InvalidOperationException("Feedback can only be set on Assistant messages."); - message.UserFeedback = request.Feedback; message.UserFeedbackMessage = request.Feedback == UserFeedback.Negative ? request.Message : null; message.Save(); } [HttpGet("api/chatbot/messages/{sessionID}")] - public List GetMessagesBySessionId(int sessionID) - { - var messages = Database.Query().Where(m => m.ChatSession.Id == sessionID).OrderBy(cm => cm.CreationDate).ToList(); - - return messages; - } + public List GetMessagesBySessionId(int sessionID) => + Database.Query() + .Where(m => m.ChatSession.Id == sessionID) + .OrderBy(cm => cm.CreationDate) + .ToList(); [HttpPost("api/chatbot/ask")] public async Task AskQuestionAsync(CancellationToken ct) { var resp = this.HttpContext.Response; + var output = new HttpAgentOutput(resp); try { - var context = this.HttpContext; - string sessionID = HttpContext.Request.Headers["X-Chatbot-Session-Id"].ToString(); string question = Encoding.UTF8.GetString(HttpContext.Request.Body.ReadAllBytesAsync().Result); @@ -84,17 +74,13 @@ public async Task AskQuestionAsync(CancellationToken ct) if (sessionID.HasText() == false || sessionID == "undefined") { - await resp.WriteAsync(UINotification(ChatbotUICommand.SessionId, session.Id.ToString()), ct); + await resp.WriteAsync(output.Notification(ChatbotUICommand.SessionId, session.Id.ToString()), ct); await resp.Body.FlushAsync(); history = CreateNewConversationHistory(session); var init = history.Messages.SingleEx(); - - await resp.WriteAsync(UINotification(ChatbotUICommand.System), ct); - await resp.WriteAsync(init.Content!, ct); - await resp.WriteAsync("\n", ct); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, init.Id.ToString()), ct); + await output.OnSystemMessageAsync(init, ct); } else { @@ -109,7 +95,7 @@ public async Task AskQuestionAsync(CancellationToken ct) .OrderBy(a => a.CreationDate) .ToList(); - var lastSystemDate = systemAndSummaries.Max(a => a.CreationDate); //Inial or Summary + var lastSystemDate = systemAndSummaries.Max(a => a.CreationDate); var remainingMessages = Database.Query() .Where(c => c.ChatSession.Is(session)) @@ -129,319 +115,83 @@ public async Task AskQuestionAsync(CancellationToken ct) } } - // If this request is a UI reply, inject the tool result into history and skip adding a new user message string? uiReplyCallId = HttpContext.Request.Headers["X-Chatbot-UIReply-CallId"].ToString().DefaultToNull(); string? uiReplyToolId = HttpContext.Request.Headers["X-Chatbot-UIReply-ToolId"].ToString().DefaultToNull(); bool isRecover = HttpContext.Request.Headers["X-Chatbot-Recover"].ToString() == "true"; if (uiReplyCallId != null && uiReplyToolId != null) { - // Create the Tool message now that we have the client's result - var toolMsg = new ChatMessageEntity() + var toolMsg = new ChatMessageEntity { ChatSession = session.ToLite(), Role = ChatMessageRole.Tool, ToolCallID = uiReplyCallId, ToolID = uiReplyToolId, - Content = question, // request body carries the JSON result + Content = question, }.Save(); - await resp.WriteAsync(UINotification(ChatbotUICommand.Tool, uiReplyToolId + "/" + uiReplyCallId), ct); + await resp.WriteAsync(output.Notification(ChatbotUICommand.Tool, uiReplyToolId + "/" + uiReplyCallId), ct); await resp.WriteAsync(question, ct); await resp.WriteAsync("\n"); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, toolMsg.Id.ToString()), ct); + await resp.WriteAsync(output.Notification(ChatbotUICommand.MessageId, toolMsg.Id.ToString()), ct); await resp.Body.FlushAsync(); history.Messages.Add(toolMsg); } else if (isRecover) { - // Recover mode: body must be empty — we resume from the last saved state if (question.HasText()) throw new InvalidOperationException("Recover requests must have an empty body."); - // Case C: last message in history is an Assistant with an unresponded regular tool call. - // Re-execute that tool call so the agent loop can continue. var lastAssistant = history.Messages.LastOrDefault(m => m.Role == ChatMessageRole.Assistant); if (lastAssistant != null) { var pendingToolCall = lastAssistant.ToolCalls - .Select(tc => tc) .FirstOrDefault(tc => !tc.IsUITool && !history.Messages.Any(m => m.Role == ChatMessageRole.Tool && m.ToolCallID == tc.CallId)); if (pendingToolCall != null) { var parsedArgs = JsonSerializer.Deserialize>(pendingToolCall.Arguments) ?? new(); - await ExecuteToolAsync(history, pendingToolCall.ToolId, pendingToolCall.CallId, parsedArgs, ct); + await ChatbotLogic.ExecuteToolAsync(history, pendingToolCall.ToolId, pendingToolCall.CallId, parsedArgs, output, ct); } } - // Case D (User or Tool as last message): history is already correct — fall through to the agent loop. } else { - ChatMessageEntity userQuestion = NewChatMessage(session.ToLite(), question, ChatMessageRole.User).Save(); - - history.Messages.Add(userQuestion); - - await resp.WriteAsync(UINotification(ChatbotUICommand.QuestionId, userQuestion.Id.ToString()), ct); - await resp.Body.FlushAsync(); - } - - var client = ChatbotLogic.GetChatClient(history.LanguageModel); - while (true) - { - if (history.LanguageModel.MaxTokens != null && history.Messages.Skip(1).LastOrDefault()?.InputTokens > history.LanguageModel.MaxTokens * 0.8) - { - var systemMsg = history.Messages.FirstEx(); - if (systemMsg.Role != ChatMessageRole.System) - throw new InvalidOperationException("First message is expected to be system"); - var normalMessages = history.Messages.Skip(1).ToList(); - var toKeepIndex = normalMessages.FindLastIndex(a => a.InputTokens < history.LanguageModel.MaxTokens * 0.5).NotFoundToNull() ?? (normalMessages.Count - 1); - var toSumarize = normalMessages.Take(toKeepIndex).ToList(); - var toKeep = normalMessages.Skip(toKeepIndex).ToList(); - - var summaryContent = await ChatbotLogic.SumarizeConversation(toSumarize, history.LanguageModel, ct); - - var summary = new ChatMessageEntity - { - ChatSession = history.Session, - Role = ChatMessageRole.System, - Content = $"## Summary of earlier conversation\n{summaryContent}\n\n---\nRecent messages follow:", - }.Save(); - - await resp.WriteAsync(UINotification(ChatbotUICommand.System), ct); - await resp.WriteAsync(summary.Content!, ct); - await resp.WriteAsync("\n", ct); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, summary.Id.ToString()), ct); - - history.Messages = [systemMsg, summary, .. toKeep]; - } - - var tools = history.GetTools(); - var options = ChatbotLogic.ChatOptions(history.LanguageModel, tools); - - var messages = history.GetMessages(); - ChatbotLogic.GetProvider(history.LanguageModel).CustomizeMessagesAndOptions(messages, options); - List updates = []; - var sw = Stopwatch.StartNew(); - await foreach (var update in client.GetStreamingResponseAsync(messages, options, ct)) - { - if (updates.Count == 0) - await resp.WriteAsync(UINotification(ChatbotUICommand.AssistantAnswer), ct); - - updates.Add(update); - var text = update.Text; - if (text.HasText()) - { - await resp.WriteAsync(text); - await resp.Body.FlushAsync(); - } - } - sw.Stop(); - - var response = updates.ToChatResponse(); - var responseMsg = response.Messages.SingleEx(); - - - var notSupported = responseMsg.Contents.Where(a => !(a is FunctionCallContent or Microsoft.Extensions.AI.TextContent)).ToList(); - - if (notSupported.Any()) - throw new InvalidOperationException("Unexpected response" + notSupported.ToString(a => a.GetType().Name, ", ")); - - var usage = response.Usage; - - var toolCalls = responseMsg.Contents.OfType().ToList(); - - // Detect UITool calls — the server never invokes their bodies - var uiToolCalls = toolCalls.Where(fc => + var userQuestion = new ChatMessageEntity { - var tool = history.RootSkill?.FindTool(fc.Name) - ?? throw new InvalidOperationException($"Tool '{fc.Name}' not found"); - return ((AIFunction)tool).UnderlyingMethod?.GetCustomAttribute() != null; - }).ToList(); - - if (uiToolCalls.Count > 1) - throw new InvalidOperationException($"The LLM invoked more than one UITool in a single response ({string.Join(", ", uiToolCalls.Select(t => t.Name))}). Only one UITool can be active at a time."); - - var answer = new ChatMessageEntity - { - ChatSession = history.Session, - Role = ChatMessageRole.Assistant, - Content = responseMsg.Text, - LanguageModel = session.LanguageModel, - InputTokens = (int?)usage?.InputTokenCount, - CachedInputTokens = (int?)usage?.CachedInputTokenCount, - OutputTokens = (int?)usage?.OutputTokenCount, - ReasoningOutputTokens = (int?)usage?.ReasoningTokenCount, - Duration = sw.Elapsed, - ToolCalls = toolCalls.Select(fc => new ToolCallEmbedded - { - ToolId = fc.Name, - CallId = fc.CallId, - Arguments = JsonSerializer.Serialize(fc.Arguments), - IsUITool = uiToolCalls.Any(u => u.CallId == fc.CallId), - }).ToMList() + ChatSession = session.ToLite(), + Role = ChatMessageRole.User, + Content = question, }.Save(); - - - Expression> NullableAdd = (a, b) => a == null && b == null ? null : (a ?? 0) + (b ?? 0); - - history.Session.InDB().UnsafeUpdate() - .Set(a => a.TotalInputTokens, a => NullableAdd.Evaluate(a.TotalInputTokens, answer.InputTokens)) - .Set(a => a.TotalCachedInputTokens, a => NullableAdd.Evaluate(a.TotalCachedInputTokens, answer.CachedInputTokens)) - .Set(a => a.TotalOutputTokens, a => NullableAdd.Evaluate(a.TotalOutputTokens, answer.OutputTokens)) - .Set(a => a.TotalReasoningOutputTokens, a => NullableAdd.Evaluate(a.TotalReasoningOutputTokens, answer.ReasoningOutputTokens)) - .Set(a => a.TotalToolCalls, a => a.TotalToolCalls + answer.ToolCalls.Count) - .Execute(); - - foreach (var item in answer.ToolCalls) - { - await resp.WriteAsync("\n"); - var cmd = item.IsUITool ? ChatbotUICommand.AssistantUITool : ChatbotUICommand.AssistantTool; - await resp.WriteAsync(UINotification(cmd, item.ToolId + "/" + item.CallId), ct); - await resp.WriteAsync(item.Arguments, ct); - } - - await resp.WriteAsync("\n"); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, answer.Id.ToString()), ct); - await resp.Body.FlushAsync(); - - history.Messages.Add(answer); - - if (toolCalls.IsEmpty()) - break; - - // If a UITool was invoked, close the stream — the client will resume via a new ask request - if (uiToolCalls.Any()) - goto doneWithTools; - - foreach (var funCall in toolCalls) - await ExecuteToolAsync(history, funCall.Name, funCall.CallId, funCall.Arguments!, ct); + history.Messages.Add(userQuestion); + await output.OnUserQuestionAsync(userQuestion, ct); } - doneWithTools: - - if (history.SessionTitle == null || history.SessionTitle.StartsWith("!*$")) - { - history.SessionTitle = history.Session.InDB(a => a.Title); - if (history.SessionTitle == null || history.SessionTitle.StartsWith("!*$")) - { - string title = await ChatbotLogic.SumarizeTitle(history, ct); - if (title.HasText() && title.ToLower() != "pending") - { - history.Session.InDB().UnsafeUpdate(a => a.Title, a => title); - await resp.WriteAsync(UINotification(ChatbotUICommand.SessionTitle, title), ct); - } - } - } + await ChatbotLogic.RunAgentLoopAsync(history, output, ct); } catch (Exception e) { var ex = e.LogException().ToLiteFat(); - - await resp.WriteAsync(UINotification(ChatbotUICommand.Exception, ex!.Id.ToString()), ct); + await resp.WriteAsync(output.Notification(ChatbotUICommand.Exception, ex!.Id.ToString()), ct); await resp.WriteAsync(ex!.ToString()!, ct); await resp.WriteAsync("\n"); await resp.Body.FlushAsync(); } } - - async Task ExecuteToolAsync(ConversationHistory history, string toolId, string callId, IDictionary arguments, CancellationToken ct) - { - var resp = this.HttpContext.Response; - - await resp.WriteAsync(UINotification(ChatbotUICommand.Tool, toolId + "/" + callId), ct); - - var toolSw = Stopwatch.StartNew(); - try - { - AITool tool = history.RootSkill?.FindTool(toolId) - ?? throw new InvalidOperationException($"Tool '{toolId}' not found"); - var obj = await ((AIFunction)tool).InvokeAsync(new AIFunctionArguments(arguments), ct); - toolSw.Stop(); - - string toolResponse = JsonSerializer.Serialize(obj); - var toolMsg = new ChatMessageEntity() - { - ChatSession = history.Session, - Role = ChatMessageRole.Tool, - ToolCallID = callId, - ToolID = toolId, - Content = toolResponse, - Duration = toolSw.Elapsed, - }.Save(); - - await resp.WriteAsync(toolResponse, ct); - await resp.WriteAsync("\n"); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, toolMsg.Id.ToString()), ct); - await resp.Body.FlushAsync(); - history.Messages.Add(toolMsg); - } - catch (Exception e) - { - toolSw.Stop(); - var errorContent = FormatToolError(toolId, e, arguments); - - ChatMessageEntity toolMsg; - using (AuthLogic.Disable()) - { - toolMsg = new ChatMessageEntity() - { - ChatSession = history.Session, - Role = ChatMessageRole.Tool, - ToolCallID = callId, - ToolID = toolId, - Content = errorContent, - Exception = e.LogException().ToLiteFat(), - Duration = toolSw.Elapsed, - }.Save(); - } - - await resp.WriteAsync(UINotification(ChatbotUICommand.Exception, toolMsg.Exception!.Id.ToString()), ct); - await resp.WriteAsync(errorContent, ct); - await resp.WriteAsync("\n"); - await resp.WriteAsync(UINotification(ChatbotUICommand.MessageId, toolMsg.Id.ToString()), ct); - await resp.Body.FlushAsync(); - history.Messages.Add(toolMsg); - } - } - - static string FormatToolError(string toolName, Exception e, IDictionary? arguments) - { - var sb = new StringBuilder(); - sb.AppendLine($"Tool '{toolName}' failed."); - if (arguments != null && arguments.Count > 0) - sb.AppendLine($"Arguments: {JsonSerializer.Serialize(arguments).Etc(300)}"); - sb.AppendLine($"Error: {e.GetType().Name}: {e.Message}"); - - if (e.Data["Hint"] is string s) - sb.AppendLine($"Hint: {s}"); - - sb.AppendLine("Please review the error and try again with corrected arguments."); - return sb.ToString(); - } - - string UINotification(ChatbotUICommand commandName, string? payload = null) - { - if (payload == null) - return "$!" + commandName + "\n"; - - if (payload.Contains("\n")) - throw new InvalidOperationException("Payload has newlines!"); - - return "$!" + commandName + ":" + payload + "\n"; - } - ChatSessionEntity GetOrCreateSession(string? sessionID) { - return sessionID.HasText() == false || sessionID == "undefined" ? new ChatSessionEntity - { - LanguageModel = ChatbotLogic.DefaultLanguageModel.Value ?? throw new InvalidOperationException($"No default {typeof(ChatbotLanguageModelEntity).Name}"), - User = UserEntity.Current, - StartDate = Clock.Now, - Title = null, - }.Save() : Database.Query().SingleEx(a => a.Id == PrimaryKey.Parse(sessionID, typeof(ChatSessionEntity))); + return sessionID.HasText() == false || sessionID == "undefined" + ? new ChatSessionEntity + { + LanguageModel = ChatbotLogic.DefaultLanguageModel.Value + ?? throw new InvalidOperationException($"No default {typeof(ChatbotLanguageModelEntity).Name}"), + User = UserEntity.Current, + StartDate = Clock.Now, + Title = null, + }.Save() + : Database.Query().SingleEx(a => a.Id == PrimaryKey.Parse(sessionID, typeof(ChatSessionEntity))); } ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) @@ -449,7 +199,7 @@ ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) var rootSkill = AgentSkillLogic.GetRootForUseCase(AgentUseCase.DefaultChatbot) ?? throw new InvalidOperationException("No active AgentSkillEntity with UseCase = DefaultChatbot"); - var history = new ConversationHistory + return new ConversationHistory { Session = session.ToLite(), SessionTitle = session.Title, @@ -465,20 +215,95 @@ ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) }.Save() } }; + } +} - return history; +// ─── HttpAgentOutput ────────────────────────────────────────────────────────── + +/// Implements IAgentOutput by streaming UINotification events to the HTTP response. +public class HttpAgentOutput : IAgentOutput +{ + readonly HttpResponse _resp; + + public HttpAgentOutput(HttpResponse resp) => _resp = resp; + + public string Notification(ChatbotUICommand cmd, string? payload = null) + { + if (payload == null) + return "$!" + cmd + "\n"; + + if (payload.Contains('\n')) + throw new InvalidOperationException("Payload has newlines!"); + + return "$!" + cmd + ":" + payload + "\n"; + } + + public async Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct) + { + await _resp.WriteAsync(Notification(ChatbotUICommand.System), ct); + await _resp.WriteAsync(msg.Content!, ct); + await _resp.WriteAsync("\n", ct); + await _resp.WriteAsync(Notification(ChatbotUICommand.MessageId, msg.Id.ToString()), ct); + } + + public async Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct) + { + await _resp.WriteAsync(Notification(ChatbotUICommand.QuestionId, msg.Id.ToString()), ct); + await _resp.Body.FlushAsync(ct); + } + + public async Task OnSummarizationAsync(ChatMessageEntity msg, CancellationToken ct) + { + await _resp.WriteAsync(Notification(ChatbotUICommand.System), ct); + await _resp.WriteAsync(msg.Content!, ct); + await _resp.WriteAsync("\n", ct); + await _resp.WriteAsync(Notification(ChatbotUICommand.MessageId, msg.Id.ToString()), ct); } - ChatMessageEntity NewChatMessage(Lite session, string message, ChatMessageRole role) + public async Task OnAssistantStartedAsync(CancellationToken ct) { - var command = new ChatMessageEntity() + await _resp.WriteAsync(Notification(ChatbotUICommand.AssistantAnswer), ct); + } + + public async Task OnTextChunkAsync(string chunk, CancellationToken ct) + { + await _resp.WriteAsync(chunk, ct); + await _resp.Body.FlushAsync(ct); + } + + public async Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct) + { + foreach (var item in msg.ToolCalls) { - ChatSession = session, - Role = role, - Content = message, - }; + await _resp.WriteAsync("\n", ct); + var cmd = item.IsUITool ? ChatbotUICommand.AssistantUITool : ChatbotUICommand.AssistantTool; + await _resp.WriteAsync(Notification(cmd, item.ToolId + "/" + item.CallId), ct); + await _resp.WriteAsync(item.Arguments, ct); + } + await _resp.WriteAsync("\n", ct); + await _resp.WriteAsync(Notification(ChatbotUICommand.MessageId, msg.Id.ToString()), ct); + await _resp.Body.FlushAsync(ct); + } - return command; + public async Task OnToolStartAsync(string toolId, string callId, CancellationToken ct) + { + await _resp.WriteAsync(Notification(ChatbotUICommand.Tool, toolId + "/" + callId), ct); + } + + public async Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct) + { + if (toolMsg.Exception != null) + await _resp.WriteAsync(Notification(ChatbotUICommand.Exception, toolMsg.Exception.Id.ToString()), ct); + + await _resp.WriteAsync(toolMsg.Content!, ct); + await _resp.WriteAsync("\n", ct); + await _resp.WriteAsync(Notification(ChatbotUICommand.MessageId, toolMsg.Id.ToString()), ct); + await _resp.Body.FlushAsync(ct); + } + + public async Task OnTitleUpdatedAsync(string title, CancellationToken ct) + { + await _resp.WriteAsync(Notification(ChatbotUICommand.SessionTitle, title), ct); } } diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index bebb9282ee..2a876bea5b 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -342,11 +342,321 @@ public static ChatOptions ChatOptions(ChatbotLanguageModelEntity languageModel, return opts; } + + // ─── Headless / extracted loop ──────────────────────────────────────────── + + /// + /// Runs the full agent loop on an already-initialised ConversationHistory. + /// history.Messages must already contain the system message and any initial + /// user / tool messages to resume from. + /// Stops when the LLM produces a response with no tool calls, or when a + /// UITool is invoked (caller must resume via a new request). + /// + public static async Task RunAgentLoopAsync(ConversationHistory history, IAgentOutput output, CancellationToken ct) + { + var client = GetChatClient(history.LanguageModel); + + while (true) + { + // Context-window management: summarise when close to the token limit + if (history.LanguageModel.MaxTokens != null && + history.Messages.Skip(1).LastOrDefault()?.InputTokens > history.LanguageModel.MaxTokens * 0.8) + { + var systemMsg = history.Messages.FirstEx(); + if (systemMsg.Role != ChatMessageRole.System) + throw new InvalidOperationException("First message is expected to be system"); + + var normalMessages = history.Messages.Skip(1).ToList(); + var toKeepIndex = normalMessages.FindLastIndex(a => a.InputTokens < history.LanguageModel.MaxTokens * 0.5) + .NotFoundToNull() ?? (normalMessages.Count - 1); + + var toSumarize = normalMessages.Take(toKeepIndex).ToList(); + var toKeep = normalMessages.Skip(toKeepIndex).ToList(); + + var summaryContent = await SumarizeConversation(toSumarize, history.LanguageModel, ct); + + var summary = new ChatMessageEntity + { + ChatSession = history.Session, + Role = ChatMessageRole.System, + Content = $"## Summary of earlier conversation\n{summaryContent}\n\n---\nRecent messages follow:", + }.Save(); + + await output.OnSummarizationAsync(summary, ct); + history.Messages = [systemMsg, summary, .. toKeep]; + } + + var tools = history.GetTools(); + var options = ChatOptions(history.LanguageModel, tools); + var messages = history.GetMessages(); + GetProvider(history.LanguageModel).CustomizeMessagesAndOptions(messages, options); + + List updates = []; + var sw = Stopwatch.StartNew(); + bool assistantStarted = false; + + await foreach (var update in client.GetStreamingResponseAsync(messages, options, ct)) + { + if (!assistantStarted) + { + await output.OnAssistantStartedAsync(ct); + assistantStarted = true; + } + updates.Add(update); + if (update.Text.HasText()) + await output.OnTextChunkAsync(update.Text, ct); + } + sw.Stop(); + + var response = updates.ToChatResponse(); + var responseMsg = response.Messages.SingleEx(); + + var notSupported = responseMsg.Contents + .Where(a => a is not FunctionCallContent and not Microsoft.Extensions.AI.TextContent) + .ToList(); + if (notSupported.Any()) + throw new InvalidOperationException("Unexpected response: " + notSupported.ToString(a => a.GetType().Name, ", ")); + + var usage = response.Usage; + var toolCalls = responseMsg.Contents.OfType().ToList(); + + var uiToolCalls = toolCalls.Where(fc => + { + var tool = history.RootSkill?.FindTool(fc.Name) + ?? throw new InvalidOperationException($"Tool '{fc.Name}' not found"); + return ((AIFunction)tool).UnderlyingMethod?.GetCustomAttribute() != null; + }).ToList(); + + if (uiToolCalls.Count > 1) + throw new InvalidOperationException( + $"The LLM invoked more than one UITool in a single response ({string.Join(", ", uiToolCalls.Select(t => t.Name))}). Only one UITool can be active at a time."); + + var answer = new ChatMessageEntity + { + ChatSession = history.Session, + Role = ChatMessageRole.Assistant, + Content = responseMsg.Text, + LanguageModel = history.Session.InDB(s => s.LanguageModel), + InputTokens = (int?)usage?.InputTokenCount, + CachedInputTokens = (int?)usage?.CachedInputTokenCount, + OutputTokens = (int?)usage?.OutputTokenCount, + ReasoningOutputTokens = (int?)usage?.ReasoningTokenCount, + Duration = sw.Elapsed, + ToolCalls = toolCalls.Select(fc => new ToolCallEmbedded + { + ToolId = fc.Name, + CallId = fc.CallId, + Arguments = JsonSerializer.Serialize(fc.Arguments), + IsUITool = uiToolCalls.Any(u => u.CallId == fc.CallId), + }).ToMList() + }.Save(); + + Expression> NullableAdd = (a, b) => + a == null && b == null ? null : (a ?? 0) + (b ?? 0); + + history.Session.InDB().UnsafeUpdate() + .Set(a => a.TotalInputTokens, a => NullableAdd.Evaluate(a.TotalInputTokens, answer.InputTokens)) + .Set(a => a.TotalCachedInputTokens, a => NullableAdd.Evaluate(a.TotalCachedInputTokens, answer.CachedInputTokens)) + .Set(a => a.TotalOutputTokens, a => NullableAdd.Evaluate(a.TotalOutputTokens, answer.OutputTokens)) + .Set(a => a.TotalReasoningOutputTokens, a => NullableAdd.Evaluate(a.TotalReasoningOutputTokens, answer.ReasoningOutputTokens)) + .Set(a => a.TotalToolCalls, a => a.TotalToolCalls + answer.ToolCalls.Count) + .Execute(); + + await output.OnAssistantMessageAsync(answer, ct); + history.Messages.Add(answer); + + if (toolCalls.IsEmpty() || uiToolCalls.Any()) + break; + + foreach (var funCall in toolCalls) + await ExecuteToolAsync(history, funCall.Name, funCall.CallId, funCall.Arguments!, output, ct); + } + + // Title summarisation (runs regardless of UITool pause or normal completion) + if (history.SessionTitle == null || history.SessionTitle.StartsWith("!*$")) + { + history.SessionTitle = history.Session.InDB(a => a.Title); + if (history.SessionTitle == null || history.SessionTitle.StartsWith("!*$")) + { + string title = await SumarizeTitle(history, ct); + if (title.HasText() && title.ToLower() != "pending") + { + history.Session.InDB().UnsafeUpdate(a => a.Title, a => title); + history.SessionTitle = title; + await output.OnTitleUpdatedAsync(title, ct); + } + } + } + } + + public static async Task ExecuteToolAsync( + ConversationHistory history, + string toolId, string callId, + IDictionary arguments, + IAgentOutput output, + CancellationToken ct) + { + await output.OnToolStartAsync(toolId, callId, ct); + var toolSw = Stopwatch.StartNew(); + try + { + AITool tool = history.RootSkill?.FindTool(toolId) + ?? throw new InvalidOperationException($"Tool '{toolId}' not found"); + var obj = await ((AIFunction)tool).InvokeAsync(new AIFunctionArguments(arguments), ct); + toolSw.Stop(); + + var toolMsg = new ChatMessageEntity + { + ChatSession = history.Session, + Role = ChatMessageRole.Tool, + ToolCallID = callId, + ToolID = toolId, + Content = JsonSerializer.Serialize(obj), + Duration = toolSw.Elapsed, + }.Save(); + + await output.OnToolFinishedAsync(toolMsg, ct); + history.Messages.Add(toolMsg); + } + catch (Exception e) + { + toolSw.Stop(); + var errorContent = FormatToolError(toolId, e, arguments); + ChatMessageEntity toolMsg; + using (AuthLogic.Disable()) + { + toolMsg = new ChatMessageEntity + { + ChatSession = history.Session, + Role = ChatMessageRole.Tool, + ToolCallID = callId, + ToolID = toolId, + Content = errorContent, + Exception = e.LogException().ToLiteFat(), + Duration = toolSw.Elapsed, + }.Save(); + } + await output.OnToolFinishedAsync(toolMsg, ct); + history.Messages.Add(toolMsg); + } + } + + public static string FormatToolError(string toolName, Exception e, IDictionary? arguments) + { + var sb = new StringBuilder(); + sb.AppendLine($"Tool '{toolName}' failed."); + if (arguments != null && arguments.Count > 0) + sb.AppendLine($"Arguments: {JsonSerializer.Serialize(arguments).Etc(300)}"); + sb.AppendLine($"Error: {e.GetType().Name}: {e.Message}"); + if (e.Data["Hint"] is string s) + sb.AppendLine($"Hint: {s}"); + sb.AppendLine("Please review the error and try again with corrected arguments."); + return sb.ToString(); + } + + /// + /// Creates a new session and runs the agent loop headlessly (no HTTP connection). + /// The caller is responsible for setting up the authentication context if needed. + /// + public static async Task RunHeadlessAsync( + string prompt, + AgentUseCaseSymbol? useCase = null, + Lite? languageModel = null, + IAgentOutput? output = null, + CancellationToken ct = default) + { + useCase ??= AgentUseCase.DefaultChatbot; + output ??= NullAgentOutput.Instance; + + var modelLite = languageModel ?? DefaultLanguageModel.Value + ?? throw new InvalidOperationException($"No default {nameof(ChatbotLanguageModelEntity)} configured."); + + var rootSkill = AgentSkillLogic.GetRootForUseCase(useCase) + ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}."); + + var session = new ChatSessionEntity + { + LanguageModel = modelLite, + User = UserEntity.Current, + StartDate = Clock.Now, + }.Save(); + + var systemMsg = new ChatMessageEntity + { + Role = ChatMessageRole.System, + ChatSession = session.ToLite(), + Content = rootSkill.GetInstruction(null), + }.Save(); + + await output.OnSystemMessageAsync(systemMsg, ct); + + var userMsg = new ChatMessageEntity + { + Role = ChatMessageRole.User, + ChatSession = session.ToLite(), + Content = prompt, + }.Save(); + + await output.OnUserQuestionAsync(userMsg, ct); + + var history = new ConversationHistory + { + Session = session.ToLite(), + LanguageModel = modelLite.RetrieveFromCache(), + RootSkill = rootSkill, + Messages = [systemMsg, userMsg], + }; + + await RunAgentLoopAsync(history, output, ct); + return history; + } } +// ─── IAgentOutput ───────────────────────────────────────────────────────────── + +/// +/// Receives events from the agent loop. All methods have no-op default implementations +/// so implementations only override what they care about. +/// +public interface IAgentOutput +{ + /// Called when the initial system message is saved (new session). + Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + + /// Called when a user question message is saved. + Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + + /// Called when the context-window summary message is saved. + Task OnSummarizationAsync(ChatMessageEntity summaryMsg, CancellationToken ct) => Task.CompletedTask; + + /// Called once at the start of each assistant response, before any text chunks. + Task OnAssistantStartedAsync(CancellationToken ct) => Task.CompletedTask; + + /// Called for each streaming text chunk from the LLM. + Task OnTextChunkAsync(string chunk, CancellationToken ct) => Task.CompletedTask; + + /// Called when the assistant message entity is fully saved (includes tool call metadata). + Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + + /// Called just before a tool is invoked. + Task OnToolStartAsync(string toolId, string callId, CancellationToken ct) => Task.CompletedTask; + + /// Called after a tool completes. Check toolMsg.Exception for errors. + Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct) => Task.CompletedTask; + + /// Called when the session title is determined or updated. + Task OnTitleUpdatedAsync(string title, CancellationToken ct) => Task.CompletedTask; +} + +public sealed class NullAgentOutput : IAgentOutput +{ + public static readonly NullAgentOutput Instance = new(); + private NullAgentOutput() { } +} + public interface IChatbotModelProvider { Task> GetModelNames(CancellationToken ct); From 318c45165c9180c400327fdae5f2044efd5c861f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 16:12:59 +0000 Subject: [PATCH 05/21] Redesign AgentSkill: DB-backed skill tree with AgentSkillCodeEntity sync - AgentSkillCodeEntity is now a SystemString entity auto-synced from code via Schema_Generating/Schema_Synchronizing hooks (like EmailModelEntity) - RegisterCode() registers types only; instantiation via Activator.CreateInstance - Merged SkillCodeDefaults+properties into single SkillCodeInfo response - AgentSkillPropertyAttribute gains ValidateValue() called during entity save - Removed ResolvedSkillNode: AgentSkillCode instances are built in-place at resolve time with SubSkills wired from DB entity tree - ConversationHistory.RootSkill: ResolvedSkillNode -> AgentSkillCode - Controller: replaced 3 endpoints with single /api/agentSkill/skillCodeInfo/{code} - Frontend: EntityCombo for skillCode field, single getSkillCodeInfo API call, removed duplicate AgentSkillEntity settings registration from ChatbotClient https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillClient.tsx | 14 +- Extensions/Signum.Agent/AgentSkillEntity.cs | 44 +- Extensions/Signum.Agent/AgentSkillLogic.cs | 395 +++++++++--------- Extensions/Signum.Agent/ChatbotClient.tsx | 3 +- Extensions/Signum.Agent/ChatbotController.cs | 14 +- Extensions/Signum.Agent/ChatbotLogic.cs | 4 +- Extensions/Signum.Agent/Signum.Agent.d.ts | 12 +- .../Signum.Agent/Templates/AgentSkill.tsx | 39 +- 8 files changed, 275 insertions(+), 250 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillClient.tsx b/Extensions/Signum.Agent/AgentSkillClient.tsx index 10a67ad7ef..7fd04d353b 100644 --- a/Extensions/Signum.Agent/AgentSkillClient.tsx +++ b/Extensions/Signum.Agent/AgentSkillClient.tsx @@ -3,7 +3,7 @@ import { ajaxGet } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator'; import * as AppContext from '@framework/AppContext'; import { TypeContext } from '@framework/TypeContext'; -import { AgentSkillEntity, SkillPropertyMeta, SkillCodeDefaults } from './Signum.Agent'; +import { AgentSkillEntity, SkillPropertyMeta, SkillCodeInfo } from './Signum.Agent'; export namespace AgentSkillClient { @@ -40,16 +40,8 @@ export namespace AgentSkillClient { // ─── API ────────────────────────────────────────────────────────────────── export namespace API { - export function getSkillCodeDefaults(skillCode: string): Promise { - return ajaxGet({ url: `/api/agentSkill/skillCodeDefaults/${encodeURIComponent(skillCode)}` }); - } - - export function getSkillCodeProperties(skillCode: string): Promise { - return ajaxGet({ url: `/api/agentSkill/skillCodeProperties/${encodeURIComponent(skillCode)}` }); - } - - export function getRegisteredCodes(): Promise { - return ajaxGet({ url: '/api/agentSkill/registeredCodes' }); + export function getSkillCodeInfo(skillCode: string): Promise { + return ajaxGet({ url: `/api/agentSkill/skillCodeInfo/${encodeURIComponent(skillCode)}` }); } } } diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs index 16d098f7f4..c9c57ba39d 100644 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -1,5 +1,23 @@ namespace Signum.Agent; +// ─── AgentSkillCodeEntity ────────────────────────────────────────────────────── + +/// +/// Catalog of registered AgentSkillCode types. Rows are auto-generated/synchronized +/// from code (same pattern as EmailModelEntity) — never created manually. +/// +[EntityKind(EntityKind.SystemString, EntityData.Master), TicksColumn(false)] +public class AgentSkillCodeEntity : Entity +{ + [UniqueIndex] + public string FullClassName { get; set; } + + [AutoExpressionField] + public override string ToString() => As.Expression(() => FullClassName.AfterLast('.')); +} + +// ─── AgentUseCaseSymbol ──────────────────────────────────────────────────────── + public class AgentUseCaseSymbol : Symbol { private AgentUseCaseSymbol() { } @@ -12,6 +30,8 @@ public static class AgentUseCase public static AgentUseCaseSymbol Summarizer = null!; } +// ─── AgentSkillEntity ───────────────────────────────────────────────────────── + [EntityKind(EntityKind.Main, EntityData.Master)] public class AgentSkillEntity : Entity { @@ -19,8 +39,7 @@ public class AgentSkillEntity : Entity [StringLengthValidator(Min = 1, Max = 200)] public string Name { get; set; } - [StringLengthValidator(Min = 1, Max = 200)] - public string SkillCode { get; set; } + public AgentSkillCodeEntity SkillCode { get; set; } public bool Active { get; set; } = true; @@ -41,18 +60,25 @@ public class AgentSkillEntity : Entity [AutoExpressionField] public override string ToString() => As.Expression(() => Name); - protected override string? PropertyValidation(PropertyInfo pi) + protected override string? ChildPropertyValidation(ModifiableEntity sender, PropertyInfo pi) { - if (pi.Name == nameof(SkillCode) && SkillCode.HasText() && AgentSkillLogic.RegisteredCodes.Any()) + if (sender is AgentSkillPropertyOverrideEmbedded po + && pi.Name == nameof(AgentSkillPropertyOverrideEmbedded.Value) + && SkillCode != null + && AgentSkillLogic.RegisteredCodes.TryGetValue(SkillCode.FullClassName, out var codeType)) { - if (!AgentSkillLogic.RegisteredCodes.ContainsKey(SkillCode)) - return $"SkillCode '{SkillCode}' is not registered. Available: {AgentSkillLogic.RegisteredCodes.Keys.ToString(", ")}"; + var propInfo = codeType.GetProperty(po.PropertyName, BindingFlags.Public | BindingFlags.Instance); + var attr = propInfo?.GetCustomAttribute(); + if (attr != null) + return attr.ValidateValue(po.Value, propInfo!.PropertyType); } - return base.PropertyValidation(pi); + return base.ChildPropertyValidation(sender, pi); } } +// ─── AgentSkillPropertyOverrideEmbedded ─────────────────────────────────────── + public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity { [StringLengthValidator(Min = 1, Max = 200)] @@ -62,6 +88,8 @@ public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity public string? Value { get; set; } } +// ─── AgentSkillSubSkillEmbedded ─────────────────────────────────────────────── + public class AgentSkillSubSkillEmbedded : EmbeddedEntity { public Lite Skill { get; set; } @@ -69,6 +97,8 @@ public class AgentSkillSubSkillEmbedded : EmbeddedEntity public SkillActivation Activation { get; set; } } +// ─── AgentSkillOperation ────────────────────────────────────────────────────── + [AutoInit] public static class AgentSkillOperation { diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 9bd22c7299..1a913ee773 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -16,45 +16,42 @@ namespace Signum.Agent; public static class AgentSkillLogic { public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); - internal static readonly AsyncThreadVariable CurrentMcpRoot = Statics.ThreadVariable("CurrentMcpRoot"); + internal static readonly AsyncThreadVariable CurrentMcpRoot = Statics.ThreadVariable("CurrentMcpRoot"); - // Registered code definitions: key = AgentSkillCode.Name (e.g. "Search", "Retrieve") - public static Dictionary? Factory)> RegisteredCodes = new(); + /// Key = FullClassName (e.g. "Signum.Agent.Skills.SearchSkill"), Value = Type. + public static Dictionary RegisteredCodes = new(); - // Legacy instances kept for ChatbotLogic internal use (summarization) + // Kept as convenient instances for internal use (e.g. ChatbotLogic summarisation). public static ConversationSumarizerSkill ConversationSumarizerSkill = null!; public static QuestionSumarizerSkill QuestionSumarizerSkill = null!; - public static ResetLazy> RootsByUseCase = null!; + public static ResetLazy> RootsByUseCase = null!; - /// Registers an AgentSkillCode singleton (no cloning on entity resolve). - public static T RegisterCode(T instance) where T : AgentSkillCode + /// Registers an AgentSkillCode type. The class must have a parameterless constructor. + public static void RegisterCode() where T : AgentSkillCode { - RegisteredCodes[instance.Name] = (instance, null); - return instance; + RegisteredCodes[typeof(T).FullName!] = typeof(T); } - /// Registers an AgentSkillCode with a factory so each entity gets a fresh instance with property overrides applied. - public static T RegisterCode(Func factory) where T : AgentSkillCode - { - var defaultInstance = factory(); - RegisteredCodes[defaultInstance.Name] = (defaultInstance, () => factory()); - return defaultInstance; - } - - public static void Start(SchemaBuilder sb, AgentSkillCode? introductionSkill = null) + public static void Start(SchemaBuilder sb) { if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) return; - ConversationSumarizerSkill = RegisterCode(new ConversationSumarizerSkill()); - QuestionSumarizerSkill = RegisterCode(new QuestionSumarizerSkill()); + RegisterCode(); + RegisterCode(); - if (introductionSkill != null) - RegisterCode(introductionSkill); + ConversationSumarizerSkill = new ConversationSumarizerSkill(); + QuestionSumarizerSkill = new QuestionSumarizerSkill(); SymbolLogic.Start(sb, () => [AgentUseCase.DefaultChatbot, AgentUseCase.Summarizer]); + sb.Include() + .WithQuery(() => e => new { Entity = e, e.Id, e.FullClassName }); + + sb.Schema.Generating += Schema_Generating; + sb.Schema.Synchronizing += Schema_Synchronizing; + sb.Include() .WithSave(AgentSkillOperation.Save) .WithDelete(AgentSkillOperation.Delete) @@ -86,11 +83,64 @@ public static void Start(SchemaBuilder sb, AgentSkillCode? introductionSkill = n .GroupBy(e => e.UseCase!) .ToDictionary( g => g.Key, - g => ResolvedSkillNode.Resolve(g.SingleEx(), allEntities) + g => ResolveCode(g.SingleEx(), allEntities) ); }, new InvalidateWith(typeof(AgentSkillEntity))); } + // ─── AgentSkillCodeEntity sync ──────────────────────────────────────────── + + static SqlPreCommand? Schema_Generating() + { + var table = Schema.Current.Table(); + return GenerateCodeEntities() + .Select(e => table.InsertSqlSync(e)) + .Combine(Spacing.Simple); + } + + static SqlPreCommand? Schema_Synchronizing(Replacements replacements) + { + var table = Schema.Current.Table(); + var should = GenerateCodeEntities().ToDictionary(e => e.FullClassName); + var current = Administrator.TryRetrieveAll(replacements) + .ToDictionary(e => e.FullClassName); + + return Synchronizer.SynchronizeScript(Spacing.Double, should, current, + createNew: (_, s) => table.InsertSqlSync(s), + removeOld: (_, c) => table.DeleteSqlSync(c, e => e.FullClassName == c.FullClassName), + mergeBoth: (_, s, c) => table.UpdateSqlSync(c, e => e.FullClassName == c.FullClassName)); + } + + static List GenerateCodeEntities() => + RegisteredCodes.Keys.Select(fc => new AgentSkillCodeEntity { FullClassName = fc }).ToList(); + + // ─── Resolve ────────────────────────────────────────────────────────────── + + public static AgentSkillCode ResolveCode( + AgentSkillEntity entity, + Dictionary, AgentSkillEntity> allEntities) + { + var type = RegisteredCodes.GetOrThrow(entity.SkillCode.FullClassName, + $"AgentSkillCode type '{entity.SkillCode.FullClassName}' is not registered."); + + var code = (AgentSkillCode)Activator.CreateInstance(type)!; + code.InstanceName = entity.Name; + + if (entity.ShortDescription != null) + code.ShortDescription = entity.ShortDescription; + if (entity.Instructions != null) + code.OriginalInstructions = entity.Instructions; + + code.ApplyPropertyOverrides(entity); + + foreach (var ss in entity.SubSkills) + code.SubSkills.Add((ResolveCode(allEntities.GetOrThrow(ss.Skill), allEntities), ss.Activation)); + + return code; + } + + // ─── Circular reference validation ──────────────────────────────────────── + static void ValidateNoCircularReferences(AgentSkillEntity entity) { using (new EntityCache(EntityCacheType.ForceNew)) @@ -113,16 +163,19 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) } } - public static ResolvedSkillNode? GetRootForUseCase(AgentUseCaseSymbol symbol) => + // ─── Public API ─────────────────────────────────────────────────────────── + + public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => RootsByUseCase.Value.TryGetC(symbol); - /// Returns skill code metadata (properties marked [AgentSkillProperty]) for the frontend. - public static List GetSkillCodeProperties(string skillCode) + public static SkillCodeInfo GetSkillCodeInfo(string fullClassName) { - if (!RegisteredCodes.TryGetValue(skillCode, out var entry)) - return new List(); + if (!RegisteredCodes.TryGetValue(fullClassName, out var type)) + throw new KeyNotFoundException($"AgentSkillCode type '{fullClassName}' is not registered."); + + var instance = (AgentSkillCode)Activator.CreateInstance(type)!; - return entry.Default.GetType() + var properties = type .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(pi => new { pi, attr = pi.GetCustomAttribute() }) .Where(x => x.attr != null) @@ -134,22 +187,25 @@ public static List GetSkillCodeProperties(string skillCode) PropertyType = x.pi.PropertyType.TypeName(), }) .ToList(); - } - - /// Returns the default short description and instructions for a registered SkillCode. - public static SkillCodeDefaults GetSkillCodeDefaults(string skillCode) - { - if (!RegisteredCodes.TryGetValue(skillCode, out var entry)) - throw new KeyNotFoundException($"SkillCode '{skillCode}' is not registered."); - return new SkillCodeDefaults + return new SkillCodeInfo { - DefaultShortDescription = entry.Default.ShortDescription, - DefaultInstructions = entry.Default.OriginalInstructions, + DefaultShortDescription = instance.ShortDescription, + DefaultInstructions = instance.OriginalInstructions, + Properties = properties, }; } } +// ─── SkillCodeInfo ──────────────────────────────────────────────────────────── + +public class SkillCodeInfo +{ + public string DefaultShortDescription { get; set; } = null!; + public string DefaultInstructions { get; set; } = null!; + public List Properties { get; set; } = null!; +} + public class SkillPropertyMeta { public string PropertyName { get; set; } = null!; @@ -158,18 +214,11 @@ public class SkillPropertyMeta public string PropertyType { get; set; } = null!; } -public class SkillCodeDefaults -{ - public string DefaultShortDescription { get; set; } = null!; - public string DefaultInstructions { get; set; } = null!; -} - -// ─── AgentSkillPropertyAttribute ────────────────────────────────────────────── +// ─── AgentSkillPropertyAttribute ───────────────────────────────────────────── [AttributeUsage(AttributeTargets.Property)] public class AgentSkillPropertyAttribute : Attribute { - /// Converts a string stored in the DB to the typed property value. Default uses ReflectionTools.Convert. public virtual object? ConvertFromString(string? value, Type targetType) { if (value == null) @@ -178,13 +227,14 @@ public class AgentSkillPropertyAttribute : Attribute return ReflectionTools.Convert(value, targetType); } + public virtual string? ValidateValue(string? value, Type targetType) => null; + public virtual string? ValueHint => null; } [AttributeUsage(AttributeTargets.Property)] public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute { - /// Splits a comma-separated list of query keys and converts each via QueryLogic.ToQueryName. public override object? ConvertFromString(string? value, Type targetType) { if (value == null) @@ -196,6 +246,21 @@ public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute .ToHashSet(); } + public override string? ValidateValue(string? value, Type targetType) + { + if (value == null) + return null; + + var errors = value.Split(',') + .Select(k => k.Trim()) + .Where(k => k.HasText() && QueryLogic.ToQueryNameCatch(k) == null) + .ToList(); + + return errors.Any() + ? $"Unknown query key(s): {errors.ToString(", ")}" + : null; + } + public override string? ValueHint => "Comma-separated query keys"; } @@ -203,25 +268,49 @@ public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute public abstract class AgentSkillCode { - public string Name => this.GetType().Name.Before("Skill"); + /// Set from AgentSkillEntity.Name when resolved from DB; falls back to the class-derived short name. + public string? InstanceName { get; internal set; } + public string Name => InstanceName ?? this.GetType().Name.Before("Skill"); + public string ShortDescription { get; set; } = ""; public Func IsAllowed { get; set; } = () => true; public Dictionary>? Replacements; - public static string SkillsDirectory = Path.Combine(Path.GetDirectoryName(typeof(AgentSkillCode).Assembly.Location)!, "Skills"); + public static string SkillsDirectory = Path.Combine( + Path.GetDirectoryName(typeof(AgentSkillCode).Assembly.Location)!, "Skills"); string? originalInstructions; public string OriginalInstructions { - get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.Name + ".md")); } + get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } set { originalInstructions = value; } } + /// Runtime sub-skill tree, populated from DB entity. Never set in code. + public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); + public string GetInstruction(object? context) { var text = OriginalInstructions; if (!Replacements.IsNullOrEmpty()) text = text.Replace(Replacements.SelectDictionary(k => k, v => v(context))); + + if (SubSkills.Any()) + { + var sb = new StringBuilder(text); + foreach (var (sub, activation) in SubSkills) + { + sb.AppendLineLF("# Skill " + sub.Name); + sb.AppendLineLF("**Summary**: " + sub.ShortDescription); + sb.AppendLineLF(); + if (activation == SkillActivation.Eager) + sb.AppendLineLF(sub.GetInstruction(null)); + else + sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); + } + return sb.ToString(); + } + return text; } @@ -231,10 +320,10 @@ public void ApplyPropertyOverrides(AgentSkillEntity entity) { var pi = this.GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .FirstOrDefault(p => p.Name == po.PropertyName && p.GetCustomAttribute() != null); + .FirstOrDefault(p => p.Name == po.PropertyName + && p.GetCustomAttribute() != null); - if (pi == null) - continue; + if (pi == null) continue; var attr = pi.GetCustomAttribute()!; var value = attr.ConvertFromString(po.Value, pi.PropertyType); @@ -242,99 +331,12 @@ public void ApplyPropertyOverrides(AgentSkillEntity entity) } } - IEnumerable? cachedTools; - internal IEnumerable GetTools() - { - return (cachedTools ??= this.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Where(m => m.GetCustomAttribute() != null) - .Select(m => - { - Type delType = Expression.GetDelegateType(m.GetParameters().Select(a => a.ParameterType).And(m.ReturnType).ToArray()); - Delegate del = m.IsStatic ? - Delegate.CreateDelegate(delType, m) : - Delegate.CreateDelegate(delType, this, m); - - string? description = m.GetCustomAttribute()?.Description; - return (AITool)AIFunctionFactory.Create(del, m.Name, description, GetJsonSerializerOptions()); - }) - .ToList()); - } - - internal IEnumerable GetMcpServerTools() => - GetTools().Select(t => McpServerTool.Create((AIFunction)t, new McpServerToolCreateOptions - { - SerializerOptions = GetJsonSerializerOptions(), - })); - - static JsonSerializerOptions JsonSerializationOptions = new JsonSerializerOptions - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }.AddSignumJsonConverters(); - - public virtual JsonSerializerOptions GetJsonSerializerOptions() => JsonSerializationOptions; -} - -// ─── ResolvedSkillNode ──────────────────────────────────────────────────────── - -/// A runtime node combining an AgentSkillEntity with its resolved AgentSkillCode and sub-skill tree. -public class ResolvedSkillNode -{ - public AgentSkillEntity Entity { get; } - public AgentSkillCode Code { get; } - public List<(ResolvedSkillNode Node, SkillActivation Activation)> SubSkills { get; } - - public string Name => Entity.Name; - public string ShortDescription => Entity.ShortDescription ?? Code.ShortDescription; - - ResolvedSkillNode(AgentSkillEntity entity, AgentSkillCode code, List<(ResolvedSkillNode, SkillActivation)> subSkills) - { - Entity = entity; - Code = code; - SubSkills = subSkills; - } - - public string GetInstruction(object? context) - { - var text = Entity.Instructions ?? Code.OriginalInstructions; - if (!Code.Replacements.IsNullOrEmpty()) - text = text.Replace(Code.Replacements.SelectDictionary(k => k, v => v(context))); - - var sb = new StringBuilder(text); - foreach (var (node, activation) in SubSkills) - { - sb.AppendLineLF("# Skill " + node.Name); - sb.AppendLineLF("**Summary**: " + node.ShortDescription); - sb.AppendLineLF(); - if (activation == SkillActivation.Eager) - sb.AppendLineLF(node.GetInstruction(null)); - else - sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); - } - - return sb.ToString(); - } - - public IEnumerable GetTools() => Code.GetTools(); - - public IEnumerable GetToolsRecursive() - { - var list = GetTools().ToList(); - foreach (var (node, activation) in SubSkills) - { - if (activation == SkillActivation.Eager) - list.AddRange(node.GetToolsRecursive()); - } - return list; - } - - public ResolvedSkillNode? FindSkill(string name) + public AgentSkillCode? FindSkill(string name) { if (this.Name == name) return this; - foreach (var (node, _) in SubSkills) + foreach (var (sub, _) in SubSkills) { - var found = node.FindSkill(name); + var found = sub.FindSkill(name); if (found != null) return found; } return null; @@ -344,55 +346,73 @@ public IEnumerable GetToolsRecursive() { var tool = GetTools().FirstOrDefault(t => t.Name.Equals(toolName, StringComparison.InvariantCultureIgnoreCase)); if (tool != null) return tool; - foreach (var (node, _) in SubSkills) + foreach (var (sub, _) in SubSkills) { - var found = node.FindTool(toolName); + var found = sub.FindTool(toolName); if (found != null) return found; } return null; } - public IEnumerable GetSkillsRecursive() + public IEnumerable GetSkillsRecursive() { yield return this; - foreach (var (node, _) in SubSkills) - foreach (var s in node.GetSkillsRecursive()) + foreach (var (sub, _) in SubSkills) + foreach (var s in sub.GetSkillsRecursive()) yield return s; } - public IEnumerable GetEagerSkillsRecursive() + public IEnumerable GetEagerSkillsRecursive() { yield return this; - foreach (var (node, activation) in SubSkills) - { + foreach (var (sub, activation) in SubSkills) if (activation == SkillActivation.Eager) - foreach (var s in node.GetEagerSkillsRecursive()) + foreach (var s in sub.GetEagerSkillsRecursive()) yield return s; - } } - public static ResolvedSkillNode Resolve(AgentSkillEntity entity, Dictionary, AgentSkillEntity> allEntities) + public IEnumerable GetToolsRecursive() + { + var list = GetTools().ToList(); + foreach (var (sub, activation) in SubSkills) + if (activation == SkillActivation.Eager) + list.AddRange(sub.GetToolsRecursive()); + return list; + } + + IEnumerable? cachedTools; + internal IEnumerable GetTools() { - var (defaultInstance, factory) = AgentSkillLogic.RegisteredCodes.GetOrThrow(entity.SkillCode, - $"SkillCode '{entity.SkillCode}' not registered. Entity: '{entity.Name}'"); + return (cachedTools ??= this.GetType() + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Where(m => m.GetCustomAttribute() != null) + .Select(m => + { + Type delType = Expression.GetDelegateType( + m.GetParameters().Select(a => a.ParameterType).And(m.ReturnType).ToArray()); + Delegate del = m.IsStatic + ? Delegate.CreateDelegate(delType, m) + : Delegate.CreateDelegate(delType, this, m); + string? description = m.GetCustomAttribute()?.Description; + return (AITool)AIFunctionFactory.Create(del, m.Name, description, GetJsonSerializerOptions()); + }) + .ToList()); + } - AgentSkillCode code; - if (factory != null && entity.PropertyOverrides.Any()) - { - code = factory(); - code.ApplyPropertyOverrides(entity); - } - else + internal IEnumerable GetMcpServerTools() => + GetTools().Select(t => McpServerTool.Create((AIFunction)t, new McpServerToolCreateOptions { - code = defaultInstance; - } + SerializerOptions = GetJsonSerializerOptions(), + })); - var subSkills = entity.SubSkills - .Select(ss => (Resolve(allEntities.GetOrThrow(ss.Skill), allEntities), ss.Activation)) - .ToList(); + static JsonSerializerOptions JsonSerializationOptions = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }.AddSignumJsonConverters(); - return new ResolvedSkillNode(entity, code, subSkills); - } + public virtual JsonSerializerOptions GetJsonSerializerOptions() => JsonSerializationOptions; } public enum SkillActivation @@ -405,8 +425,7 @@ public enum SkillActivation /// /// Marks a [McpServerTool] method as a UI tool: the server never invokes its body. -/// Instead the controller detects this attribute before calling InvokeAsync and routes -/// the call to the client via the $!AssistantUITool streaming command. +/// The controller routes the call to the client via the $!AssistantUITool streaming command. /// The method body must throw InvalidOperationException("This method should not be called on the server"). /// [AttributeUsage(AttributeTargets.Method)] @@ -422,7 +441,7 @@ public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, var sessionActivated = new ConcurrentDictionary>(); - ResolvedSkillNode GetRoot() => + AgentSkillCode GetRoot() => AgentSkillLogic.GetRootForUseCase(useCase) ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}"); @@ -434,16 +453,6 @@ IEnumerable GetActivated(string? sessionId) => ? s : InitialActivated(); - McpServerTool? FindMcpTool(string toolName, IEnumerable activatedNames) - { - var root = GetRoot(); - return activatedNames - .Select(name => root.FindSkill(name)) - .OfType() - .SelectMany(n => n.Code.GetMcpServerTools()) - .FirstOrDefault(t => t.ProtocolTool.Name == toolName); - } - return builder .WithHttpTransport(options => { @@ -463,12 +472,12 @@ IEnumerable GetActivated(string? sessionId) => }) .WithListToolsHandler(async (ctx, ct) => { - var activated = GetActivated(ctx.Server.SessionId); var root = GetRoot(); + var activated = GetActivated(ctx.Server.SessionId); var tools = activated .Select(name => root.FindSkill(name)) - .OfType() - .SelectMany(n => n.Code.GetMcpServerTools()) + .OfType() + .SelectMany(s => s.GetMcpServerTools()) .Select(t => t.ProtocolTool) .ToList(); @@ -477,22 +486,26 @@ IEnumerable GetActivated(string? sessionId) => .WithCallToolHandler(async (ctx, ct) => { var toolName = ctx.Params!.Name; + var root = GetRoot(); var activated = GetActivated(ctx.Server.SessionId); - var tool = FindMcpTool(toolName, activated) + + var tool = activated + .Select(name => root.FindSkill(name)) + .OfType() + .SelectMany(s => s.GetMcpServerTools()) + .FirstOrDefault(t => t.ProtocolTool.Name == toolName) ?? throw new McpException($"Tool '{toolName}' not found"); CallToolResult result; - using (AgentSkillLogic.CurrentMcpRoot.Override(GetRoot())) + using (AgentSkillLogic.CurrentMcpRoot.Override(root)) using (AgentSkillLogic.IsMCP.Override(true)) result = await tool.InvokeAsync(ctx, ct); - // When Describe is called for a Lazy skill, activate it for this session if (toolName == nameof(IntroductionSkill.Describe) && ctx.Params.Arguments?.TryGetValue("skillName", out var je) == true && je.GetString() is { } skillName && ctx.Server.SessionId is { } sessionId) { - var root = GetRoot(); var newSkill = root.FindSkill(skillName); if (newSkill != null && sessionActivated.TryGetValue(sessionId, out var skills)) { diff --git a/Extensions/Signum.Agent/ChatbotClient.tsx b/Extensions/Signum.Agent/ChatbotClient.tsx index c9d0a9d184..6d18f6aecd 100644 --- a/Extensions/Signum.Agent/ChatbotClient.tsx +++ b/Extensions/Signum.Agent/ChatbotClient.tsx @@ -3,7 +3,7 @@ import { RouteObject } from 'react-router' import { ajaxGet, ajaxPost, wrapRequest, AjaxOptions } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' -import { ChatbotLanguageModelEntity, ChatSessionEntity, ChatMessageEntity, LanguageModelProviderSymbol, EmbeddingsLanguageModelEntity, ToolCallEmbedded, UserFeedback, AgentSkillEntity } from './Signum.Agent' +import { ChatbotLanguageModelEntity, ChatSessionEntity, ChatMessageEntity, LanguageModelProviderSymbol, EmbeddingsLanguageModelEntity, ToolCallEmbedded, UserFeedback } from './Signum.Agent' import { toAbsoluteUrl } from '../../Signum/React/AppContext'; import { Dic } from '@framework/Globals'; import { Finder } from '@framework/Finder'; @@ -17,7 +17,6 @@ export namespace ChatbotClient { //export let renderMarkdown = (markdown: string): React.JSX.Element => {markdown}; export function start(options: { routes: RouteObject[] }): void { - Navigator.addSettings(new EntitySettings(AgentSkillEntity, e => import('./Templates/AgentSkill'))); Navigator.addSettings(new EntitySettings(ChatbotLanguageModelEntity, e => import('./Templates/ChatbotLanguageModel'))); Navigator.addSettings(new EntitySettings(EmbeddingsLanguageModelEntity, e => import('./Templates/EmbeddingsLanguageModel'))); Navigator.addSettings(new EntitySettings(ChatSessionEntity, a => import('./Templates/ChatSession'))); diff --git a/Extensions/Signum.Agent/ChatbotController.cs b/Extensions/Signum.Agent/ChatbotController.cs index 6ecf3967a4..7b22b609ff 100644 --- a/Extensions/Signum.Agent/ChatbotController.cs +++ b/Extensions/Signum.Agent/ChatbotController.cs @@ -14,17 +14,9 @@ namespace Signum.Agent; public class ChatbotController : Controller { - [HttpGet("api/agentSkill/skillCodeDefaults/{skillCode}")] - public SkillCodeDefaults GetSkillCodeDefaults(string skillCode) => - AgentSkillLogic.GetSkillCodeDefaults(skillCode); - - [HttpGet("api/agentSkill/skillCodeProperties/{skillCode}")] - public List GetSkillCodeProperties(string skillCode) => - AgentSkillLogic.GetSkillCodeProperties(skillCode); - - [HttpGet("api/agentSkill/registeredCodes")] - public List GetRegisteredCodes() => - AgentSkillLogic.RegisteredCodes.Keys.Order().ToList(); + [HttpGet("api/agentSkill/skillCodeInfo/{skillCode}")] + public SkillCodeInfo GetSkillCodeInfo(string skillCode) => + AgentSkillLogic.GetSkillCodeInfo(skillCode); [HttpGet("api/chatbot/provider/{providerKey}/models")] public async Task> GetModels(string providerKey, CancellationToken token) diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index 2a876bea5b..164ee249b5 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -683,7 +683,7 @@ public class ConversationHistory public string? SessionTitle { get; internal set; } - public ResolvedSkillNode? RootSkill { get; set; } + public AgentSkillCode? RootSkill { get; set; } public List GetMessages() { @@ -774,7 +774,7 @@ public List GetTools() return activatedSkills .Select(name => RootSkill.FindSkill(name)) - .OfType() + .OfType() .SelectMany(skill => skill.GetTools()) .ToList(); } diff --git a/Extensions/Signum.Agent/Signum.Agent.d.ts b/Extensions/Signum.Agent/Signum.Agent.d.ts index 1c118ca4de..da36726b94 100644 --- a/Extensions/Signum.Agent/Signum.Agent.d.ts +++ b/Extensions/Signum.Agent/Signum.Agent.d.ts @@ -3,6 +3,11 @@ import * as Entities from '../../Signum/React/Signum.Entities'; import * as Basics from '../../Signum/React/Signum.Basics'; import * as Operations from '../../Signum/React/Signum.Operations'; import * as Authorization from '../Signum.Authorization/Signum.Authorization'; +export declare const AgentSkillCodeEntity: Type; +export interface AgentSkillCodeEntity extends Entities.Entity { + Type: "AgentSkillCode"; + fullClassName: string; +} export declare const AgentUseCaseSymbol: Type; export interface AgentUseCaseSymbol extends Basics.Symbol { Type: "AgentUseCase"; @@ -15,7 +20,7 @@ export declare const AgentSkillEntity: Type; export interface AgentSkillEntity extends Entities.Entity { Type: "AgentSkill"; name: string; - skillCode: string; + skillCode: AgentSkillCodeEntity; active: boolean; useCase: AgentUseCaseSymbol | null; shortDescription: string | null; @@ -47,9 +52,10 @@ export interface SkillPropertyMeta { valueHint: string | null; propertyType: string; } -export interface SkillCodeDefaults { +export interface SkillCodeInfo { defaultShortDescription: string; defaultInstructions: string; + properties: SkillPropertyMeta[]; } export interface ToolCallEmbedded { _response?: ChatMessageEntity; @@ -89,7 +95,7 @@ export declare namespace ChatbotPermission { const UseChatbot: Basics.PermissionSymbol; } export declare const ChatbotUICommand: EnumType; -export type ChatbotUICommand = "System" | "SessionId" | "SessionTitle" | "QuestionId" | "AnswerId" | "AssistantAnswer" | "AssistantTool" | "Tool" | "Exception"; +export type ChatbotUICommand = "System" | "SessionId" | "SessionTitle" | "QuestionId" | "MessageId" | "AssistantAnswer" | "AssistantTool" | "AssistantUITool" | "Tool" | "Exception"; export declare const ChatMessageEntity: Type; export interface ChatMessageEntity extends Entities.Entity { Type: "ChatMessage"; diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/AgentSkill.tsx index 18fe04ef92..ced04e8177 100644 --- a/Extensions/Signum.Agent/Templates/AgentSkill.tsx +++ b/Extensions/Signum.Agent/Templates/AgentSkill.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { AutoLine, CheckboxLine, EntityCombo, EntityLine, EntityTable, EnumLine, TextBoxLine } from '@framework/Lines' +import { CheckboxLine, EntityCombo, EntityLine, EntityTable, EnumLine, TextBoxLine } from '@framework/Lines' import { TypeContext } from '@framework/TypeContext' import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded, SkillPropertyMeta } from '../Signum.Agent' import { useAPI, useForceUpdate } from '@framework/Hooks' @@ -15,24 +15,17 @@ export default function AgentSkill(p: { ctx: TypeContext }): R const skillCode = ctx.value.skillCode; - const skillCodeDefaults = useAPI( - () => skillCode ? AgentSkillClient.API.getSkillCodeDefaults(skillCode) : Promise.resolve(null), + const skillCodeInfo = useAPI( + () => skillCode ? AgentSkillClient.API.getSkillCodeInfo(skillCode.fullClassName) : Promise.resolve(null), [skillCode] ); - const skillCodeProperties = useAPI( - () => skillCode ? AgentSkillClient.API.getSkillCodeProperties(skillCode) : Promise.resolve([]), - [skillCode] - ); - - const registeredCodes = useAPI(() => AgentSkillClient.API.getRegisteredCodes(), []); - return (
e.name)} /> - e.skillCode)} readOnly={registeredCodes == null} optionItems={registeredCodes ?? []} + e.skillCode)} onChange={() => { ctx.value.shortDescription = null; ctx.value.instructions = null; @@ -44,13 +37,13 @@ export default function AgentSkill(p: { ctx: TypeContext }): R
e.shortDescription)} - helpText={skillCodeDefaults && ctx.value.shortDescription == null - ? `Default: ${skillCodeDefaults.defaultShortDescription}` + helpText={skillCodeInfo && ctx.value.shortDescription == null + ? `Default: ${skillCodeInfo.defaultShortDescription}` : undefined} />
- +
Sub-Skills @@ -60,7 +53,7 @@ export default function AgentSkill(p: { ctx: TypeContext }): R ])} />
- {skillCodeProperties && skillCodeProperties.length > 0 && ( + {skillCodeInfo && skillCodeInfo.properties.length > 0 && (
Property Overrides e.propertyOverrides)} columns={EntityTable.typedColumns([ @@ -68,13 +61,13 @@ export default function AgentSkill(p: { ctx: TypeContext }): R property: e => e.propertyName, template: ectx => ( e.propertyName)} - optionItems={skillCodeProperties.map(m => m.propertyName)} + optionItems={skillCodeInfo.properties.map(m => m.propertyName)} onChange={() => { ectx.value.value = null; forceUpdate(); }} /> ) }, { property: e => e.value, - template: ectx => e.value)} properties={skillCodeProperties} propertyName={ectx.value.propertyName} /> + template: ectx => e.value)} properties={skillCodeInfo.properties} propertyName={ectx.value.propertyName} /> }, ])} />
@@ -85,7 +78,7 @@ export default function AgentSkill(p: { ctx: TypeContext }): R function InstructionsField(p: { ctx: TypeContext, - defaults: { defaultInstructions: string } | null | undefined + info: { defaultInstructions: string } | null | undefined }): React.JSX.Element { const [showDiff, setShowDiff] = React.useState(false); const ctx = p.ctx; @@ -93,7 +86,7 @@ function InstructionsField(p: { const label = ( Instructions - {p.defaults && ( + {p.info && ( setShowDiff(v => !v)}> {showDiff ? "Show Editor" : "Show Diff"} @@ -102,13 +95,13 @@ function InstructionsField(p: { ); - if (showDiff && p.defaults) { + if (showDiff && p.info) { return (
); @@ -116,7 +109,7 @@ function InstructionsField(p: { return ( e.instructions)} - helpText={p.defaults && ctx.value.instructions == null ? "Using default from code (.md file)" : undefined} + helpText={p.info && ctx.value.instructions == null ? "Using default from code (.md file)" : undefined} label={label as any} /> ); From 798d7bfa15c3ee3c3b9de2c066bdb0b8c8ec9706 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:12:08 +0000 Subject: [PATCH 06/21] Remove CurrentMcpRoot: IntroductionSkill is always the root IntroductionSkill is always the tree root, so Describe/ListSkillNames can use 'this' directly instead of a thread-local variable. https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillLogic.cs | 2 -- Extensions/Signum.Agent/Skills/IntroductionSkill.cs | 10 ++-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 1a913ee773..7fa167aec6 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -16,7 +16,6 @@ namespace Signum.Agent; public static class AgentSkillLogic { public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); - internal static readonly AsyncThreadVariable CurrentMcpRoot = Statics.ThreadVariable("CurrentMcpRoot"); /// Key = FullClassName (e.g. "Signum.Agent.Skills.SearchSkill"), Value = Type. public static Dictionary RegisteredCodes = new(); @@ -497,7 +496,6 @@ IEnumerable GetActivated(string? sessionId) => ?? throw new McpException($"Tool '{toolName}' not found"); CallToolResult result; - using (AgentSkillLogic.CurrentMcpRoot.Override(root)) using (AgentSkillLogic.IsMCP.Override(true)) result = await tool.InvokeAsync(ctx, ct); diff --git a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs index c48569aace..90fcc8e5a8 100644 --- a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs +++ b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs @@ -24,10 +24,7 @@ public string Describe(string skillName) if (skillName.Contains("error")) throw new Exception(skillName + " has an error"); - var root = AgentSkillLogic.CurrentMcpRoot.Value - ?? throw new InvalidOperationException("Describe can only be called from an MCP context"); - - var skill = root.FindSkill(skillName) + var skill = this.FindSkill(skillName) ?? throw new KeyNotFoundException($"Skill '{skillName}' not found"); return skill.GetInstruction(null); @@ -36,10 +33,7 @@ public string Describe(string skillName) [McpServerTool, Description("List available skills with a short description, start here to discover new tools.")] public Dictionary ListSkillNames() { - var root = AgentSkillLogic.CurrentMcpRoot.Value - ?? throw new InvalidOperationException("ListSkillNames can only be called from an MCP context"); - - return root.GetSkillsRecursive().ToDictionary(a => a.Name, a => a.ShortDescription); + return this.GetSkillsRecursive().ToDictionary(a => a.Name, a => a.ShortDescription); } } From dd17b91466b7b891528518450a46c372119d3ede Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Fri, 3 Apr 2026 22:17:42 +0200 Subject: [PATCH 07/21] fix some compilation issues --- Extensions/Signum.Agent/AgentSkillLogic.cs | 7 +++++-- Extensions/Signum.Agent/ChatbotLogic.cs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 1a913ee773..04cb4a4ddb 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -5,6 +5,9 @@ using ModelContextProtocol.Server; using Signum.Agent.Skills; using Signum.API; +using Signum.Engine.Sync; +using Signum.Utilities.DataStructures; +using Signum.Utilities.Reflection; using System.Collections.Concurrent; using System.ComponentModel; using System.IO; @@ -224,7 +227,7 @@ public class AgentSkillPropertyAttribute : Attribute if (value == null) return null; - return ReflectionTools.Convert(value, targetType); + return ReflectionTools.ChangeType(value, targetType); } public virtual string? ValidateValue(string? value, Type targetType) => null; @@ -253,7 +256,7 @@ public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute var errors = value.Split(',') .Select(k => k.Trim()) - .Where(k => k.HasText() && QueryLogic.ToQueryNameCatch(k) == null) + .Where(k => k.HasText() && QueryLogic.ToQueryName(k) == null) .ToList(); return errors.Any() diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index 164ee249b5..ff5776f87a 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -6,6 +6,7 @@ using Signum.Utilities.Synchronization; using System.Text.Json; using Pgvector; +using System.Diagnostics; namespace Signum.Agent; @@ -560,12 +561,11 @@ public static string FormatToolError(string toolName, Exception e, IDictionary public static async Task RunHeadlessAsync( string prompt, - AgentUseCaseSymbol? useCase = null, + AgentUseCaseSymbol useCase, Lite? languageModel = null, IAgentOutput? output = null, CancellationToken ct = default) { - useCase ??= AgentUseCase.DefaultChatbot; output ??= NullAgentOutput.Instance; var modelLite = languageModel ?? DefaultLanguageModel.Value From b0f0e8f3a6d8035c6a5150aba99debc9b077a952 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:28:50 +0000 Subject: [PATCH 08/21] Extract LanguageModelLogic/Controller; remove unnecessary comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LanguageModelLogic.cs: extracted from ChatbotLogic — entity setup for ChatbotLanguageModelEntity/EmbeddingsLanguageModelEntity, provider dictionaries, GetChatClient, ChatOptions, GetEmbeddingsAsync, lazy caches, IChatbotModelProvider/IEmbeddingsProvider interfaces - LanguageModelController.cs: extracted GET models/embeddingModels endpoints - All providers updated to call LanguageModelLogic.GetConfig() - ChatbotLogic.Start now delegates to LanguageModelLogic.Start - Removed all section-divider comments and XML doc blocks; kept only the UIToolAttribute summary and the SubSkills population hint https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillEntity.cs | 16 - Extensions/Signum.Agent/AgentSkillLogic.cs | 28 +- Extensions/Signum.Agent/ChatbotController.cs | 20 +- Extensions/Signum.Agent/ChatbotLogic.cs | 308 +----------------- .../Signum.Agent/LanguageModelController.cs | 22 ++ Extensions/Signum.Agent/LanguageModelLogic.cs | 198 +++++++++++ .../Providers/AnthropicProvider.cs | 2 +- .../Providers/DeepSeekProvider.cs | 2 +- .../Signum.Agent/Providers/GeminiProvider.cs | 2 +- .../Providers/GithubModelsProvider.cs | 2 +- .../Signum.Agent/Providers/MistralProvider.cs | 2 +- .../Signum.Agent/Providers/OllamaProvider.cs | 2 +- .../Signum.Agent/Providers/OpenAIProvider.cs | 2 +- 13 files changed, 246 insertions(+), 360 deletions(-) create mode 100644 Extensions/Signum.Agent/LanguageModelController.cs create mode 100644 Extensions/Signum.Agent/LanguageModelLogic.cs diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs index c9c57ba39d..432da029dc 100644 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -1,11 +1,5 @@ namespace Signum.Agent; -// ─── AgentSkillCodeEntity ────────────────────────────────────────────────────── - -/// -/// Catalog of registered AgentSkillCode types. Rows are auto-generated/synchronized -/// from code (same pattern as EmailModelEntity) — never created manually. -/// [EntityKind(EntityKind.SystemString, EntityData.Master), TicksColumn(false)] public class AgentSkillCodeEntity : Entity { @@ -16,8 +10,6 @@ public class AgentSkillCodeEntity : Entity public override string ToString() => As.Expression(() => FullClassName.AfterLast('.')); } -// ─── AgentUseCaseSymbol ──────────────────────────────────────────────────────── - public class AgentUseCaseSymbol : Symbol { private AgentUseCaseSymbol() { } @@ -30,8 +22,6 @@ public static class AgentUseCase public static AgentUseCaseSymbol Summarizer = null!; } -// ─── AgentSkillEntity ───────────────────────────────────────────────────────── - [EntityKind(EntityKind.Main, EntityData.Master)] public class AgentSkillEntity : Entity { @@ -77,8 +67,6 @@ public class AgentSkillEntity : Entity } } -// ─── AgentSkillPropertyOverrideEmbedded ─────────────────────────────────────── - public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity { [StringLengthValidator(Min = 1, Max = 200)] @@ -88,8 +76,6 @@ public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity public string? Value { get; set; } } -// ─── AgentSkillSubSkillEmbedded ─────────────────────────────────────────────── - public class AgentSkillSubSkillEmbedded : EmbeddedEntity { public Lite Skill { get; set; } @@ -97,8 +83,6 @@ public class AgentSkillSubSkillEmbedded : EmbeddedEntity public SkillActivation Activation { get; set; } } -// ─── AgentSkillOperation ────────────────────────────────────────────────────── - [AutoInit] public static class AgentSkillOperation { diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 7fa167aec6..0554edf5a1 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -17,16 +17,13 @@ public static class AgentSkillLogic { public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); - /// Key = FullClassName (e.g. "Signum.Agent.Skills.SearchSkill"), Value = Type. public static Dictionary RegisteredCodes = new(); - // Kept as convenient instances for internal use (e.g. ChatbotLogic summarisation). public static ConversationSumarizerSkill ConversationSumarizerSkill = null!; public static QuestionSumarizerSkill QuestionSumarizerSkill = null!; public static ResetLazy> RootsByUseCase = null!; - /// Registers an AgentSkillCode type. The class must have a parameterless constructor. public static void RegisterCode() where T : AgentSkillCode { RegisteredCodes[typeof(T).FullName!] = typeof(T); @@ -87,8 +84,6 @@ public static void Start(SchemaBuilder sb) }, new InvalidateWith(typeof(AgentSkillEntity))); } - // ─── AgentSkillCodeEntity sync ──────────────────────────────────────────── - static SqlPreCommand? Schema_Generating() { var table = Schema.Current.Table(); @@ -113,8 +108,6 @@ public static void Start(SchemaBuilder sb) static List GenerateCodeEntities() => RegisteredCodes.Keys.Select(fc => new AgentSkillCodeEntity { FullClassName = fc }).ToList(); - // ─── Resolve ────────────────────────────────────────────────────────────── - public static AgentSkillCode ResolveCode( AgentSkillEntity entity, Dictionary, AgentSkillEntity> allEntities) @@ -138,8 +131,6 @@ public static AgentSkillCode ResolveCode( return code; } - // ─── Circular reference validation ──────────────────────────────────────── - static void ValidateNoCircularReferences(AgentSkillEntity entity) { using (new EntityCache(EntityCacheType.ForceNew)) @@ -162,8 +153,6 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) } } - // ─── Public API ─────────────────────────────────────────────────────────── - public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => RootsByUseCase.Value.TryGetC(symbol); @@ -196,8 +185,6 @@ public static SkillCodeInfo GetSkillCodeInfo(string fullClassName) } } -// ─── SkillCodeInfo ──────────────────────────────────────────────────────────── - public class SkillCodeInfo { public string DefaultShortDescription { get; set; } = null!; @@ -213,8 +200,6 @@ public class SkillPropertyMeta public string PropertyType { get; set; } = null!; } -// ─── AgentSkillPropertyAttribute ───────────────────────────────────────────── - [AttributeUsage(AttributeTargets.Property)] public class AgentSkillPropertyAttribute : Attribute { @@ -263,11 +248,8 @@ public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute public override string? ValueHint => "Comma-separated query keys"; } -// ─── AgentSkillCode ─────────────────────────────────────────────────────────── - public abstract class AgentSkillCode { - /// Set from AgentSkillEntity.Name when resolved from DB; falls back to the class-derived short name. public string? InstanceName { get; internal set; } public string Name => InstanceName ?? this.GetType().Name.Before("Skill"); @@ -285,7 +267,7 @@ public string OriginalInstructions set { originalInstructions = value; } } - /// Runtime sub-skill tree, populated from DB entity. Never set in code. + // Populated from DB at resolve time; never set in code. public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); public string GetInstruction(object? context) @@ -420,18 +402,14 @@ public enum SkillActivation Lazy, } -// ─── UIToolAttribute ────────────────────────────────────────────────────────── - /// -/// Marks a [McpServerTool] method as a UI tool: the server never invokes its body. +/// Marks a [McpServerTool] as a UI tool: the server never invokes its body. /// The controller routes the call to the client via the $!AssistantUITool streaming command. -/// The method body must throw InvalidOperationException("This method should not be called on the server"). +/// The method body must throw InvalidOperationException. /// [AttributeUsage(AttributeTargets.Method)] public class UIToolAttribute : Attribute { } -// ─── MCP server builder extension ──────────────────────────────────────────── - public static partial class SignumMcpServerBuilderExtensions { public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, AgentUseCaseSymbol? useCase = null) diff --git a/Extensions/Signum.Agent/ChatbotController.cs b/Extensions/Signum.Agent/ChatbotController.cs index 7b22b609ff..05c5d26aef 100644 --- a/Extensions/Signum.Agent/ChatbotController.cs +++ b/Extensions/Signum.Agent/ChatbotController.cs @@ -5,7 +5,6 @@ using Signum.API; using Signum.API.Filters; using Signum.Authorization; -using System.Data; using System.Diagnostics; using System.Reflection; using System.Text.Json; @@ -18,20 +17,6 @@ public class ChatbotController : Controller public SkillCodeInfo GetSkillCodeInfo(string skillCode) => AgentSkillLogic.GetSkillCodeInfo(skillCode); - [HttpGet("api/chatbot/provider/{providerKey}/models")] - public async Task> GetModels(string providerKey, CancellationToken token) - { - var symbol = SymbolLogic.ToSymbol(providerKey); - return (await ChatbotLogic.GetModelNamesAsync(symbol, token)).Order().ToList(); - } - - [HttpGet("api/chatbot/provider/{providerKey}/embeddingModels")] - public async Task> GetEmbeddingModels(string providerKey, CancellationToken token) - { - var symbol = SymbolLogic.ToSymbol(providerKey); - return (await ChatbotLogic.GetEmbeddingModelNamesAsync(symbol, token)).Order().ToList(); - } - [HttpPost("api/chatbot/feedback/{messageId}")] public void SetFeedback(int messageId, [FromBody] SetFeedbackRequest request) { @@ -177,7 +162,7 @@ ChatSessionEntity GetOrCreateSession(string? sessionID) return sessionID.HasText() == false || sessionID == "undefined" ? new ChatSessionEntity { - LanguageModel = ChatbotLogic.DefaultLanguageModel.Value + LanguageModel = LanguageModelLogic.DefaultLanguageModel.Value ?? throw new InvalidOperationException($"No default {typeof(ChatbotLanguageModelEntity).Name}"), User = UserEntity.Current, StartDate = Clock.Now, @@ -210,9 +195,6 @@ ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) } } -// ─── HttpAgentOutput ────────────────────────────────────────────────────────── - -/// Implements IAgentOutput by streaming UINotification events to the HTTP response. public class HttpAgentOutput : IAgentOutput { readonly HttpResponse _resp; diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index 164ee249b5..9da2ec9d03 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -2,14 +2,11 @@ using Signum.Authorization; using Signum.Authorization.Rules; using Signum.Agent.Skills; -using Signum.Agent.Providers; using Signum.Utilities.Synchronization; using System.Text.Json; -using Pgvector; namespace Signum.Agent; - public static class ChatbotLogic { [AutoExpressionField] @@ -37,145 +34,12 @@ public static IQueryable Messages(this ChatSessionEntity sess public static decimal? TotalPrice(this ChatSessionEntity session) => As.Expression(() => session.Messages().Sum(m => m.Price())); - public static ResetLazy, ChatbotLanguageModelEntity>> LanguageModels = null!; - public static ResetLazy?> DefaultLanguageModel = null!; - - public static ResetLazy, EmbeddingsLanguageModelEntity>> EmbeddingsModels = null!; - public static ResetLazy?> DefaultEmbeddingsModel = null!; - - public static Dictionary ChatbotModelProviders = new Dictionary - { - { LanguageModelProviders.OpenAI, new OpenAIProvider()}, - { LanguageModelProviders.Gemini, new GeminiProvider()}, - { LanguageModelProviders.Anthropic, new AnthropicProvider()}, - { LanguageModelProviders.GithubModels, new GithubModelsProvider()}, - { LanguageModelProviders.Mistral, new MistralProvider()}, - { LanguageModelProviders.Ollama, new OllamaProvider()}, - { LanguageModelProviders.DeepSeek, new DeepSeekProvider()}, - }; - - public static Dictionary EmbeddingsProviders = new Dictionary - { - { LanguageModelProviders.OpenAI, new OpenAIProvider()}, - { LanguageModelProviders.Gemini, new GeminiProvider()}, - { LanguageModelProviders.GithubModels, new GithubModelsProvider()}, - { LanguageModelProviders.Mistral, new MistralProvider()}, - { LanguageModelProviders.Ollama, new OllamaProvider()}, - }; - - public static Func GetConfig; - - public static ChatbotLanguageModelEntity RetrieveFromCache(this Lite lite) - { - return LanguageModels.Value.GetOrThrow(lite); - } - - public static EmbeddingsLanguageModelEntity RetrieveFromCache(this Lite lite) - { - return EmbeddingsModels.Value.GetOrThrow(lite); - } - public static void Start(SchemaBuilder sb, Func config) { if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) return; - GetConfig = config; - - SymbolLogic.Start(sb, () => ChatbotModelProviders.Keys.Union(EmbeddingsProviders.Keys)); - - sb.Include() - .WithSave(ChatbotLanguageModelOperation.Save, (m, args) => - { - if (!m.IsNew && Database.Query().Any(a => a.LanguageModel.Is(m))) - { - var inDb = m.InDB(a => new { a.Model, a.Provider }); - if (inDb.Model != m.Model || !inDb.Provider.Is(m.Provider)) - { - throw new ArgumentNullException(ChatbotMessage.UnableToChangeModelOrProviderOnceUsed.NiceToString()); - } - } - }) - .WithUniqueIndex(a => a.IsDefault, a => a.IsDefault == true) - .WithQuery(() => e => new - { - Entity = e, - e.Id, - e.IsDefault, - e.Provider, - e.Model, - e.Temperature, - e.MaxTokens, - e.PricePerInputToken, - e.PricePerOutputToken, - e.PricePerCachedInputToken, - e.PricePerReasoningOutputToken, - }); - - new Graph.Execute(ChatbotLanguageModelOperation.MakeDefault) - { - CanExecute = a => !a.IsDefault ? null : ValidationMessage._0IsSet.NiceToString(Entity.NicePropertyName(() => a.IsDefault)), - Execute = (e, _) => - { - var other = Database.Query().Where(a => a.IsDefault).SingleOrDefaultEx(); - if (other != null) - { - other.IsDefault = false; - other.Execute(ChatbotLanguageModelOperation.Save); - } - - e.IsDefault = true; - e.Save(); - } - }.Register(); - - - new Graph.Delete(ChatbotLanguageModelOperation.Delete) - { - Delete = (e, _) => { e.Delete(); }, - }.Register(); - - - LanguageModels = sb.GlobalLazy(() => Database.Query().ToDictionary(a => a.ToLite()), new InvalidateWith(typeof(ChatbotLanguageModelEntity))); - DefaultLanguageModel = sb.GlobalLazy(() => LanguageModels.Value.Values.SingleOrDefaultEx(a => a.IsDefault)?.ToLite(), new InvalidateWith(typeof(ChatbotLanguageModelEntity))); - - sb.Include() - .WithSave(EmbeddingsLanguageModelOperation.Save) - .WithUniqueIndex(a => a.IsDefault, a => a.IsDefault == true) - .WithQuery(() => e => new - { - Entity = e, - e.Id, - e.IsDefault, - e.Provider, - e.Model, - e.Dimensions, - }); - - new Graph.Execute(EmbeddingsLanguageModelOperation.MakeDefault) - { - CanExecute = a => !a.IsDefault ? null : ValidationMessage._0IsSet.NiceToString(Entity.NicePropertyName(() => a.IsDefault)), - Execute = (e, _) => - { - var other = Database.Query().Where(a => a.IsDefault).SingleOrDefaultEx(); - if (other != null) - { - other.IsDefault = false; - other.Execute(EmbeddingsLanguageModelOperation.Save); - } - - e.IsDefault = true; - e.Save(); - } - }.Register(); - - new Graph.Delete(EmbeddingsLanguageModelOperation.Delete) - { - Delete = (e, _) => { e.Delete(); }, - }.Register(); - - EmbeddingsModels = sb.GlobalLazy(() => Database.Query().ToDictionary(a => a.ToLite()), new InvalidateWith(typeof(EmbeddingsLanguageModelEntity))); - DefaultEmbeddingsModel = sb.GlobalLazy(() => EmbeddingsModels.Value.Values.SingleOrDefaultEx(a => a.IsDefault)?.ToLite(), new InvalidateWith(typeof(EmbeddingsLanguageModelEntity))); + LanguageModelLogic.Start(sb, config); sb.Include() .WithDelete(ChatSessionOperation.Delete) @@ -213,7 +77,6 @@ public static void Start(SchemaBuilder sb, Func co e.ChatSession, }); - new Graph.Delete(ChatMessageOperation.Delete) { CanDelete = m => m.Is(Database.Query().Where(a => a.ChatSession.Is(m.ChatSession)).OrderByDescending(a => a.CreationDate).Select(a => a.ToLite()).First()) ? null : ChatbotMessage.MessageMustBeTheLastToDelete.NiceToString(), @@ -224,20 +87,6 @@ public static void Start(SchemaBuilder sb, Func co QueryLogic.Expressions.Register((ChatSessionEntity cm) => cm.TotalPrice(), ChatbotMessage.TotalPrice); PermissionLogic.RegisterTypes(typeof(ChatbotPermission)); - - Filter.GetEmbeddingForSmartSearch = (vectorToken, searchString) => - { - // Get the default embeddings model - var modelLite = ChatbotLogic.DefaultEmbeddingsModel.Value; - if (modelLite == null) - throw new InvalidOperationException("No default EmbeddingsLanguageModelEntity configured."); - - // Retrieve and call the embeddings API - var model = modelLite.RetrieveFromCache(); - var embeddings = model.GetEmbeddingsAsync(new[] { searchString }, CancellationToken.None).ResultSafe(); - - return new Vector(embeddings.SingleEx()); - }; } public static void RegisterUserTypeCondition(TypeConditionSymbol userEntities) @@ -268,8 +117,8 @@ public static async Task SumarizeConversation(List me var skill = AgentSkillLogic.ConversationSumarizerSkill; var prompt = skill.GetInstruction(conversationText.ToString()); - var client = GetChatClient(languageModel); - var options = ChatOptions(languageModel, []); + var client = LanguageModelLogic.GetChatClient(languageModel); + var options = LanguageModelLogic.ChatOptions(languageModel, []); var cr = await client.GetResponseAsync(prompt, options, cancellationToken: ct); return cr.Text; } @@ -277,88 +126,18 @@ public static async Task SumarizeConversation(List me public static async Task SumarizeTitle(ConversationHistory history, CancellationToken ct) { var prompt = AgentSkillLogic.QuestionSumarizerSkill.GetInstruction(history); - var client = GetChatClient(history.LanguageModel); - var options = ChatbotLogic.ChatOptions(history.LanguageModel, []); + var client = LanguageModelLogic.GetChatClient(history.LanguageModel); + var options = LanguageModelLogic.ChatOptions(history.LanguageModel, []); var cr = await client.GetResponseAsync(prompt, options, cancellationToken: ct); return cr.Text; } - public static void RegisterChatbotModelProvider(LanguageModelProviderSymbol symbol, IChatbotModelProvider provider) - { - ChatbotModelProviders.Add(symbol, provider); - } - - public static void RegisterEmbeddingsProvider(LanguageModelProviderSymbol symbol, IEmbeddingsProvider provider) - { - EmbeddingsProviders.Add(symbol, provider); - } - - - public static Task> GetModelNamesAsync(LanguageModelProviderSymbol provider, CancellationToken ct) - { - return ChatbotModelProviders.GetOrThrow(provider).GetModelNames(ct); - } - - public static Task> GetEmbeddingModelNamesAsync(LanguageModelProviderSymbol provider, CancellationToken ct) - { - return EmbeddingsProviders.GetOrThrow(provider).GetEmbeddingModelNames(ct); - } - - public static IChatbotModelProvider GetProvider(ChatbotLanguageModelEntity model) - { - return ChatbotModelProviders.GetOrThrow(model.Provider); - } - - public static IChatClient GetChatClient(ChatbotLanguageModelEntity model) - { - return GetProvider(model).CreateChatClient(model); - } - - public static Task> GetEmbeddingsAsync(this EmbeddingsLanguageModelEntity model, string[] inputs, CancellationToken ct) - { - using (HeavyProfiler.Log("GetEmbeddings", () => model.GetMessage() + "\n" + inputs.ToString("\n"))) - { - return EmbeddingsProviders.GetOrThrow(model.Provider).GetEmbeddings(inputs, model, ct); - } - } - - public static ChatOptions ChatOptions(ChatbotLanguageModelEntity languageModel, List? tools) - { - var opts = new ChatOptions - { - ModelId = languageModel.Model, - }; - - if (languageModel.MaxTokens != null) - opts.MaxOutputTokens = languageModel.MaxTokens; - else - opts.MaxOutputTokens = 64000; - - if (languageModel.Temperature != null) - opts.Temperature = languageModel.Temperature; - - if (tools.HasItems()) - opts.Tools = tools; - - return opts; - } - - // ─── Headless / extracted loop ──────────────────────────────────────────── - - /// - /// Runs the full agent loop on an already-initialised ConversationHistory. - /// history.Messages must already contain the system message and any initial - /// user / tool messages to resume from. - /// Stops when the LLM produces a response with no tool calls, or when a - /// UITool is invoked (caller must resume via a new request). - /// public static async Task RunAgentLoopAsync(ConversationHistory history, IAgentOutput output, CancellationToken ct) { - var client = GetChatClient(history.LanguageModel); + var client = LanguageModelLogic.GetChatClient(history.LanguageModel); while (true) { - // Context-window management: summarise when close to the token limit if (history.LanguageModel.MaxTokens != null && history.Messages.Skip(1).LastOrDefault()?.InputTokens > history.LanguageModel.MaxTokens * 0.8) { @@ -387,9 +166,9 @@ public static async Task RunAgentLoopAsync(ConversationHistory history, IAgentOu } var tools = history.GetTools(); - var options = ChatOptions(history.LanguageModel, tools); + var options = LanguageModelLogic.ChatOptions(history.LanguageModel, tools); var messages = history.GetMessages(); - GetProvider(history.LanguageModel).CustomizeMessagesAndOptions(messages, options); + LanguageModelLogic.GetProvider(history.LanguageModel).CustomizeMessagesAndOptions(messages, options); List updates = []; var sw = Stopwatch.StartNew(); @@ -472,7 +251,6 @@ public static async Task RunAgentLoopAsync(ConversationHistory history, IAgentOu await ExecuteToolAsync(history, funCall.Name, funCall.CallId, funCall.Arguments!, output, ct); } - // Title summarisation (runs regardless of UITool pause or normal completion) if (history.SessionTitle == null || history.SessionTitle.StartsWith("!*$")) { history.SessionTitle = history.Session.InDB(a => a.Title); @@ -554,10 +332,6 @@ public static string FormatToolError(string toolName, Exception e, IDictionary - /// Creates a new session and runs the agent loop headlessly (no HTTP connection). - /// The caller is responsible for setting up the authentication context if needed. - ///
public static async Task RunHeadlessAsync( string prompt, AgentUseCaseSymbol? useCase = null, @@ -568,7 +342,7 @@ public static async Task RunHeadlessAsync( useCase ??= AgentUseCase.DefaultChatbot; output ??= NullAgentOutput.Instance; - var modelLite = languageModel ?? DefaultLanguageModel.Value + var modelLite = languageModel ?? LanguageModelLogic.DefaultLanguageModel.Value ?? throw new InvalidOperationException($"No default {nameof(ChatbotLanguageModelEntity)} configured."); var rootSkill = AgentSkillLogic.GetRootForUseCase(useCase) @@ -612,42 +386,16 @@ public static async Task RunHeadlessAsync( } } - - - -// ─── IAgentOutput ───────────────────────────────────────────────────────────── - -/// -/// Receives events from the agent loop. All methods have no-op default implementations -/// so implementations only override what they care about. -/// public interface IAgentOutput { - /// Called when the initial system message is saved (new session). Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - - /// Called when a user question message is saved. Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - - /// Called when the context-window summary message is saved. Task OnSummarizationAsync(ChatMessageEntity summaryMsg, CancellationToken ct) => Task.CompletedTask; - - /// Called once at the start of each assistant response, before any text chunks. Task OnAssistantStartedAsync(CancellationToken ct) => Task.CompletedTask; - - /// Called for each streaming text chunk from the LLM. Task OnTextChunkAsync(string chunk, CancellationToken ct) => Task.CompletedTask; - - /// Called when the assistant message entity is fully saved (includes tool call metadata). Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - - /// Called just before a tool is invoked. Task OnToolStartAsync(string toolId, string callId, CancellationToken ct) => Task.CompletedTask; - - /// Called after a tool completes. Check toolMsg.Exception for errors. Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct) => Task.CompletedTask; - - /// Called when the session title is determined or updated. Task OnTitleUpdatedAsync(string title, CancellationToken ct) => Task.CompletedTask; } @@ -657,38 +405,16 @@ public sealed class NullAgentOutput : IAgentOutput private NullAgentOutput() { } } -public interface IChatbotModelProvider -{ - Task> GetModelNames(CancellationToken ct); - - IChatClient CreateChatClient(ChatbotLanguageModelEntity model); - - void CustomizeMessagesAndOptions(List messages, ChatOptions options) { } -} - -public interface IEmbeddingsProvider -{ - Task> GetEmbeddingModelNames(CancellationToken ct); - - Task> GetEmbeddings(string[] inputs, EmbeddingsLanguageModelEntity model, CancellationToken ct); -} - public class ConversationHistory { public Lite Session; - public ChatbotLanguageModelEntity LanguageModel; - public List Messages; - public string? SessionTitle { get; internal set; } - public AgentSkillCode? RootSkill { get; set; } - public List GetMessages() - { - return Messages.Select(m => ToChatMessage(m)).ToList(); - } + public List GetMessages() => + Messages.Select(ToChatMessage).ToList(); ChatMessage ToChatMessage(ChatMessageEntity c) { @@ -699,11 +425,7 @@ ChatMessage ToChatMessage(ChatMessageEntity c) : null); if (c.Role == ChatMessageRole.Tool) - { - return new ChatMessage(role, [ - new FunctionResultContent(c.ToolCallID!, content) - ]); - } + return new ChatMessage(role, [new FunctionResultContent(c.ToolCallID!, content)]); if (c.ToolCalls.IsEmpty()) return new ChatMessage(role, content); @@ -721,7 +443,7 @@ ChatMessage ToChatMessage(ChatMessageEntity c) return new ChatMessage(role, contents); } - private static object? CleanValue(object? value) + static object? CleanValue(object? value) { if (value is JsonElement je) { @@ -741,7 +463,7 @@ ChatMessage ToChatMessage(ChatMessageEntity c) public List GetTools() { if (RootSkill == null) - return new List(); + return []; var activatedSkills = new HashSet( RootSkill.GetEagerSkillsRecursive().Select(s => s.Name)); @@ -779,7 +501,7 @@ public List GetTools() .ToList(); } - private ChatRole ToChatRole(ChatMessageRole role) => role switch + ChatRole ToChatRole(ChatMessageRole role) => role switch { ChatMessageRole.System => ChatRole.System, ChatMessageRole.User => ChatRole.User, diff --git a/Extensions/Signum.Agent/LanguageModelController.cs b/Extensions/Signum.Agent/LanguageModelController.cs new file mode 100644 index 0000000000..48c57e4b36 --- /dev/null +++ b/Extensions/Signum.Agent/LanguageModelController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; +using Signum.API; +using Signum.API.Filters; + +namespace Signum.Agent; + +public class LanguageModelController : Controller +{ + [HttpGet("api/chatbot/provider/{providerKey}/models")] + public async Task> GetModels(string providerKey, CancellationToken token) + { + var symbol = SymbolLogic.ToSymbol(providerKey); + return (await LanguageModelLogic.GetModelNamesAsync(symbol, token)).Order().ToList(); + } + + [HttpGet("api/chatbot/provider/{providerKey}/embeddingModels")] + public async Task> GetEmbeddingModels(string providerKey, CancellationToken token) + { + var symbol = SymbolLogic.ToSymbol(providerKey); + return (await LanguageModelLogic.GetEmbeddingModelNamesAsync(symbol, token)).Order().ToList(); + } +} diff --git a/Extensions/Signum.Agent/LanguageModelLogic.cs b/Extensions/Signum.Agent/LanguageModelLogic.cs new file mode 100644 index 0000000000..eeaa9e39b8 --- /dev/null +++ b/Extensions/Signum.Agent/LanguageModelLogic.cs @@ -0,0 +1,198 @@ +using Microsoft.Extensions.AI; +using Signum.Agent.Providers; +using Pgvector; + +namespace Signum.Agent; + +public static class LanguageModelLogic +{ + public static Func GetConfig = null!; + + public static ResetLazy, ChatbotLanguageModelEntity>> LanguageModels = null!; + public static ResetLazy?> DefaultLanguageModel = null!; + + public static ResetLazy, EmbeddingsLanguageModelEntity>> EmbeddingsModels = null!; + public static ResetLazy?> DefaultEmbeddingsModel = null!; + + public static Dictionary ChatbotModelProviders = new() + { + { LanguageModelProviders.OpenAI, new OpenAIProvider() }, + { LanguageModelProviders.Gemini, new GeminiProvider() }, + { LanguageModelProviders.Anthropic, new AnthropicProvider() }, + { LanguageModelProviders.GithubModels, new GithubModelsProvider() }, + { LanguageModelProviders.Mistral, new MistralProvider() }, + { LanguageModelProviders.Ollama, new OllamaProvider() }, + { LanguageModelProviders.DeepSeek, new DeepSeekProvider() }, + }; + + public static Dictionary EmbeddingsProviders = new() + { + { LanguageModelProviders.OpenAI, new OpenAIProvider() }, + { LanguageModelProviders.Gemini, new GeminiProvider() }, + { LanguageModelProviders.GithubModels, new GithubModelsProvider() }, + { LanguageModelProviders.Mistral, new MistralProvider() }, + { LanguageModelProviders.Ollama, new OllamaProvider() }, + }; + + public static ChatbotLanguageModelEntity RetrieveFromCache(this Lite lite) => + LanguageModels.Value.GetOrThrow(lite); + + public static EmbeddingsLanguageModelEntity RetrieveFromCache(this Lite lite) => + EmbeddingsModels.Value.GetOrThrow(lite); + + public static void Start(SchemaBuilder sb, Func config) + { + GetConfig = config; + + SymbolLogic.Start(sb, () => ChatbotModelProviders.Keys.Union(EmbeddingsProviders.Keys)); + + sb.Include() + .WithSave(ChatbotLanguageModelOperation.Save, (m, args) => + { + if (!m.IsNew && Database.Query().Any(a => a.LanguageModel.Is(m))) + { + var inDb = m.InDB(a => new { a.Model, a.Provider }); + if (inDb.Model != m.Model || !inDb.Provider.Is(m.Provider)) + throw new ArgumentNullException(ChatbotMessage.UnableToChangeModelOrProviderOnceUsed.NiceToString()); + } + }) + .WithUniqueIndex(a => a.IsDefault, a => a.IsDefault == true) + .WithQuery(() => e => new + { + Entity = e, + e.Id, + e.IsDefault, + e.Provider, + e.Model, + e.Temperature, + e.MaxTokens, + e.PricePerInputToken, + e.PricePerOutputToken, + e.PricePerCachedInputToken, + e.PricePerReasoningOutputToken, + }); + + new Graph.Execute(ChatbotLanguageModelOperation.MakeDefault) + { + CanExecute = a => !a.IsDefault ? null : ValidationMessage._0IsSet.NiceToString(Entity.NicePropertyName(() => a.IsDefault)), + Execute = (e, _) => + { + var other = Database.Query().Where(a => a.IsDefault).SingleOrDefaultEx(); + if (other != null) + { + other.IsDefault = false; + other.Execute(ChatbotLanguageModelOperation.Save); + } + e.IsDefault = true; + e.Save(); + } + }.Register(); + + new Graph.Delete(ChatbotLanguageModelOperation.Delete) + { + Delete = (e, _) => { e.Delete(); }, + }.Register(); + + LanguageModels = sb.GlobalLazy(() => Database.Query().ToDictionary(a => a.ToLite()), new InvalidateWith(typeof(ChatbotLanguageModelEntity))); + DefaultLanguageModel = sb.GlobalLazy(() => LanguageModels.Value.Values.SingleOrDefaultEx(a => a.IsDefault)?.ToLite(), new InvalidateWith(typeof(ChatbotLanguageModelEntity))); + + sb.Include() + .WithSave(EmbeddingsLanguageModelOperation.Save) + .WithUniqueIndex(a => a.IsDefault, a => a.IsDefault == true) + .WithQuery(() => e => new + { + Entity = e, + e.Id, + e.IsDefault, + e.Provider, + e.Model, + e.Dimensions, + }); + + new Graph.Execute(EmbeddingsLanguageModelOperation.MakeDefault) + { + CanExecute = a => !a.IsDefault ? null : ValidationMessage._0IsSet.NiceToString(Entity.NicePropertyName(() => a.IsDefault)), + Execute = (e, _) => + { + var other = Database.Query().Where(a => a.IsDefault).SingleOrDefaultEx(); + if (other != null) + { + other.IsDefault = false; + other.Execute(EmbeddingsLanguageModelOperation.Save); + } + e.IsDefault = true; + e.Save(); + } + }.Register(); + + new Graph.Delete(EmbeddingsLanguageModelOperation.Delete) + { + Delete = (e, _) => { e.Delete(); }, + }.Register(); + + EmbeddingsModels = sb.GlobalLazy(() => Database.Query().ToDictionary(a => a.ToLite()), new InvalidateWith(typeof(EmbeddingsLanguageModelEntity))); + DefaultEmbeddingsModel = sb.GlobalLazy(() => EmbeddingsModels.Value.Values.SingleOrDefaultEx(a => a.IsDefault)?.ToLite(), new InvalidateWith(typeof(EmbeddingsLanguageModelEntity))); + + Filter.GetEmbeddingForSmartSearch = (vectorToken, searchString) => + { + var modelLite = DefaultEmbeddingsModel.Value + ?? throw new InvalidOperationException("No default EmbeddingsLanguageModelEntity configured."); + var embeddings = modelLite.RetrieveFromCache().GetEmbeddingsAsync([searchString], CancellationToken.None).ResultSafe(); + return new Vector(embeddings.SingleEx()); + }; + } + + public static void RegisterChatbotModelProvider(LanguageModelProviderSymbol symbol, IChatbotModelProvider provider) => + ChatbotModelProviders.Add(symbol, provider); + + public static void RegisterEmbeddingsProvider(LanguageModelProviderSymbol symbol, IEmbeddingsProvider provider) => + EmbeddingsProviders.Add(symbol, provider); + + public static Task> GetModelNamesAsync(LanguageModelProviderSymbol provider, CancellationToken ct) => + ChatbotModelProviders.GetOrThrow(provider).GetModelNames(ct); + + public static Task> GetEmbeddingModelNamesAsync(LanguageModelProviderSymbol provider, CancellationToken ct) => + EmbeddingsProviders.GetOrThrow(provider).GetEmbeddingModelNames(ct); + + public static IChatbotModelProvider GetProvider(ChatbotLanguageModelEntity model) => + ChatbotModelProviders.GetOrThrow(model.Provider); + + public static IChatClient GetChatClient(ChatbotLanguageModelEntity model) => + GetProvider(model).CreateChatClient(model); + + public static Task> GetEmbeddingsAsync(this EmbeddingsLanguageModelEntity model, string[] inputs, CancellationToken ct) + { + using (HeavyProfiler.Log("GetEmbeddings", () => model.GetMessage() + "\n" + inputs.ToString("\n"))) + return EmbeddingsProviders.GetOrThrow(model.Provider).GetEmbeddings(inputs, model, ct); + } + + public static ChatOptions ChatOptions(ChatbotLanguageModelEntity languageModel, List? tools) + { + var opts = new ChatOptions + { + ModelId = languageModel.Model, + MaxOutputTokens = languageModel.MaxTokens ?? 64000, + }; + + if (languageModel.Temperature != null) + opts.Temperature = languageModel.Temperature; + + if (tools.HasItems()) + opts.Tools = tools; + + return opts; + } +} + +public interface IChatbotModelProvider +{ + Task> GetModelNames(CancellationToken ct); + IChatClient CreateChatClient(ChatbotLanguageModelEntity model); + void CustomizeMessagesAndOptions(List messages, ChatOptions options) { } +} + +public interface IEmbeddingsProvider +{ + Task> GetEmbeddingModelNames(CancellationToken ct); + Task> GetEmbeddings(string[] inputs, EmbeddingsLanguageModelEntity model, CancellationToken ct); +} diff --git a/Extensions/Signum.Agent/Providers/AnthropicProvider.cs b/Extensions/Signum.Agent/Providers/AnthropicProvider.cs index fab2bb8f53..4eb027254a 100644 --- a/Extensions/Signum.Agent/Providers/AnthropicProvider.cs +++ b/Extensions/Signum.Agent/Providers/AnthropicProvider.cs @@ -44,7 +44,7 @@ public void CustomizeMessagesAndOptions(List messages, ChatOptions private static string GetApiKey() { - var apiKey = ChatbotLogic.GetConfig().AnthropicAPIKey; + var apiKey = LanguageModelLogic.GetConfig().AnthropicAPIKey; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No API Key for Claude configured!"); diff --git a/Extensions/Signum.Agent/Providers/DeepSeekProvider.cs b/Extensions/Signum.Agent/Providers/DeepSeekProvider.cs index f14da22c42..108b964490 100644 --- a/Extensions/Signum.Agent/Providers/DeepSeekProvider.cs +++ b/Extensions/Signum.Agent/Providers/DeepSeekProvider.cs @@ -49,7 +49,7 @@ public void CustomizeMessagesAndOptions(List> GetEmbeddings(string[] inputs, EmbeddingsLangua static string GetApiKey() { - var apiKey = ChatbotLogic.GetConfig().GeminiAPIKey; + var apiKey = LanguageModelLogic.GetConfig().GeminiAPIKey; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No API Key for Gemini configured!"); diff --git a/Extensions/Signum.Agent/Providers/GithubModelsProvider.cs b/Extensions/Signum.Agent/Providers/GithubModelsProvider.cs index c236df4a0d..e5e9b818bd 100644 --- a/Extensions/Signum.Agent/Providers/GithubModelsProvider.cs +++ b/Extensions/Signum.Agent/Providers/GithubModelsProvider.cs @@ -94,7 +94,7 @@ public async Task> GetEmbeddings(string[] inputs, EmbeddingsLangua static string GetToken() { - var apiKey = ChatbotLogic.GetConfig().GithubModelsToken; + var apiKey = LanguageModelLogic.GetConfig().GithubModelsToken; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No Token for Github Models configured!"); diff --git a/Extensions/Signum.Agent/Providers/MistralProvider.cs b/Extensions/Signum.Agent/Providers/MistralProvider.cs index 884f9c654d..293952a379 100644 --- a/Extensions/Signum.Agent/Providers/MistralProvider.cs +++ b/Extensions/Signum.Agent/Providers/MistralProvider.cs @@ -53,7 +53,7 @@ public async Task> GetEmbeddings(string[] inputs, EmbeddingsLangua static string GetApiKey() { - var apiKey = ChatbotLogic.GetConfig().MistralAPIKey; + var apiKey = LanguageModelLogic.GetConfig().MistralAPIKey; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No API Key for Mistral configured!"); diff --git a/Extensions/Signum.Agent/Providers/OllamaProvider.cs b/Extensions/Signum.Agent/Providers/OllamaProvider.cs index 2b624aadf9..f11ef0b2f0 100644 --- a/Extensions/Signum.Agent/Providers/OllamaProvider.cs +++ b/Extensions/Signum.Agent/Providers/OllamaProvider.cs @@ -51,7 +51,7 @@ public async Task> GetEmbeddings(string[] inputs, EmbeddingsLangua private static string GetOllamaUrl() { - var apiKey = ChatbotLogic.GetConfig().OllamaUrl; + var apiKey = LanguageModelLogic.GetConfig().OllamaUrl; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No Ollama URL configured!"); diff --git a/Extensions/Signum.Agent/Providers/OpenAIProvider.cs b/Extensions/Signum.Agent/Providers/OpenAIProvider.cs index 59f6f68fd6..1d65423d7a 100644 --- a/Extensions/Signum.Agent/Providers/OpenAIProvider.cs +++ b/Extensions/Signum.Agent/Providers/OpenAIProvider.cs @@ -47,7 +47,7 @@ public async Task> GetEmbeddings(string[] inputs, EmbeddingsLangua static string GetApiKey() { - var apiKey = ChatbotLogic.GetConfig().OpenAIAPIKey; + var apiKey = LanguageModelLogic.GetConfig().OpenAIAPIKey; if (apiKey.IsNullOrEmpty()) throw new InvalidOperationException("No API Key for OpenAI configured!"); From 7b0c1d45079a5ee0fea9e7ea1fa366fec9b91883 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:36:21 +0000 Subject: [PATCH 09/21] Add LanguageModelClient.tsx; remove language model from ChatbotClient - LanguageModelClient: start() registers ChatbotLanguageModel/EmbeddingsLanguageModel entity settings; API.getModels/getEmbeddingModels methods - ChatbotClient: removed language model entity settings and model API methods; also removed the stale optiions typo and the UITool registry block comment - Templates updated to import from LanguageModelClient instead of ChatbotClient https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/ChatbotClient.tsx | 45 +++++-------------- .../Signum.Agent/LanguageModelClient.tsx | 21 +++++++++ .../Templates/ChatbotLanguageModel.tsx | 4 +- .../Templates/EmbeddingsLanguageModel.tsx | 4 +- 4 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 Extensions/Signum.Agent/LanguageModelClient.tsx diff --git a/Extensions/Signum.Agent/ChatbotClient.tsx b/Extensions/Signum.Agent/ChatbotClient.tsx index 6d18f6aecd..13ae1732f3 100644 --- a/Extensions/Signum.Agent/ChatbotClient.tsx +++ b/Extensions/Signum.Agent/ChatbotClient.tsx @@ -3,7 +3,7 @@ import { RouteObject } from 'react-router' import { ajaxGet, ajaxPost, wrapRequest, AjaxOptions } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' -import { ChatbotLanguageModelEntity, ChatSessionEntity, ChatMessageEntity, LanguageModelProviderSymbol, EmbeddingsLanguageModelEntity, ToolCallEmbedded, UserFeedback } from './Signum.Agent' +import { ChatSessionEntity, ChatMessageEntity, ToolCallEmbedded, UserFeedback } from './Signum.Agent' import { toAbsoluteUrl } from '../../Signum/React/AppContext'; import { Dic } from '@framework/Globals'; import { Finder } from '@framework/Finder'; @@ -14,15 +14,12 @@ const ChatMarkdown = React.lazy(() => import("./Templates/ChatMarkdown")); export namespace ChatbotClient { export let renderMarkdown = (markdown: string): React.JSX.Element => ; - //export let renderMarkdown = (markdown: string): React.JSX.Element => {markdown}; - export function start(options: { routes: RouteObject[] }): void { - Navigator.addSettings(new EntitySettings(ChatbotLanguageModelEntity, e => import('./Templates/ChatbotLanguageModel'))); - Navigator.addSettings(new EntitySettings(EmbeddingsLanguageModelEntity, e => import('./Templates/EmbeddingsLanguageModel'))); + export function start(options: { routes: RouteObject[] }): void { Navigator.addSettings(new EntitySettings(ChatSessionEntity, a => import('./Templates/ChatSession'))); Navigator.addSettings(new EntitySettings(ChatMessageEntity, a => import('./Templates/ChatMessage'))); Finder.registerPropertyFormatter(ChatMessageEntity.tryPropertyRoute(a => a.content), new Finder.CellFormatter((cell, ctx, column) => cell && , true)); - + AppContext.clearSettingsActions.push(() => uiToolRegistry.clear()); } @@ -40,23 +37,23 @@ export namespace ChatbotClient { export namespace API { - export function ask(question: string, optiions: { sessionId?: string | number, callId?: string, toolId?: string, recover?: boolean }, signal?: AbortSignal): Promise { + export function ask(question: string, options: { sessionId?: string | number, callId?: string, toolId?: string, recover?: boolean }, signal?: AbortSignal): Promise { - const options: AjaxOptions = { url: "/api/chatbot/ask", }; + const ajaxOptions: AjaxOptions = { url: "/api/chatbot/ask" }; - return wrapRequest(options, () => { + return wrapRequest(ajaxOptions, () => { const headers = { 'Accept': 'text/plain', 'Content-Type': 'text/plain', - 'X-Chatbot-Session-Id': optiions.sessionId, - 'X-Chatbot-UIReply-CallId': optiions.callId, - 'X-Chatbot-UIReply-ToolId': optiions.toolId, - 'X-Chatbot-Recover': optiions.recover ? 'true' : undefined, - ...options.headers + 'X-Chatbot-Session-Id': options.sessionId, + 'X-Chatbot-UIReply-CallId': options.callId, + 'X-Chatbot-UIReply-ToolId': options.toolId, + 'X-Chatbot-Recover': options.recover ? 'true' : undefined, + ...ajaxOptions.headers } as any; - return fetch(toAbsoluteUrl(options.url), { + return fetch(toAbsoluteUrl(ajaxOptions.url), { method: "POST", credentials: "same-origin", headers: Dic.simplify(headers), @@ -67,15 +64,6 @@ export namespace ChatbotClient { }); } - - export function getModels(provider: LanguageModelProviderSymbol): Promise> { - return ajaxGet({ url: `/api/chatbot/provider/${provider.key}/models` }); - } - - export function getEmbeddingModels(provider: LanguageModelProviderSymbol): Promise> { - return ajaxGet({ url: `/api/chatbot/provider/${provider.key}/embeddingModels` }); - } - export function getMessagesBySessionId(sessionId: string | number | undefined): Promise> { return ajaxGet({ url: "/api/chatbot/messages/" + sessionId }); } @@ -84,17 +72,8 @@ export namespace ChatbotClient { return ajaxPost({ url: "/api/chatbot/feedback/" + messageId }, { feedback, message }); } } - } -// UI Tool registry -// -// Subclass UITool and implement either: -// • handleDirectly() — resolves automatically without showing anything in the chat -// (e.g. GetUIContext just reads browser state and calls sendToolResponse) -// • renderWidget() — renders a React widget inline in the conversation; -// call sendToolResponse() from the widget when the user responds. -// export abstract class UITool { abstract uiToolName: string; diff --git a/Extensions/Signum.Agent/LanguageModelClient.tsx b/Extensions/Signum.Agent/LanguageModelClient.tsx new file mode 100644 index 0000000000..c7df8a7908 --- /dev/null +++ b/Extensions/Signum.Agent/LanguageModelClient.tsx @@ -0,0 +1,21 @@ +import { ajaxGet } from '@framework/Services'; +import { Navigator, EntitySettings } from '@framework/Navigator'; +import { ChatbotLanguageModelEntity, EmbeddingsLanguageModelEntity, LanguageModelProviderSymbol } from './Signum.Agent'; + +export namespace LanguageModelClient { + + export function start(options: { routes: unknown[] }): void { + Navigator.addSettings(new EntitySettings(ChatbotLanguageModelEntity, e => import('./Templates/ChatbotLanguageModel'))); + Navigator.addSettings(new EntitySettings(EmbeddingsLanguageModelEntity, e => import('./Templates/EmbeddingsLanguageModel'))); + } + + export namespace API { + export function getModels(provider: LanguageModelProviderSymbol): Promise { + return ajaxGet({ url: `/api/chatbot/provider/${provider.key}/models` }); + } + + export function getEmbeddingModels(provider: LanguageModelProviderSymbol): Promise { + return ajaxGet({ url: `/api/chatbot/provider/${provider.key}/embeddingModels` }); + } + } +} diff --git a/Extensions/Signum.Agent/Templates/ChatbotLanguageModel.tsx b/Extensions/Signum.Agent/Templates/ChatbotLanguageModel.tsx index 2ef25664fc..8d6ee2c38b 100644 --- a/Extensions/Signum.Agent/Templates/ChatbotLanguageModel.tsx +++ b/Extensions/Signum.Agent/Templates/ChatbotLanguageModel.tsx @@ -3,7 +3,7 @@ import { AutoLine, EntityCombo, EnumLine } from '@framework/Lines' import { TypeContext } from '@framework/TypeContext' import { ChatbotLanguageModelEntity } from '../Signum.Agent'; import { useAPI, useForceUpdate } from '@framework/Hooks'; -import { ChatbotClient } from '../ChatbotClient'; +import { LanguageModelClient } from '../LanguageModelClient'; export default function ChatbotConfiguration(p: { ctx: TypeContext }): React.JSX.Element { const ctx = p.ctx; @@ -12,7 +12,7 @@ export default function ChatbotConfiguration(p: { ctx: TypeContext provider && ChatbotClient.API.getModels(provider), [provider]); + const models = useAPI(() => provider && LanguageModelClient.API.getModels(provider), [provider]); return (
diff --git a/Extensions/Signum.Agent/Templates/EmbeddingsLanguageModel.tsx b/Extensions/Signum.Agent/Templates/EmbeddingsLanguageModel.tsx index 57281de37e..6dadadadd8 100644 --- a/Extensions/Signum.Agent/Templates/EmbeddingsLanguageModel.tsx +++ b/Extensions/Signum.Agent/Templates/EmbeddingsLanguageModel.tsx @@ -3,7 +3,7 @@ import { AutoLine, EntityCombo, EnumLine,NumberLine } from '@framework/Lines' import { TypeContext } from '@framework/TypeContext' import { EmbeddingsLanguageModelEntity } from '../Signum.Agent'; import { useAPI, useForceUpdate } from '@framework/Hooks'; -import { ChatbotClient } from '../ChatbotClient'; +import { LanguageModelClient } from '../LanguageModelClient'; export default function EmbeddingsLanguageModel(p: { ctx: TypeContext }): React.JSX.Element { const ctx = p.ctx; @@ -11,7 +11,7 @@ export default function EmbeddingsLanguageModel(p: { ctx: TypeContext provider && ChatbotClient.API.getEmbeddingModels(provider), [provider]); + const models = useAPI(() => provider && LanguageModelClient.API.getEmbeddingModels(provider), [provider]); return (
From 383cd13dbe14037fd8d30f71cfc18c0484f5b299 Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Fri, 3 Apr 2026 22:54:31 +0200 Subject: [PATCH 10/21] fix compilation issues --- Extensions/Signum.Agent/ChatbotClient.tsx | 8 +++ Extensions/Signum.Agent/LanguageModelLogic.cs | 1 + Extensions/Signum.Agent/Signum.Agent.ts | 53 +++++++++++++++++++ .../Signum.Agent/Templates/AgentSkill.tsx | 10 ++-- Extensions/Signum.Agent/tsconfig.json | 3 +- 5 files changed, 69 insertions(+), 6 deletions(-) diff --git a/Extensions/Signum.Agent/ChatbotClient.tsx b/Extensions/Signum.Agent/ChatbotClient.tsx index 13ae1732f3..ce69f1bebf 100644 --- a/Extensions/Signum.Agent/ChatbotClient.tsx +++ b/Extensions/Signum.Agent/ChatbotClient.tsx @@ -74,6 +74,14 @@ export namespace ChatbotClient { } } +// UI Tool registry +// +// Subclass UITool and implement either: +// • handleDirectly() — resolves automatically without showing anything in the chat +// (e.g. GetUIContext just reads browser state and calls sendToolResponse) +// • renderWidget() — renders a React widget inline in the conversation; +// call sendToolResponse() from the widget when the user responds. +// export abstract class UITool { abstract uiToolName: string; diff --git a/Extensions/Signum.Agent/LanguageModelLogic.cs b/Extensions/Signum.Agent/LanguageModelLogic.cs index eeaa9e39b8..e8d21409f2 100644 --- a/Extensions/Signum.Agent/LanguageModelLogic.cs +++ b/Extensions/Signum.Agent/LanguageModelLogic.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.AI; using Signum.Agent.Providers; using Pgvector; +using Signum.Utilities.Synchronization; namespace Signum.Agent; diff --git a/Extensions/Signum.Agent/Signum.Agent.ts b/Extensions/Signum.Agent/Signum.Agent.ts index f468d2cbb4..5a633ca379 100644 --- a/Extensions/Signum.Agent/Signum.Agent.ts +++ b/Extensions/Signum.Agent/Signum.Agent.ts @@ -12,6 +12,54 @@ export interface ToolCallEmbedded { _response?: ChatMessageEntity } +export const AgentSkillCodeEntity: Type = new Type("AgentSkillCode"); +export interface AgentSkillCodeEntity extends Entities.Entity { + Type: "AgentSkillCode"; + fullClassName: string; +} + +export const AgentSkillEntity: Type = new Type("AgentSkill"); +export interface AgentSkillEntity extends Entities.Entity { + Type: "AgentSkill"; + name: string; + skillCode: AgentSkillCodeEntity; + active: boolean; + useCase: AgentUseCaseSymbol | null; + shortDescription: string | null; + instructions: string | null; + propertyOverrides: Entities.MList; + subSkills: Entities.MList; +} + +export namespace AgentSkillOperation { + export const Save : Operations.ExecuteSymbol = registerSymbol("Operation", "AgentSkillOperation.Save"); + export const Delete : Operations.DeleteSymbol = registerSymbol("Operation", "AgentSkillOperation.Delete"); +} + +export const AgentSkillPropertyOverrideEmbedded: Type = new Type("AgentSkillPropertyOverrideEmbedded"); +export interface AgentSkillPropertyOverrideEmbedded extends Entities.EmbeddedEntity { + Type: "AgentSkillPropertyOverrideEmbedded"; + propertyName: string; + value: string | null; +} + +export const AgentSkillSubSkillEmbedded: Type = new Type("AgentSkillSubSkillEmbedded"); +export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { + Type: "AgentSkillSubSkillEmbedded"; + skill: Entities.Lite; + activation: SkillActivation; +} + +export namespace AgentUseCase { + export const DefaultChatbot : AgentUseCaseSymbol = registerSymbol("AgentUseCase", "AgentUseCase.DefaultChatbot"); + export const Summarizer : AgentUseCaseSymbol = registerSymbol("AgentUseCase", "AgentUseCase.Summarizer"); +} + +export const AgentUseCaseSymbol: Type = new Type("AgentUseCase"); +export interface AgentUseCaseSymbol extends Basics.Symbol { + Type: "AgentUseCase"; +} + export const ChatbotConfigurationEmbedded: Type = new Type("ChatbotConfigurationEmbedded"); export interface ChatbotConfigurationEmbedded extends Entities.EmbeddedEntity { Type: "ChatbotConfigurationEmbedded"; @@ -159,6 +207,11 @@ export interface LanguageModelProviderSymbol extends Basics.Symbol { Type: "LanguageModelProvider"; } +export const SkillActivation: EnumType = new EnumType("SkillActivation"); +export type SkillActivation = + "Eager" | + "Lazy"; + export const ToolCallEmbedded: Type = new Type("ToolCallEmbedded"); export interface ToolCallEmbedded extends Entities.EmbeddedEntity { Type: "ToolCallEmbedded"; diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/AgentSkill.tsx index ced04e8177..8a1ace1795 100644 --- a/Extensions/Signum.Agent/Templates/AgentSkill.tsx +++ b/Extensions/Signum.Agent/Templates/AgentSkill.tsx @@ -47,16 +47,16 @@ export default function AgentSkill(p: { ctx: TypeContext }): R
Sub-Skills - e.subSkills)} columns={EntityTable.typedColumns([ - { property: e => e.skill, template: ectx => e.skill)} /> }, + e.subSkills)} columns={[ { property: e => e.activation, template: ectx => e.activation)} /> }, - ])} /> + { property: e => e.skill, template: ectx => e.skill)} /> }, + ]} />
{skillCodeInfo && skillCodeInfo.properties.length > 0 && (
Property Overrides - e.propertyOverrides)} columns={EntityTable.typedColumns([ + e.propertyOverrides)} columns={[ { property: e => e.propertyName, template: ectx => ( @@ -69,7 +69,7 @@ export default function AgentSkill(p: { ctx: TypeContext }): R property: e => e.value, template: ectx => e.value)} properties={skillCodeInfo.properties} propertyName={ectx.value.propertyName} /> }, - ])} /> + ]} />
)}
diff --git a/Extensions/Signum.Agent/tsconfig.json b/Extensions/Signum.Agent/tsconfig.json index 169f2844b7..efdfe8dde4 100644 --- a/Extensions/Signum.Agent/tsconfig.json +++ b/Extensions/Signum.Agent/tsconfig.json @@ -9,6 +9,7 @@ "references": [ { "path": "../../Signum" }, { "path": "../Signum.Authorization" }, - { "path": "../Signum.HtmlEditor" } + { "path": "../Signum.HtmlEditor" }, + { "path": "../Signum.DiffLog" } ] } From 2a7648a02cd2a7a8fdbba1393b8f8919b2d7b154 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:00:18 +0000 Subject: [PATCH 11/21] Add SkillCodeInfo and SkillPropertyMeta to Signum.Agent.ts These plain DTO interfaces are not generated by the T4 template so they need to be declared manually in the .ts source file. https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/Signum.Agent.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Extensions/Signum.Agent/Signum.Agent.ts b/Extensions/Signum.Agent/Signum.Agent.ts index 5a633ca379..20ffe2f3e2 100644 --- a/Extensions/Signum.Agent/Signum.Agent.ts +++ b/Extensions/Signum.Agent/Signum.Agent.ts @@ -212,6 +212,19 @@ export type SkillActivation = "Eager" | "Lazy"; +export interface SkillPropertyMeta { + propertyName: string; + attributeName: string; + valueHint: string | null; + propertyType: string; +} + +export interface SkillCodeInfo { + defaultShortDescription: string; + defaultInstructions: string; + properties: SkillPropertyMeta[]; +} + export const ToolCallEmbedded: Type = new Type("ToolCallEmbedded"); export interface ToolCallEmbedded extends Entities.EmbeddedEntity { Type: "ToolCallEmbedded"; From 041be2f4aef2e7aa5384baeb4b115c33644334d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:01:44 +0000 Subject: [PATCH 12/21] Move SkillCodeInfo/SkillPropertyMeta to AgentSkillClient.tsx Signum.Agent.ts is auto-generated and would overwrite manual additions. Defining these plain DTO interfaces in AgentSkillClient.tsx instead; template updated to import SkillPropertyMeta from there. Also removed them from Signum.Agent.d.ts (derived file). https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillClient.tsx | 29 ++++++++++--------- Extensions/Signum.Agent/Signum.Agent.d.ts | 11 ------- Extensions/Signum.Agent/Signum.Agent.ts | 13 --------- .../Signum.Agent/Templates/AgentSkill.tsx | 3 +- 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillClient.tsx b/Extensions/Signum.Agent/AgentSkillClient.tsx index 7fd04d353b..bbe19dd143 100644 --- a/Extensions/Signum.Agent/AgentSkillClient.tsx +++ b/Extensions/Signum.Agent/AgentSkillClient.tsx @@ -3,7 +3,20 @@ import { ajaxGet } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator'; import * as AppContext from '@framework/AppContext'; import { TypeContext } from '@framework/TypeContext'; -import { AgentSkillEntity, SkillPropertyMeta, SkillCodeInfo } from './Signum.Agent'; +import { AgentSkillEntity } from './Signum.Agent'; + +export interface SkillPropertyMeta { + propertyName: string; + attributeName: string; + valueHint: string | null; + propertyType: string; +} + +export interface SkillCodeInfo { + defaultShortDescription: string; + defaultInstructions: string; + properties: SkillPropertyMeta[]; +} export namespace AgentSkillClient { @@ -12,8 +25,6 @@ export namespace AgentSkillClient { AppContext.clearSettingsActions.push(() => propertyValueRegistry.clear()); } - // ─── Property value control registry ───────────────────────────────────── - export type PropertyValueFactory = ( ctx: TypeContext, meta: SkillPropertyMeta @@ -21,15 +32,7 @@ export namespace AgentSkillClient { const propertyValueRegistry = new Map(); - /** - * Register a custom control for editing AgentSkillPropertyOverride.value, - * keyed by the C# attribute name without "Attribute" - * (e.g. "AgentSkillProperty_QueryList", "AgentSkillProperty"). - */ - export function registerPropertyValueControl( - attributeName: string, - factory: PropertyValueFactory - ): void { + export function registerPropertyValueControl(attributeName: string, factory: PropertyValueFactory): void { propertyValueRegistry.set(attributeName, factory); } @@ -37,8 +40,6 @@ export namespace AgentSkillClient { return propertyValueRegistry.get(attributeName); } - // ─── API ────────────────────────────────────────────────────────────────── - export namespace API { export function getSkillCodeInfo(skillCode: string): Promise { return ajaxGet({ url: `/api/agentSkill/skillCodeInfo/${encodeURIComponent(skillCode)}` }); diff --git a/Extensions/Signum.Agent/Signum.Agent.d.ts b/Extensions/Signum.Agent/Signum.Agent.d.ts index da36726b94..1511c374a6 100644 --- a/Extensions/Signum.Agent/Signum.Agent.d.ts +++ b/Extensions/Signum.Agent/Signum.Agent.d.ts @@ -46,17 +46,6 @@ export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { } export declare const SkillActivation: EnumType; export type SkillActivation = "Eager" | "Lazy"; -export interface SkillPropertyMeta { - propertyName: string; - attributeName: string; - valueHint: string | null; - propertyType: string; -} -export interface SkillCodeInfo { - defaultShortDescription: string; - defaultInstructions: string; - properties: SkillPropertyMeta[]; -} export interface ToolCallEmbedded { _response?: ChatMessageEntity; } diff --git a/Extensions/Signum.Agent/Signum.Agent.ts b/Extensions/Signum.Agent/Signum.Agent.ts index 20ffe2f3e2..5a633ca379 100644 --- a/Extensions/Signum.Agent/Signum.Agent.ts +++ b/Extensions/Signum.Agent/Signum.Agent.ts @@ -212,19 +212,6 @@ export type SkillActivation = "Eager" | "Lazy"; -export interface SkillPropertyMeta { - propertyName: string; - attributeName: string; - valueHint: string | null; - propertyType: string; -} - -export interface SkillCodeInfo { - defaultShortDescription: string; - defaultInstructions: string; - properties: SkillPropertyMeta[]; -} - export const ToolCallEmbedded: Type = new Type("ToolCallEmbedded"); export interface ToolCallEmbedded extends Entities.EmbeddedEntity { Type: "ToolCallEmbedded"; diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/AgentSkill.tsx index 8a1ace1795..3cd1c1a4cc 100644 --- a/Extensions/Signum.Agent/Templates/AgentSkill.tsx +++ b/Extensions/Signum.Agent/Templates/AgentSkill.tsx @@ -1,7 +1,8 @@ import * as React from 'react' import { CheckboxLine, EntityCombo, EntityLine, EntityTable, EnumLine, TextBoxLine } from '@framework/Lines' import { TypeContext } from '@framework/TypeContext' -import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded, SkillPropertyMeta } from '../Signum.Agent' +import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded } from '../Signum.Agent' +import { AgentSkillClient, SkillPropertyMeta } from '../AgentSkillClient' import { useAPI, useForceUpdate } from '@framework/Hooks' import { AgentSkillClient } from '../AgentSkillClient' import { MarkdownLine } from '@framework/Lines/MarkdownLine' From 2760d4a2afd69a54950e1f8285806583e7bf7d03 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:03:23 +0000 Subject: [PATCH 13/21] Add Signum/yarn.lock https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Signum/yarn.lock | 1627 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1627 insertions(+) create mode 100644 Signum/yarn.lock diff --git a/Signum/yarn.lock b/Signum/yarn.lock new file mode 100644 index 0000000000..2024806def --- /dev/null +++ b/Signum/yarn.lock @@ -0,0 +1,1627 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.2.tgz#9a6e2d05f4b6692e1801cd4fb176ad823930ed5e" + integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== + +"@fortawesome/fontawesome-common-types@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470" + integrity sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg== + +"@fortawesome/fontawesome-svg-core@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz#0ac6013724d5cc327c1eb81335b91300a4fce2f2" + integrity sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/free-brands-svg-icons@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz#4ebee8098f31da5446dda81edc344025eb59b27e" + integrity sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/free-regular-svg-icons@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz#f1651e55e6651a15589b0569516208f9c65f96db" + integrity sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/free-solid-svg-icons@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz#fe25883b5eb8464a82918599950d283c465b57f6" + integrity sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/react-fontawesome@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-3.0.0.tgz#20ed596fc402d6f607511d3ec33f325794fb0be9" + integrity sha512-x6boc1RLEjf/QPrMS20VJcabTZeGCb1hbwNybPPLjJohGPowXfjOpwQlVK6aH6MVKfCq2JXeHRIlx+tYpS18FA== + dependencies: + semver "^7.7.2" + +"@microsoft/signalr@8.0.7": + version "8.0.7" + resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.7.tgz#94419ddbf9418753e493f4ae4c13990316ec2ea5" + integrity sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw== + dependencies: + abort-controller "^3.0.0" + eventsource "^2.0.2" + fetch-cookie "^2.0.3" + node-fetch "^2.6.7" + ws "^7.4.5" + +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@react-aria/ssr@^3.5.0": + version "3.9.10" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6" + integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== + dependencies: + "@swc/helpers" "^0.5.0" + +"@restart/hooks@^0.4.5", "@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + +"@restart/hooks@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" + integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== + dependencies: + dequal "^2.0.3" + +"@restart/ui@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" + integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== + dependencies: + "@babel/runtime" "^7.26.0" + "@popperjs/core" "^2.11.8" + "@react-aria/ssr" "^3.5.0" + "@restart/hooks" "^0.5.0" + "@types/warning" "^3.0.3" + dequal "^2.0.3" + dom-helpers "^5.2.0" + uncontrollable "^8.0.4" + warning "^4.0.3" + +"@swc/helpers@^0.5.0": + version "0.5.21" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.21.tgz#0b1b020317ee1282860ca66f7e9a7c7790f05ae0" + integrity sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg== + dependencies: + tslib "^2.8.0" + +"@types/classnames@^2.3.1": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.4.tgz#1a1fdf5023ef216219f13e702543f9ce9b394560" + integrity sha512-dwmfrMMQb9ujX1uYGvB5ERDlOzBNywnZAZBtOe107/hORWP05ESgU4QyaanZMWYYfd2BzrG78y13/Bju8IQcMQ== + dependencies: + classnames "*" + +"@types/d3-array@*": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.2.tgz#e02151464d02d4a1b44646d0fcdb93faf88fde8c" + integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== + +"@types/d3-axis@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" + integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" + integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" + integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-contour@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" + integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" + integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== + +"@types/d3-dispatch@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz#ef004d8a128046cfce434d17182f834e44ef95b2" + integrity sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA== + +"@types/d3-drag@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" + integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== + +"@types/d3-ease@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-fetch@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" + integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" + integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== + +"@types/d3-format@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" + integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== + +"@types/d3-geo@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" + integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b" + integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" + integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== + +"@types/d3-polygon@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" + integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== + +"@types/d3-quadtree@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" + integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== + +"@types/d3-random@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" + integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== + +"@types/d3-scale-chromatic@*", "@types/d3-scale-chromatic@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" + integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== + +"@types/d3-scale@*": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3" + integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w== + +"@types/d3-shape@*": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.8.tgz#d1516cc508753be06852cd06758e3bb54a22b0e3" + integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" + integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== + +"@types/d3-time@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/d3-timer@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/d3-transition@*": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706" + integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@7.4.3": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" + integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + +"@types/debug@^4.0.0": + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" + integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== + dependencies: + "@types/ms" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/geojson@*": + version "7946.0.16" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" + integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== + +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@types/luxon@3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.6.2.tgz#be6536931801f437eafcb9c0f6d6781f72308041" + integrity sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw== + +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/prop-types@^15.7.12": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + +"@types/react-dom@19.1.6": + version "19.1.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.6.tgz#4af629da0e9f9c0f506fc4d1caa610399c595d64" + integrity sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== + +"@types/react-transition-group@^4.4.6": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@19.1.8": + version "19.1.8" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.8.tgz#ff8395f2afb764597265ced15f8dddb0720ae1c3" + integrity sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== + dependencies: + csstype "^3.0.2" + +"@types/react@>=16.9.11": + version "19.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== + dependencies: + csstype "^3.2.2" + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/warning@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.4.tgz#ebc0c83180dc83994d902bbd51ab0af8a445b1f9" + integrity sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg== + +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +bootstrap@5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" + integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +classnames@*, classnames@^2.3.1, classnames@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +commander@7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +cookie@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + +csstype@^3.0.2, csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.2.tgz#01fdb46b58beb1f55b10b42ad70b6e344d5eb2ae" + integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg== + +d3-geo@3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3, d3-scale-chromatic@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +date-arithmetic@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-arithmetic/-/date-arithmetic-4.1.0.tgz#e5d6434e9deb71f79760a37b729e4a515e730ddf" + integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg== + +debug@^4.0.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decode-named-character-reference@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== + dependencies: + character-entities "^2.0.0" + +delaunator@5: + version "5.1.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.1.0.tgz#d13271fbf3aff6753f9ea6e235557f20901046ea" + integrity sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ== + dependencies: + robust-predicates "^3.0.2" + +dequal@^2.0.0, dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fetch-cookie@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-2.2.0.tgz#01086b6b5b1c3e08f15ffd8647b02ca100377365" + integrity sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ== + dependencies: + set-cookie-parser "^2.4.8" + tough-cookie "^4.0.0" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +loose-envify@^1.0.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +luxon@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.1.tgz#9bd09aa84a56afb00c57ea78a8ec5bd16eb24ec0" + integrity sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg== + +mdast-util-from-markdown@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" + integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +popper.js@1.16.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + +prop-types-extra@^1.1.0, prop-types-extra@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== + dependencies: + react-is "^16.3.2" + warning "^4.0.0" + +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== + +psl@^1.1.33: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +react-bootstrap@2.10.10: + version "2.10.10" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" + integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== + dependencies: + "@babel/runtime" "^7.24.7" + "@restart/hooks" "^0.4.9" + "@restart/ui" "^1.9.4" + "@types/prop-types" "^15.7.12" + "@types/react-transition-group" "^4.4.6" + classnames "^2.3.2" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.5" + uncontrollable "^7.2.1" + warning "^4.0.3" + +react-dom@19.1.2: + version "19.1.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.2.tgz#18ce6b14f682f4871a69cc6b2568d42af2cd5a33" + integrity sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g== + dependencies: + scheduler "^0.26.0" + +react-is@^16.13.1, react-is@^16.3.2: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-markdown@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +react-router-dom@7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.7.0.tgz#25be91ba474b934cdeb36b54551789c2361fbcad" + integrity sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA== + dependencies: + react-router "7.7.0" + +react-router@7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.7.0.tgz#9e5dc8a3be0c7a1931fcff6f32624b66a285b56a" + integrity sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + +react-transition-group@^4.4.2, react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react-widgets-up@6.0.14: + version "6.0.14" + resolved "https://registry.yarnpkg.com/react-widgets-up/-/react-widgets-up-6.0.14.tgz#bc52a7a924102b98bf35014c3b5a5ca2884402d1" + integrity sha512-EbZm8iB/Ay/v32OHSVOAc2Z2aRp3lVAO6L5gIql7eCEKTjH19g+0OTh8lM6IdAVI1u2RCbCsj1j+Zns1rURwMw== + dependencies: + "@restart/hooks" "^0.4.5" + "@types/classnames" "^2.3.1" + classnames "^2.3.1" + date-arithmetic "^4.0.1" + dom-helpers "^5.2.1" + prop-types-extra "^1.1.1" + react-transition-group "^4.4.2" + tiny-warning "^1.0.3" + uncontrollable "^7.2.1" + +react@19.1.2: + version "19.1.2" + resolved "https://registry.yarnpkg.com/react/-/react-19.1.2.tgz#fd75c4f684b52fe45101381535c58486f4407545" + integrity sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w== + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +robust-predicates@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.3.tgz#1099061b3349e2c5abec6c2ab0acd440d24d4062" + integrity sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA== + +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scheduler@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" + integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== + +semver@^7.7.2: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +set-cookie-parser@^2.4.8, set-cookie-parser@^2.6.0: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +style-to-js@^1.0.0: + version "1.1.21" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== + dependencies: + style-to-object "1.0.14" + +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== + dependencies: + inline-style-parser "0.2.7" + +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tough-cookie@^4.0.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + +uncontrollable@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-is@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" + integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +ws@^7.4.5: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== From b6251d6db0f850a55b1eb61970b9d025c1b6572e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:09:08 +0000 Subject: [PATCH 14/21] Remove Signum/yarn.lock Framework is used as a submodule; the parent project manages the lockfile. https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Signum/yarn.lock | 1627 ---------------------------------------------- 1 file changed, 1627 deletions(-) delete mode 100644 Signum/yarn.lock diff --git a/Signum/yarn.lock b/Signum/yarn.lock deleted file mode 100644 index 2024806def..0000000000 --- a/Signum/yarn.lock +++ /dev/null @@ -1,1627 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": - version "7.29.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.2.tgz#9a6e2d05f4b6692e1801cd4fb176ad823930ed5e" - integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== - -"@fortawesome/fontawesome-common-types@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470" - integrity sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg== - -"@fortawesome/fontawesome-svg-core@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz#0ac6013724d5cc327c1eb81335b91300a4fce2f2" - integrity sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/free-brands-svg-icons@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz#4ebee8098f31da5446dda81edc344025eb59b27e" - integrity sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/free-regular-svg-icons@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz#f1651e55e6651a15589b0569516208f9c65f96db" - integrity sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/free-solid-svg-icons@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz#fe25883b5eb8464a82918599950d283c465b57f6" - integrity sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/react-fontawesome@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-3.0.0.tgz#20ed596fc402d6f607511d3ec33f325794fb0be9" - integrity sha512-x6boc1RLEjf/QPrMS20VJcabTZeGCb1hbwNybPPLjJohGPowXfjOpwQlVK6aH6MVKfCq2JXeHRIlx+tYpS18FA== - dependencies: - semver "^7.7.2" - -"@microsoft/signalr@8.0.7": - version "8.0.7" - resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.7.tgz#94419ddbf9418753e493f4ae4c13990316ec2ea5" - integrity sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw== - dependencies: - abort-controller "^3.0.0" - eventsource "^2.0.2" - fetch-cookie "^2.0.3" - node-fetch "^2.6.7" - ws "^7.4.5" - -"@popperjs/core@^2.11.8": - version "2.11.8" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" - integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - -"@react-aria/ssr@^3.5.0": - version "3.9.10" - resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6" - integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== - dependencies: - "@swc/helpers" "^0.5.0" - -"@restart/hooks@^0.4.5", "@restart/hooks@^0.4.9": - version "0.4.16" - resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" - integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== - dependencies: - dequal "^2.0.3" - -"@restart/hooks@^0.5.0": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" - integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== - dependencies: - dequal "^2.0.3" - -"@restart/ui@^1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" - integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== - dependencies: - "@babel/runtime" "^7.26.0" - "@popperjs/core" "^2.11.8" - "@react-aria/ssr" "^3.5.0" - "@restart/hooks" "^0.5.0" - "@types/warning" "^3.0.3" - dequal "^2.0.3" - dom-helpers "^5.2.0" - uncontrollable "^8.0.4" - warning "^4.0.3" - -"@swc/helpers@^0.5.0": - version "0.5.21" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.21.tgz#0b1b020317ee1282860ca66f7e9a7c7790f05ae0" - integrity sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg== - dependencies: - tslib "^2.8.0" - -"@types/classnames@^2.3.1": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.4.tgz#1a1fdf5023ef216219f13e702543f9ce9b394560" - integrity sha512-dwmfrMMQb9ujX1uYGvB5ERDlOzBNywnZAZBtOe107/hORWP05ESgU4QyaanZMWYYfd2BzrG78y13/Bju8IQcMQ== - dependencies: - classnames "*" - -"@types/d3-array@*": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.2.tgz#e02151464d02d4a1b44646d0fcdb93faf88fde8c" - integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== - -"@types/d3-axis@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" - integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== - dependencies: - "@types/d3-selection" "*" - -"@types/d3-brush@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" - integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== - dependencies: - "@types/d3-selection" "*" - -"@types/d3-chord@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" - integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== - -"@types/d3-color@*": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" - integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== - -"@types/d3-contour@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" - integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== - dependencies: - "@types/d3-array" "*" - "@types/geojson" "*" - -"@types/d3-delaunay@*": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" - integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== - -"@types/d3-dispatch@*": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz#ef004d8a128046cfce434d17182f834e44ef95b2" - integrity sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA== - -"@types/d3-drag@*": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" - integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== - dependencies: - "@types/d3-selection" "*" - -"@types/d3-dsv@*": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" - integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== - -"@types/d3-ease@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" - integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== - -"@types/d3-fetch@*": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" - integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== - dependencies: - "@types/d3-dsv" "*" - -"@types/d3-force@*": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" - integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== - -"@types/d3-format@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" - integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== - -"@types/d3-geo@*": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" - integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== - dependencies: - "@types/geojson" "*" - -"@types/d3-hierarchy@*": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b" - integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== - -"@types/d3-interpolate@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" - integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== - dependencies: - "@types/d3-color" "*" - -"@types/d3-path@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" - integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== - -"@types/d3-polygon@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" - integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== - -"@types/d3-quadtree@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" - integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== - -"@types/d3-random@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" - integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== - -"@types/d3-scale-chromatic@*", "@types/d3-scale-chromatic@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" - integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== - -"@types/d3-scale@*": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" - integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== - dependencies: - "@types/d3-time" "*" - -"@types/d3-selection@*": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3" - integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w== - -"@types/d3-shape@*": - version "3.1.8" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.8.tgz#d1516cc508753be06852cd06758e3bb54a22b0e3" - integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w== - dependencies: - "@types/d3-path" "*" - -"@types/d3-time-format@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" - integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== - -"@types/d3-time@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" - integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== - -"@types/d3-timer@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" - integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== - -"@types/d3-transition@*": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706" - integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg== - dependencies: - "@types/d3-selection" "*" - -"@types/d3-zoom@*": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" - integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== - dependencies: - "@types/d3-interpolate" "*" - "@types/d3-selection" "*" - -"@types/d3@7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" - integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== - dependencies: - "@types/d3-array" "*" - "@types/d3-axis" "*" - "@types/d3-brush" "*" - "@types/d3-chord" "*" - "@types/d3-color" "*" - "@types/d3-contour" "*" - "@types/d3-delaunay" "*" - "@types/d3-dispatch" "*" - "@types/d3-drag" "*" - "@types/d3-dsv" "*" - "@types/d3-ease" "*" - "@types/d3-fetch" "*" - "@types/d3-force" "*" - "@types/d3-format" "*" - "@types/d3-geo" "*" - "@types/d3-hierarchy" "*" - "@types/d3-interpolate" "*" - "@types/d3-path" "*" - "@types/d3-polygon" "*" - "@types/d3-quadtree" "*" - "@types/d3-random" "*" - "@types/d3-scale" "*" - "@types/d3-scale-chromatic" "*" - "@types/d3-selection" "*" - "@types/d3-shape" "*" - "@types/d3-time" "*" - "@types/d3-time-format" "*" - "@types/d3-timer" "*" - "@types/d3-transition" "*" - "@types/d3-zoom" "*" - -"@types/debug@^4.0.0": - version "4.1.13" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" - integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== - dependencies: - "@types/ms" "*" - -"@types/estree-jsx@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" - integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== - dependencies: - "@types/estree" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - -"@types/geojson@*": - version "7946.0.16" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" - integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== - -"@types/hast@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" - integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== - dependencies: - "@types/unist" "*" - -"@types/luxon@3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.6.2.tgz#be6536931801f437eafcb9c0f6d6781f72308041" - integrity sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw== - -"@types/mdast@^4.0.0": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" - integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== - dependencies: - "@types/unist" "*" - -"@types/ms@*": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" - integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== - -"@types/prop-types@^15.7.12": - version "15.7.15" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" - integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== - -"@types/react-dom@19.1.6": - version "19.1.6" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.6.tgz#4af629da0e9f9c0f506fc4d1caa610399c595d64" - integrity sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== - -"@types/react-transition-group@^4.4.6": - version "4.4.12" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" - integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== - -"@types/react@19.1.8": - version "19.1.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.8.tgz#ff8395f2afb764597265ced15f8dddb0720ae1c3" - integrity sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== - dependencies: - csstype "^3.0.2" - -"@types/react@>=16.9.11": - version "19.2.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" - integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== - dependencies: - csstype "^3.2.2" - -"@types/unist@*", "@types/unist@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" - integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== - -"@types/unist@^2.0.0": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" - integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== - -"@types/warning@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.4.tgz#ebc0c83180dc83994d902bbd51ab0af8a445b1f9" - integrity sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg== - -"@ungap/structured-clone@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -bail@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" - integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== - -bootstrap@5.3.7: - version "5.3.7" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" - integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== - -ccount@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" - integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== - -character-entities-html4@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" - integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== - -character-entities-legacy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" - integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== - -character-entities@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" - integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== - -character-reference-invalid@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" - integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== - -classnames@*, classnames@^2.3.1, classnames@^2.3.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" - integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== - -comma-separated-tokens@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" - integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== - -commander@7: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -cookie@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" - integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== - -csstype@^3.0.2, csstype@^3.2.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" - integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== - -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" - integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== - dependencies: - internmap "1 - 2" - -d3-axis@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" - integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== - -d3-brush@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" - integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== - dependencies: - d3-dispatch "1 - 3" - d3-drag "2 - 3" - d3-interpolate "1 - 3" - d3-selection "3" - d3-transition "3" - -d3-chord@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" - integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== - dependencies: - d3-path "1 - 3" - -"d3-color@1 - 3", d3-color@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" - integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== - -d3-contour@4: - version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" - integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== - dependencies: - d3-array "^3.2.0" - -d3-delaunay@6: - version "6.0.4" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" - integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== - dependencies: - delaunator "5" - -"d3-dispatch@1 - 3", d3-dispatch@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" - integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== - -"d3-drag@2 - 3", d3-drag@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" - integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== - dependencies: - d3-dispatch "1 - 3" - d3-selection "3" - -"d3-dsv@1 - 3", d3-dsv@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" - integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== - dependencies: - commander "7" - iconv-lite "0.6" - rw "1" - -"d3-ease@1 - 3", d3-ease@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" - integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== - -d3-fetch@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" - integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== - dependencies: - d3-dsv "1 - 3" - -d3-force@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" - integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== - dependencies: - d3-dispatch "1 - 3" - d3-quadtree "1 - 3" - d3-timer "1 - 3" - -"d3-format@1 - 3", d3-format@3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.2.tgz#01fdb46b58beb1f55b10b42ad70b6e344d5eb2ae" - integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg== - -d3-geo@3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" - integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== - dependencies: - d3-array "2.5.0 - 3" - -d3-hierarchy@3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" - integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== - -"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" - integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== - dependencies: - d3-color "1 - 3" - -"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" - integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== - -d3-polygon@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" - integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== - -"d3-quadtree@1 - 3", d3-quadtree@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" - integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== - -d3-random@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" - integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== - -d3-scale-chromatic@3, d3-scale-chromatic@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" - integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== - dependencies: - d3-color "1 - 3" - d3-interpolate "1 - 3" - -d3-scale@4: - version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" - integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== - dependencies: - d3-array "2.10.0 - 3" - d3-format "1 - 3" - d3-interpolate "1.2.0 - 3" - d3-time "2.1.1 - 3" - d3-time-format "2 - 4" - -"d3-selection@2 - 3", d3-selection@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" - integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== - -d3-shape@3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" - integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== - dependencies: - d3-path "^3.1.0" - -"d3-time-format@2 - 4", d3-time-format@4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" - integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== - dependencies: - d3-time "1 - 3" - -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" - integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== - dependencies: - d3-array "2 - 3" - -"d3-timer@1 - 3", d3-timer@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" - integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== - -"d3-transition@2 - 3", d3-transition@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" - integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== - dependencies: - d3-color "1 - 3" - d3-dispatch "1 - 3" - d3-ease "1 - 3" - d3-interpolate "1 - 3" - d3-timer "1 - 3" - -d3-zoom@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" - integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== - dependencies: - d3-dispatch "1 - 3" - d3-drag "2 - 3" - d3-interpolate "1 - 3" - d3-selection "2 - 3" - d3-transition "2 - 3" - -d3@7.9.0: - version "7.9.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" - integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== - dependencies: - d3-array "3" - d3-axis "3" - d3-brush "3" - d3-chord "3" - d3-color "3" - d3-contour "4" - d3-delaunay "6" - d3-dispatch "3" - d3-drag "3" - d3-dsv "3" - d3-ease "3" - d3-fetch "3" - d3-force "3" - d3-format "3" - d3-geo "3" - d3-hierarchy "3" - d3-interpolate "3" - d3-path "3" - d3-polygon "3" - d3-quadtree "3" - d3-random "3" - d3-scale "4" - d3-scale-chromatic "3" - d3-selection "3" - d3-shape "3" - d3-time "3" - d3-time-format "4" - d3-timer "3" - d3-transition "3" - d3-zoom "3" - -date-arithmetic@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/date-arithmetic/-/date-arithmetic-4.1.0.tgz#e5d6434e9deb71f79760a37b729e4a515e730ddf" - integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg== - -debug@^4.0.0: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decode-named-character-reference@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" - integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== - dependencies: - character-entities "^2.0.0" - -delaunator@5: - version "5.1.0" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.1.0.tgz#d13271fbf3aff6753f9ea6e235557f20901046ea" - integrity sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ== - dependencies: - robust-predicates "^3.0.2" - -dequal@^2.0.0, dequal@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - -devlop@^1.0.0, devlop@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" - integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== - dependencies: - dequal "^2.0.0" - -dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - -estree-util-is-identifier-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" - integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventsource@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" - integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fetch-cookie@^2.0.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-2.2.0.tgz#01086b6b5b1c3e08f15ffd8647b02ca100377365" - integrity sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ== - dependencies: - set-cookie-parser "^2.4.8" - tough-cookie "^4.0.0" - -hast-util-to-jsx-runtime@^2.0.0: - version "2.3.6" - resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" - integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== - dependencies: - "@types/estree" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/unist" "^3.0.0" - comma-separated-tokens "^2.0.0" - devlop "^1.0.0" - estree-util-is-identifier-name "^3.0.0" - hast-util-whitespace "^3.0.0" - mdast-util-mdx-expression "^2.0.0" - mdast-util-mdx-jsx "^3.0.0" - mdast-util-mdxjs-esm "^2.0.0" - property-information "^7.0.0" - space-separated-tokens "^2.0.0" - style-to-js "^1.0.0" - unist-util-position "^5.0.0" - vfile-message "^4.0.0" - -hast-util-whitespace@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" - integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== - dependencies: - "@types/hast" "^3.0.0" - -html-url-attributes@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" - integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== - -iconv-lite@0.6: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -inline-style-parser@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" - integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== - -"internmap@1 - 2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" - integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-alphabetical@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" - integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== - -is-alphanumerical@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" - integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== - dependencies: - is-alphabetical "^2.0.0" - is-decimal "^2.0.0" - -is-decimal@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" - integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== - -is-hexadecimal@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" - integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== - -is-plain-obj@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" - integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -longest-streak@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" - integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== - -loose-envify@^1.0.0, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -luxon@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.1.tgz#9bd09aa84a56afb00c57ea78a8ec5bd16eb24ec0" - integrity sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg== - -mdast-util-from-markdown@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" - integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== - dependencies: - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - mdast-util-to-string "^4.0.0" - micromark "^4.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-decode-string "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - unist-util-stringify-position "^4.0.0" - -mdast-util-mdx-expression@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" - integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - -mdast-util-mdx-jsx@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" - integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - ccount "^2.0.0" - devlop "^1.1.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - parse-entities "^4.0.0" - stringify-entities "^4.0.0" - unist-util-stringify-position "^4.0.0" - vfile-message "^4.0.0" - -mdast-util-mdxjs-esm@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" - integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== - dependencies: - "@types/estree-jsx" "^1.0.0" - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - mdast-util-from-markdown "^2.0.0" - mdast-util-to-markdown "^2.0.0" - -mdast-util-phrasing@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" - integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== - dependencies: - "@types/mdast" "^4.0.0" - unist-util-is "^6.0.0" - -mdast-util-to-hast@^13.0.0: - version "13.2.1" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" - integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - "@ungap/structured-clone" "^1.0.0" - devlop "^1.0.0" - micromark-util-sanitize-uri "^2.0.0" - trim-lines "^3.0.0" - unist-util-position "^5.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - -mdast-util-to-markdown@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" - integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== - dependencies: - "@types/mdast" "^4.0.0" - "@types/unist" "^3.0.0" - longest-streak "^3.0.0" - mdast-util-phrasing "^4.0.0" - mdast-util-to-string "^4.0.0" - micromark-util-classify-character "^2.0.0" - micromark-util-decode-string "^2.0.0" - unist-util-visit "^5.0.0" - zwitch "^2.0.0" - -mdast-util-to-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" - integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== - dependencies: - "@types/mdast" "^4.0.0" - -micromark-core-commonmark@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" - integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== - dependencies: - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - micromark-factory-destination "^2.0.0" - micromark-factory-label "^2.0.0" - micromark-factory-space "^2.0.0" - micromark-factory-title "^2.0.0" - micromark-factory-whitespace "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-classify-character "^2.0.0" - micromark-util-html-tag-name "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-resolve-all "^2.0.0" - micromark-util-subtokenize "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-destination@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" - integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-label@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" - integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== - dependencies: - devlop "^1.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-space@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" - integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-title@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" - integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== - dependencies: - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-factory-whitespace@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" - integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== - dependencies: - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-character@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" - integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== - dependencies: - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-chunked@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" - integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-classify-character@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" - integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-combine-extensions@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" - integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== - dependencies: - micromark-util-chunked "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-decode-numeric-character-reference@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" - integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-decode-string@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" - integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== - dependencies: - decode-named-character-reference "^1.0.0" - micromark-util-character "^2.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-symbol "^2.0.0" - -micromark-util-encode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" - integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== - -micromark-util-html-tag-name@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" - integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== - -micromark-util-normalize-identifier@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" - integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== - dependencies: - micromark-util-symbol "^2.0.0" - -micromark-util-resolve-all@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" - integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== - dependencies: - micromark-util-types "^2.0.0" - -micromark-util-sanitize-uri@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" - integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== - dependencies: - micromark-util-character "^2.0.0" - micromark-util-encode "^2.0.0" - micromark-util-symbol "^2.0.0" - -micromark-util-subtokenize@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" - integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== - dependencies: - devlop "^1.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -micromark-util-symbol@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" - integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== - -micromark-util-types@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" - integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== - -micromark@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" - integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== - dependencies: - "@types/debug" "^4.0.0" - debug "^4.0.0" - decode-named-character-reference "^1.0.0" - devlop "^1.0.0" - micromark-core-commonmark "^2.0.0" - micromark-factory-space "^2.0.0" - micromark-util-character "^2.0.0" - micromark-util-chunked "^2.0.0" - micromark-util-combine-extensions "^2.0.0" - micromark-util-decode-numeric-character-reference "^2.0.0" - micromark-util-encode "^2.0.0" - micromark-util-normalize-identifier "^2.0.0" - micromark-util-resolve-all "^2.0.0" - micromark-util-sanitize-uri "^2.0.0" - micromark-util-subtokenize "^2.0.0" - micromark-util-symbol "^2.0.0" - micromark-util-types "^2.0.0" - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -parse-entities@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" - integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== - dependencies: - "@types/unist" "^2.0.0" - character-entities-legacy "^3.0.0" - character-reference-invalid "^2.0.0" - decode-named-character-reference "^1.0.0" - is-alphanumerical "^2.0.0" - is-decimal "^2.0.0" - is-hexadecimal "^2.0.0" - -popper.js@1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" - integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== - -prop-types-extra@^1.1.0, prop-types-extra@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" - integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== - dependencies: - react-is "^16.3.2" - warning "^4.0.0" - -prop-types@^15.6.2, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -property-information@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" - integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== - -psl@^1.1.33: - version "1.15.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" - integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== - dependencies: - punycode "^2.3.1" - -punycode@^2.1.1, punycode@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -react-bootstrap@2.10.10: - version "2.10.10" - resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" - integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== - dependencies: - "@babel/runtime" "^7.24.7" - "@restart/hooks" "^0.4.9" - "@restart/ui" "^1.9.4" - "@types/prop-types" "^15.7.12" - "@types/react-transition-group" "^4.4.6" - classnames "^2.3.2" - dom-helpers "^5.2.1" - invariant "^2.2.4" - prop-types "^15.8.1" - prop-types-extra "^1.1.0" - react-transition-group "^4.4.5" - uncontrollable "^7.2.1" - warning "^4.0.3" - -react-dom@19.1.2: - version "19.1.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.2.tgz#18ce6b14f682f4871a69cc6b2568d42af2cd5a33" - integrity sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g== - dependencies: - scheduler "^0.26.0" - -react-is@^16.13.1, react-is@^16.3.2: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - -react-markdown@10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" - integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - devlop "^1.0.0" - hast-util-to-jsx-runtime "^2.0.0" - html-url-attributes "^3.0.0" - mdast-util-to-hast "^13.0.0" - remark-parse "^11.0.0" - remark-rehype "^11.0.0" - unified "^11.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - -react-router-dom@7.7.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.7.0.tgz#25be91ba474b934cdeb36b54551789c2361fbcad" - integrity sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA== - dependencies: - react-router "7.7.0" - -react-router@7.7.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.7.0.tgz#9e5dc8a3be0c7a1931fcff6f32624b66a285b56a" - integrity sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw== - dependencies: - cookie "^1.0.1" - set-cookie-parser "^2.6.0" - -react-transition-group@^4.4.2, react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react-widgets-up@6.0.14: - version "6.0.14" - resolved "https://registry.yarnpkg.com/react-widgets-up/-/react-widgets-up-6.0.14.tgz#bc52a7a924102b98bf35014c3b5a5ca2884402d1" - integrity sha512-EbZm8iB/Ay/v32OHSVOAc2Z2aRp3lVAO6L5gIql7eCEKTjH19g+0OTh8lM6IdAVI1u2RCbCsj1j+Zns1rURwMw== - dependencies: - "@restart/hooks" "^0.4.5" - "@types/classnames" "^2.3.1" - classnames "^2.3.1" - date-arithmetic "^4.0.1" - dom-helpers "^5.2.1" - prop-types-extra "^1.1.1" - react-transition-group "^4.4.2" - tiny-warning "^1.0.3" - uncontrollable "^7.2.1" - -react@19.1.2: - version "19.1.2" - resolved "https://registry.yarnpkg.com/react/-/react-19.1.2.tgz#fd75c4f684b52fe45101381535c58486f4407545" - integrity sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w== - -remark-parse@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" - integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== - dependencies: - "@types/mdast" "^4.0.0" - mdast-util-from-markdown "^2.0.0" - micromark-util-types "^2.0.0" - unified "^11.0.0" - -remark-rehype@^11.0.0: - version "11.1.2" - resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" - integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== - dependencies: - "@types/hast" "^3.0.0" - "@types/mdast" "^4.0.0" - mdast-util-to-hast "^13.0.0" - unified "^11.0.0" - vfile "^6.0.0" - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -robust-predicates@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.3.tgz#1099061b3349e2c5abec6c2ab0acd440d24d4062" - integrity sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA== - -rw@1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -scheduler@^0.26.0: - version "0.26.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" - integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== - -semver@^7.7.2: - version "7.7.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" - integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== - -set-cookie-parser@^2.4.8, set-cookie-parser@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" - integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== - -space-separated-tokens@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" - integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== - -stringify-entities@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" - integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== - dependencies: - character-entities-html4 "^2.0.0" - character-entities-legacy "^3.0.0" - -style-to-js@^1.0.0: - version "1.1.21" - resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" - integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== - dependencies: - style-to-object "1.0.14" - -style-to-object@1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" - integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== - dependencies: - inline-style-parser "0.2.7" - -tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -tough-cookie@^4.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" - integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-lines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" - integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== - -trough@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" - integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== - -tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -uncontrollable@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" - integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== - dependencies: - "@babel/runtime" "^7.6.3" - "@types/react" ">=16.9.11" - invariant "^2.2.4" - react-lifecycles-compat "^3.0.4" - -uncontrollable@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" - integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== - -unified@^11.0.0: - version "11.0.5" - resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" - integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== - dependencies: - "@types/unist" "^3.0.0" - bail "^2.0.0" - devlop "^1.0.0" - extend "^3.0.0" - is-plain-obj "^4.0.0" - trough "^2.0.0" - vfile "^6.0.0" - -unist-util-is@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" - integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== - dependencies: - "@types/unist" "^3.0.0" - -unist-util-position@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" - integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== - dependencies: - "@types/unist" "^3.0.0" - -unist-util-stringify-position@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" - integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== - dependencies: - "@types/unist" "^3.0.0" - -unist-util-visit-parents@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" - integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== - dependencies: - "@types/unist" "^3.0.0" - unist-util-is "^6.0.0" - -unist-util-visit@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" - integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== - dependencies: - "@types/unist" "^3.0.0" - unist-util-is "^6.0.0" - unist-util-visit-parents "^6.0.0" - -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -vfile-message@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" - integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== - dependencies: - "@types/unist" "^3.0.0" - unist-util-stringify-position "^4.0.0" - -vfile@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" - integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== - dependencies: - "@types/unist" "^3.0.0" - vfile-message "^4.0.0" - -warning@^4.0.0, warning@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -ws@^7.4.5: - version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== - -zwitch@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== From b19776d59029dcfff7bdf977a62d2e3ab38a4ee9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:13:22 +0000 Subject: [PATCH 15/21] Add disableSourceOfProjectReferenceRedirect to tsconfig.base.json Without this, TypeScript silently falls back to including source files from referenced projects when their ts_out hasn't been built, generating stray .d.ts/.d.ts.map files in the wrong project's output directory. With this flag, TypeScript errors immediately instead, making missing project references obvious. https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- tsconfig.base.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.base.json b/tsconfig.base.json index 6377f302c9..2e41278ef0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,7 @@ "strict": true, "noImplicitOverride": true, "declarationMap": true, + "disableSourceOfProjectReferenceRedirect": true, "noUncheckedSideEffectImports": false, "lib": [ "ESNext", From fb084ab9c0973f79d1d0bd509f1064d7470415bc Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Fri, 3 Apr 2026 23:14:36 +0200 Subject: [PATCH 16/21] AgentSkill --- Extensions/Signum.Agent/Templates/AgentSkill.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/AgentSkill.tsx index 3cd1c1a4cc..18f2419426 100644 --- a/Extensions/Signum.Agent/Templates/AgentSkill.tsx +++ b/Extensions/Signum.Agent/Templates/AgentSkill.tsx @@ -4,7 +4,6 @@ import { TypeContext } from '@framework/TypeContext' import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded } from '../Signum.Agent' import { AgentSkillClient, SkillPropertyMeta } from '../AgentSkillClient' import { useAPI, useForceUpdate } from '@framework/Hooks' -import { AgentSkillClient } from '../AgentSkillClient' import { MarkdownLine } from '@framework/Lines/MarkdownLine' import { DiffDocument } from '../../Signum.DiffLog/Templates/DiffDocument' import { LinkButton } from '@framework/Basics/LinkButton' From e9dba375ac00851a366dbddc89bb68c460a3dccd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:47:29 +0000 Subject: [PATCH 17/21] AgentSkill: polymorphic sub-skills, default roots, IAgentOutput cleanup IAgentOutput: - Remove default implementations; move them all to NullAgentOutput AgentSkillSubSkillEmbedded.Skill: - Changed from Lite to [ImplementedBy] Lite so a sub-skill can be an AgentSkillEntity (customised) or an AgentSkillCodeEntity (default, no DB entity required) DefaultRoots registry: - AgentSkillLogic.RegisterDefaultRoot(useCase, factory) registers a Func used as fallback when no DB entity exists for that use case, and as the source for ConstructFromUseCase - RootsByUseCase falls back to factory if no active entity found in DB AgentSkillOperation.CreateFromUseCase: - ConstructFrom: calls the registered factory, converts the code tree to AgentSkillEntity/AgentSkillCodeEntity references (NeedsEntity decides per node), saves child entities AgentSkillPropertyAttribute.ConvertValueToString: - Reverse of ConvertFromString; used by NeedsEntity to detect non-default property values when converting a factory tree to DB AgentSkillCode.WithSubSkill(): - Fluent builder for constructing default trees in factory functions https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillEntity.cs | 5 +- Extensions/Signum.Agent/AgentSkillLogic.cs | 155 ++++++++++++++++++-- Extensions/Signum.Agent/ChatbotLogic.cs | 28 ++-- Extensions/Signum.Agent/Signum.Agent.ts | 3 +- 4 files changed, 169 insertions(+), 22 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs index 432da029dc..1185598527 100644 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -78,7 +78,9 @@ public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity public class AgentSkillSubSkillEmbedded : EmbeddedEntity { - public Lite Skill { get; set; } + // Can reference either an AgentSkillEntity (customised) or AgentSkillCodeEntity (default, no DB entity needed) + [ImplementedBy(typeof(AgentSkillEntity), typeof(AgentSkillCodeEntity))] + public Lite Skill { get; set; } public SkillActivation Activation { get; set; } } @@ -88,4 +90,5 @@ public static class AgentSkillOperation { public static ExecuteSymbol Save = null!; public static DeleteSymbol Delete = null!; + public static ConstructSymbol.From CreateFromUseCase = null!; } diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index c21bda1905..8464a5e704 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -21,6 +21,7 @@ public static class AgentSkillLogic public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); public static Dictionary RegisteredCodes = new(); + public static Dictionary> DefaultRoots = new(); public static ConversationSumarizerSkill ConversationSumarizerSkill = null!; public static QuestionSumarizerSkill QuestionSumarizerSkill = null!; @@ -32,6 +33,11 @@ public static void RegisterCode() where T : AgentSkillCode RegisteredCodes[typeof(T).FullName!] = typeof(T); } + public static void RegisterDefaultRoot(AgentUseCaseSymbol useCase, Func factory) + { + DefaultRoots[useCase] = factory; + } + public static void Start(SchemaBuilder sb) { if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) @@ -65,6 +71,19 @@ public static void Start(SchemaBuilder sb) e.ShortDescription, }); + new Graph.ConstructFrom(AgentSkillOperation.CreateFromUseCase) + { + Construct = (useCase, _) => + { + if (!DefaultRoots.TryGetValue(useCase, out var factory)) + return new AgentSkillEntity { UseCase = useCase }; + + var root = factory(); + var codeEntities = Database.Query().ToDictionary(e => e.FullClassName); + return ConvertToEntity(root, useCase, codeEntities); + } + }.Register(); + sb.Schema.EntityEvents().Saving += entity => { if (!entity.IsNew && entity.SubSkills.IsGraphModified) @@ -73,17 +92,25 @@ public static void Start(SchemaBuilder sb) RootsByUseCase = sb.GlobalLazy(() => { - var allEntities = Database.Query() - .ToList() - .ToDictionary(e => e.ToLite()); + var allEntities = Database.Query().ToList(); + var allEntitiesById = allEntities.ToDictionary(e => e.Id); + var codeFullNames = Database.Query() + .Select(e => new { e.Id, e.FullClassName }) + .ToDictionary(e => e.Id, e => e.FullClassName); - return allEntities.Values + var fromDb = allEntities .Where(e => e.UseCase != null && e.Active) .GroupBy(e => e.UseCase!) .ToDictionary( g => g.Key, - g => ResolveCode(g.SingleEx(), allEntities) + g => ResolveCode(g.SingleEx(), allEntitiesById, codeFullNames) ); + + // Fall back to registered default factories for use cases not in DB + foreach (var (useCase, factory) in DefaultRoots) + fromDb.TryAdd(useCase, factory()); + + return fromDb; }, new InvalidateWith(typeof(AgentSkillEntity))); } @@ -113,7 +140,8 @@ static List GenerateCodeEntities() => public static AgentSkillCode ResolveCode( AgentSkillEntity entity, - Dictionary, AgentSkillEntity> allEntities) + Dictionary allEntitiesById, + Dictionary codeFullNames) { var type = RegisteredCodes.GetOrThrow(entity.SkillCode.FullClassName, $"AgentSkillCode type '{entity.SkillCode.FullClassName}' is not registered."); @@ -129,7 +157,22 @@ public static AgentSkillCode ResolveCode( code.ApplyPropertyOverrides(entity); foreach (var ss in entity.SubSkills) - code.SubSkills.Add((ResolveCode(allEntities.GetOrThrow(ss.Skill), allEntities), ss.Activation)); + { + AgentSkillCode subCode; + if (ss.Skill.EntityType == typeof(AgentSkillEntity)) + { + var subEntity = allEntitiesById.GetOrThrow(ss.Skill.Id); + subCode = ResolveCode(subEntity, allEntitiesById, codeFullNames); + } + else + { + var fullClassName = codeFullNames.GetOrThrow(ss.Skill.Id); + var subType = RegisteredCodes.GetOrThrow(fullClassName, + $"AgentSkillCode type '{fullClassName}' is not registered."); + subCode = (AgentSkillCode)Activator.CreateInstance(subType)!; + } + code.SubSkills.Add((subCode, ss.Activation)); + } return code; } @@ -143,9 +186,14 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) var graph = DirectedGraph.Generate( allEntities, - e => e.Is(entity) - ? entity.SubSkills.Select(s => s.Skill.RetrieveAndRemember()).ToList() - : e.SubSkills.Select(s => s.Skill.RetrieveAndRemember()).ToList() + e => + { + var subSkills = e.Is(entity) ? entity.SubSkills : e.SubSkills; + return subSkills + .Where(s => s.Skill.EntityType == typeof(AgentSkillEntity)) + .Select(s => (AgentSkillEntity)s.Skill.RetrieveAndRemember()) + .ToList(); + } ); var problems = graph.FeedbackEdgeSet().Edges.ToList(); @@ -159,6 +207,76 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => RootsByUseCase.Value.TryGetC(symbol); + static bool NeedsEntity(AgentSkillCode code) + { + if (code.SubSkills.Any()) return true; + if (code.HasCustomInstructions) return true; + + var defaultCode = (AgentSkillCode)Activator.CreateInstance(code.GetType())!; + if (code.ShortDescription != defaultCode.ShortDescription) return true; + + foreach (var pi in code.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var attr = pi.GetCustomAttribute(); + if (attr == null) continue; + + var currentStr = attr.ConvertValueToString(pi.GetValue(code), pi.PropertyType); + var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); + if (currentStr != defaultStr) return true; + } + + return false; + } + + static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? useCase, + Dictionary codeEntities) + { + var type = code.GetType(); + var defaultCode = (AgentSkillCode)Activator.CreateInstance(type)!; + + var entity = new AgentSkillEntity + { + Name = code.Name, + SkillCode = codeEntities.GetOrThrow(type.FullName!), + Active = true, + UseCase = useCase, + ShortDescription = code.ShortDescription != defaultCode.ShortDescription ? code.ShortDescription : null, + Instructions = code.HasCustomInstructions ? code.OriginalInstructions : null, + }; + + foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var attr = pi.GetCustomAttribute(); + if (attr == null) continue; + + var currentStr = attr.ConvertValueToString(pi.GetValue(code), pi.PropertyType); + var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); + if (currentStr != defaultStr && currentStr != null) + entity.PropertyOverrides.Add(new AgentSkillPropertyOverrideEmbedded + { + PropertyName = pi.Name, + Value = currentStr, + }); + } + + foreach (var (sub, activation) in code.SubSkills) + { + Lite skillLite; + if (NeedsEntity(sub)) + { + var subEntity = ConvertToEntity(sub, null, codeEntities).Save(); + skillLite = (Lite)(object)subEntity.ToLite(); + } + else + { + skillLite = (Lite)(object)codeEntities.GetOrThrow(sub.GetType().FullName!).ToLite(); + } + entity.SubSkills.Add(new AgentSkillSubSkillEmbedded { Skill = skillLite, Activation = activation }); + } + + return entity; + } + public static SkillCodeInfo GetSkillCodeInfo(string fullClassName) { if (!RegisteredCodes.TryGetValue(fullClassName, out var type)) @@ -214,6 +332,8 @@ public class AgentSkillPropertyAttribute : Attribute return ReflectionTools.ChangeType(value, targetType); } + public virtual string? ConvertValueToString(object? value, Type targetType) => value?.ToString(); + public virtual string? ValidateValue(string? value, Type targetType) => null; public virtual string? ValueHint => null; @@ -233,6 +353,12 @@ public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute .ToHashSet(); } + public override string? ConvertValueToString(object? value, Type targetType) + { + if (value is not System.Collections.IEnumerable enumerable) return value?.ToString(); + return enumerable.Cast().Select(q => QueryLogic.GetQueryEntity(q).Key).ToString(", "); + } + public override string? ValidateValue(string? value, Type targetType) { if (value == null) @@ -269,10 +395,17 @@ public string OriginalInstructions get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } set { originalInstructions = value; } } + internal bool HasCustomInstructions => originalInstructions != null; - // Populated from DB at resolve time; never set in code. + // Populated from DB at resolve time, or from code when building a default tree for a factory. public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); + public AgentSkillCode WithSubSkill(AgentSkillCode sub, SkillActivation activation = SkillActivation.Lazy) + { + SubSkills.Add((sub, activation)); + return this; + } + public string GetInstruction(object? context) { var text = OriginalInstructions; diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index 8ea6568573..720d256973 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -388,21 +388,31 @@ public static async Task RunHeadlessAsync( public interface IAgentOutput { - Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - Task OnSummarizationAsync(ChatMessageEntity summaryMsg, CancellationToken ct) => Task.CompletedTask; - Task OnAssistantStartedAsync(CancellationToken ct) => Task.CompletedTask; - Task OnTextChunkAsync(string chunk, CancellationToken ct) => Task.CompletedTask; - Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; - Task OnToolStartAsync(string toolId, string callId, CancellationToken ct) => Task.CompletedTask; - Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct) => Task.CompletedTask; - Task OnTitleUpdatedAsync(string title, CancellationToken ct) => Task.CompletedTask; + Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct); + Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct); + Task OnSummarizationAsync(ChatMessageEntity summaryMsg, CancellationToken ct); + Task OnAssistantStartedAsync(CancellationToken ct); + Task OnTextChunkAsync(string chunk, CancellationToken ct); + Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct); + Task OnToolStartAsync(string toolId, string callId, CancellationToken ct); + Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct); + Task OnTitleUpdatedAsync(string title, CancellationToken ct); } public sealed class NullAgentOutput : IAgentOutput { public static readonly NullAgentOutput Instance = new(); private NullAgentOutput() { } + + public Task OnSystemMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + public Task OnUserQuestionAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + public Task OnSummarizationAsync(ChatMessageEntity summaryMsg, CancellationToken ct) => Task.CompletedTask; + public Task OnAssistantStartedAsync(CancellationToken ct) => Task.CompletedTask; + public Task OnTextChunkAsync(string chunk, CancellationToken ct) => Task.CompletedTask; + public Task OnAssistantMessageAsync(ChatMessageEntity msg, CancellationToken ct) => Task.CompletedTask; + public Task OnToolStartAsync(string toolId, string callId, CancellationToken ct) => Task.CompletedTask; + public Task OnToolFinishedAsync(ChatMessageEntity toolMsg, CancellationToken ct) => Task.CompletedTask; + public Task OnTitleUpdatedAsync(string title, CancellationToken ct) => Task.CompletedTask; } public class ConversationHistory diff --git a/Extensions/Signum.Agent/Signum.Agent.ts b/Extensions/Signum.Agent/Signum.Agent.ts index 5a633ca379..a771659435 100644 --- a/Extensions/Signum.Agent/Signum.Agent.ts +++ b/Extensions/Signum.Agent/Signum.Agent.ts @@ -34,6 +34,7 @@ export interface AgentSkillEntity extends Entities.Entity { export namespace AgentSkillOperation { export const Save : Operations.ExecuteSymbol = registerSymbol("Operation", "AgentSkillOperation.Save"); export const Delete : Operations.DeleteSymbol = registerSymbol("Operation", "AgentSkillOperation.Delete"); + export const CreateFromUseCase : Operations.ConstructSymbol_From = registerSymbol("Operation", "AgentSkillOperation.CreateFromUseCase"); } export const AgentSkillPropertyOverrideEmbedded: Type = new Type("AgentSkillPropertyOverrideEmbedded"); @@ -46,7 +47,7 @@ export interface AgentSkillPropertyOverrideEmbedded extends Entities.EmbeddedEnt export const AgentSkillSubSkillEmbedded: Type = new Type("AgentSkillSubSkillEmbedded"); export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { Type: "AgentSkillSubSkillEmbedded"; - skill: Entities.Lite; + skill: Entities.Lite; activation: SkillActivation; } From bbfa1063b7b687c11baf412efcdac6240305d389 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:53:50 +0000 Subject: [PATCH 18/21] Add AgentSkillEntity.IsDefault(), remove HasCustomInstructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IsDefault() checks no sub-skills, no instructions/description overrides, and no property overrides — the entity-side equivalent of NeedsEntity(). NeedsEntity now compares OriginalInstructions directly instead of using the removed HasCustomInstructions flag. https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillEntity.cs | 7 +++++++ Extensions/Signum.Agent/AgentSkillLogic.cs | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs index 1185598527..fcf5b8e08a 100644 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -50,6 +50,13 @@ public class AgentSkillEntity : Entity [AutoExpressionField] public override string ToString() => As.Expression(() => Name); + // True when the entity has no customisations over its SkillCode defaults. + public bool IsDefault() => + SubSkills.Count == 0 && + Instructions == null && + ShortDescription == null && + PropertyOverrides.Count == 0; + protected override string? ChildPropertyValidation(ModifiableEntity sender, PropertyInfo pi) { if (sender is AgentSkillPropertyOverrideEmbedded po diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 8464a5e704..51d5b50eb5 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -210,10 +210,10 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) static bool NeedsEntity(AgentSkillCode code) { if (code.SubSkills.Any()) return true; - if (code.HasCustomInstructions) return true; var defaultCode = (AgentSkillCode)Activator.CreateInstance(code.GetType())!; if (code.ShortDescription != defaultCode.ShortDescription) return true; + if (code.OriginalInstructions != defaultCode.OriginalInstructions) return true; foreach (var pi in code.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { @@ -241,7 +241,7 @@ static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? Active = true, UseCase = useCase, ShortDescription = code.ShortDescription != defaultCode.ShortDescription ? code.ShortDescription : null, - Instructions = code.HasCustomInstructions ? code.OriginalInstructions : null, + Instructions = code.OriginalInstructions != defaultCode.OriginalInstructions ? code.OriginalInstructions : null, }; foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) @@ -395,8 +395,6 @@ public string OriginalInstructions get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } set { originalInstructions = value; } } - internal bool HasCustomInstructions => originalInstructions != null; - // Populated from DB at resolve time, or from code when building a default tree for a factory. public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); From 29b36e336bc9ed13eecc30818f006135d2350c98 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 21:55:22 +0000 Subject: [PATCH 19/21] Move IsDefault() to AgentSkillCode, remove from AgentSkillEntity AgentSkillCode.IsDefault() checks no sub-skills, unmodified short description, unmodified instructions, and all [AgentSkillProperty] values at their defaults. NeedsEntity() becomes a one-liner: !code.IsDefault(). https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillEntity.cs | 7 ---- Extensions/Signum.Agent/AgentSkillLogic.cs | 41 +++++++++++---------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs index fcf5b8e08a..1185598527 100644 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ b/Extensions/Signum.Agent/AgentSkillEntity.cs @@ -50,13 +50,6 @@ public class AgentSkillEntity : Entity [AutoExpressionField] public override string ToString() => As.Expression(() => Name); - // True when the entity has no customisations over its SkillCode defaults. - public bool IsDefault() => - SubSkills.Count == 0 && - Instructions == null && - ShortDescription == null && - PropertyOverrides.Count == 0; - protected override string? ChildPropertyValidation(ModifiableEntity sender, PropertyInfo pi) { if (sender is AgentSkillPropertyOverrideEmbedded po diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 51d5b50eb5..7dde9e5dce 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -207,26 +207,7 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => RootsByUseCase.Value.TryGetC(symbol); - static bool NeedsEntity(AgentSkillCode code) - { - if (code.SubSkills.Any()) return true; - - var defaultCode = (AgentSkillCode)Activator.CreateInstance(code.GetType())!; - if (code.ShortDescription != defaultCode.ShortDescription) return true; - if (code.OriginalInstructions != defaultCode.OriginalInstructions) return true; - - foreach (var pi in code.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var attr = pi.GetCustomAttribute(); - if (attr == null) continue; - - var currentStr = attr.ConvertValueToString(pi.GetValue(code), pi.PropertyType); - var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); - if (currentStr != defaultStr) return true; - } - - return false; - } + static bool NeedsEntity(AgentSkillCode code) => !code.IsDefault(); static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? useCase, Dictionary codeEntities) @@ -395,6 +376,26 @@ public string OriginalInstructions get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } set { originalInstructions = value; } } + public bool IsDefault() + { + if (SubSkills.Count > 0) return false; + + var defaultCode = (AgentSkillCode)Activator.CreateInstance(GetType())!; + if (ShortDescription != defaultCode.ShortDescription) return false; + if (OriginalInstructions != defaultCode.OriginalInstructions) return false; + + foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var attr = pi.GetCustomAttribute(); + if (attr == null) continue; + var currentStr = attr.ConvertValueToString(pi.GetValue(this), pi.PropertyType); + var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); + if (currentStr != defaultStr) return false; + } + + return true; + } + // Populated from DB at resolve time, or from code when building a default tree for a factory. public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); From a251006aea2f9e96f5acfdf72bbbddd0ba31a5a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 22:00:14 +0000 Subject: [PATCH 20/21] Inline NeedsEntity as !code.IsDefault() https://claude.ai/code/session_01DzaiFG2j85WBup5bdszYFb --- Extensions/Signum.Agent/AgentSkillLogic.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs index 7dde9e5dce..8382e7e4e1 100644 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ b/Extensions/Signum.Agent/AgentSkillLogic.cs @@ -207,9 +207,7 @@ static void ValidateNoCircularReferences(AgentSkillEntity entity) public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => RootsByUseCase.Value.TryGetC(symbol); - static bool NeedsEntity(AgentSkillCode code) => !code.IsDefault(); - - static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? useCase, +static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? useCase, Dictionary codeEntities) { var type = code.GetType(); @@ -243,7 +241,7 @@ static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? foreach (var (sub, activation) in code.SubSkills) { Lite skillLite; - if (NeedsEntity(sub)) + if (!sub.IsDefault()) { var subEntity = ConvertToEntity(sub, null, codeEntities).Save(); skillLite = (Lite)(object)subEntity.ToLite(); From cc0a8440db1f66bc256b770d4802d1a9f259165f Mon Sep 17 00:00:00 2001 From: Olmo del Corral Date: Wed, 8 Apr 2026 18:39:39 +0200 Subject: [PATCH 21/21] more on Signum.Agent --- .../{AgentSkillClient.tsx => AgentClient.tsx} | 34 +- Extensions/Signum.Agent/AgentLogic.cs | 344 ++++++++++ Extensions/Signum.Agent/AgentSkillEntity.cs | 94 --- Extensions/Signum.Agent/AgentSkillLogic.cs | 630 ------------------ Extensions/Signum.Agent/ChatbotController.cs | 10 +- Extensions/Signum.Agent/ChatbotLogic.cs | 13 +- Extensions/Signum.Agent/Message.tsx | 8 +- Extensions/Signum.Agent/Signum.Agent.d.ts | 149 ----- Extensions/Signum.Agent/Signum.Agent.ts | 93 ++- Extensions/Signum.Agent/SkillCode.cs | 194 ++++++ Extensions/Signum.Agent/SkillCodeLogic.cs | 148 ++++ .../Signum.Agent/SkillCustomizationEntity.cs | 100 +++ .../Signum.Agent/Skills/AutocompleteSkill.cs | 2 +- Extensions/Signum.Agent/Skills/ChartSkill.cs | 2 +- .../Signum.Agent/Skills/ConfirmUISkill.cs | 2 +- .../Skills/ConversationSumarizerSkill.cs | 2 +- .../Skills/CurrentServerContextSkill.cs | 2 +- .../Signum.Agent/Skills/EntityUrlSkill.cs | 2 +- .../Signum.Agent/Skills/GetUIContextSkill.cs | 2 +- .../Signum.Agent/Skills/IntroductionSkill.cs | 2 +- .../Signum.Agent/Skills/OperationSkill.cs | 2 +- .../Skills/QuestionSumarizerSkill.cs | 2 +- .../Signum.Agent/Skills/RetrieveSkill.cs | 2 +- Extensions/Signum.Agent/Skills/Search.md | 4 +- Extensions/Signum.Agent/Skills/SearchSkill.cs | 13 +- .../Signum.Agent/Templates/ChatMarkdown.tsx | 34 +- ...{AgentSkill.tsx => SkillCustomization.tsx} | 64 +- .../GoogleMapScripts/Markermap.tsx | 1 - .../View/DynamicViewComponent.tsx | 4 +- .../Signum.Workflow/Case/CaseFrameModal.tsx | 2 +- .../Signum.Workflow/Workflow/Workflow.tsx | 2 +- 31 files changed, 929 insertions(+), 1034 deletions(-) rename Extensions/Signum.Agent/{AgentSkillClient.tsx => AgentClient.tsx} (71%) create mode 100644 Extensions/Signum.Agent/AgentLogic.cs delete mode 100644 Extensions/Signum.Agent/AgentSkillEntity.cs delete mode 100644 Extensions/Signum.Agent/AgentSkillLogic.cs delete mode 100644 Extensions/Signum.Agent/Signum.Agent.d.ts create mode 100644 Extensions/Signum.Agent/SkillCode.cs create mode 100644 Extensions/Signum.Agent/SkillCodeLogic.cs create mode 100644 Extensions/Signum.Agent/SkillCustomizationEntity.cs rename Extensions/Signum.Agent/Templates/{AgentSkill.tsx => SkillCustomization.tsx} (63%) diff --git a/Extensions/Signum.Agent/AgentSkillClient.tsx b/Extensions/Signum.Agent/AgentClient.tsx similarity index 71% rename from Extensions/Signum.Agent/AgentSkillClient.tsx rename to Extensions/Signum.Agent/AgentClient.tsx index bbe19dd143..ac66eb860d 100644 --- a/Extensions/Signum.Agent/AgentSkillClient.tsx +++ b/Extensions/Signum.Agent/AgentClient.tsx @@ -3,25 +3,12 @@ import { ajaxGet } from '@framework/Services'; import { Navigator, EntitySettings } from '@framework/Navigator'; import * as AppContext from '@framework/AppContext'; import { TypeContext } from '@framework/TypeContext'; -import { AgentSkillEntity } from './Signum.Agent'; +import { SkillCustomizationEntity } from './Signum.Agent'; -export interface SkillPropertyMeta { - propertyName: string; - attributeName: string; - valueHint: string | null; - propertyType: string; -} - -export interface SkillCodeInfo { - defaultShortDescription: string; - defaultInstructions: string; - properties: SkillPropertyMeta[]; -} - -export namespace AgentSkillClient { +export namespace AgentClient { export function start(options: { routes: unknown[] }): void { - Navigator.addSettings(new EntitySettings(AgentSkillEntity, e => import('./Templates/AgentSkill'))); + Navigator.addSettings(new EntitySettings(SkillCustomizationEntity, e => import('./Templates/SkillCustomization'))); AppContext.clearSettingsActions.push(() => propertyValueRegistry.clear()); } @@ -46,3 +33,18 @@ export namespace AgentSkillClient { } } } + + +export interface SkillPropertyMeta { + propertyName: string; + attributeName: string; + valueHint: string | null; + propertyType: string; + } + + export interface SkillCodeInfo { + defaultShortDescription: string; + defaultInstructions: string; + properties: SkillPropertyMeta[]; + } + diff --git a/Extensions/Signum.Agent/AgentLogic.cs b/Extensions/Signum.Agent/AgentLogic.cs new file mode 100644 index 0000000000..a58b36a2c4 --- /dev/null +++ b/Extensions/Signum.Agent/AgentLogic.cs @@ -0,0 +1,344 @@ +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol; +using ModelContextProtocol.Protocol; +using Signum.Agent.Skills; +using Signum.Engine.Sync; +using Signum.Utilities.DataStructures; +using Signum.Utilities.Reflection; +using System.Collections.Concurrent; +using System.Collections.Frozen; + +namespace Signum.Agent; + + +public static class AgentLogic +{ + public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); + + public static Dictionary> RegisteredAgents = new(); + + public static ResetLazy> SkillCodeByAgent = null!; + + public static ResetLazy> SkillCustomizationByAgent = null!; + + + public static void RegisterAgent(AgentSymbol agent, Func factory) + { + RegisteredAgents[agent] = factory; + + using (SkillCodeLogic.AutoRegister()) + factory(); //Check if it works at registration time + } + + public static void Start(SchemaBuilder sb, Func? getChatBot) + { + if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) + return; + + SkillCodeLogic.Start(sb); + + SkillCodeLogic.Register(); + SkillCodeLogic.Register(); + + if (getChatBot != null) + RegisterAgent(DefaultAgent.Chatbot, getChatBot); + + RegisterAgent(DefaultAgent.QuestionSummarizer, () => new QuestionSumarizerSkill()); + RegisterAgent(DefaultAgent.ConversationSumarizer, () => new ConversationSumarizerSkill()); + + SymbolLogic.Start(sb, () => RegisteredAgents.Keys); + + + sb.Include() + .WithUniqueIndex(a => a.Agent, a => a.Agent != null) + .WithSave(SkillCustomizationOperation.Save) + .WithDelete(SkillCustomizationOperation.Delete) + .WithQuery(() => e => new + { + Entity = e, + e.Id, + e.SkillCode, + e.Agent, + e.ShortDescription, + }); + + new Graph.ConstructFrom(SkillCustomizationOperation.CreateFromAgent) + { + Construct = (agentSymbol, _) => + { + if (!RegisteredAgents.TryGetValue(agentSymbol, out var factory)) + return new SkillCustomizationEntity { Agent = agentSymbol }; + + var code = factory(); + return code.ToCustomizationEntity(agentSymbol); + } + }.Register(); + + sb.Schema.EntityEvents().Saving += entity => + { + if (!entity.IsNew && entity.SubSkills.IsGraphModified) + ValidateNoCircularReferences(entity); + }; + + SkillCodeByAgent = sb.GlobalLazy(() => new ConcurrentDictionary(), + new InvalidateWith(typeof(SkillCustomizationEntity))); + + + SkillCustomizationByAgent = sb.GlobalLazy(() => Database.Query().Where(a=>a.Agent != null).ToFrozenDictionaryEx(a=>a.Agent!), + new InvalidateWith(typeof(SkillCustomizationEntity))); + } + + public static SkillCode ToSkillCode(this SkillCustomizationEntity entity) + { + var code = (SkillCode)Activator.CreateInstance(entity.SkillCode.ToType())!; + code.Customization = entity.ToLite(); + + if (entity.ShortDescription != null) + code.ShortDescription = entity.ShortDescription; + if (entity.Instructions != null) + code.OriginalInstructions = entity.Instructions; + + code.ApplyPropertyOverrides(entity); + + foreach (var ss in entity.SubSkills) + { + SkillCode subCode = + ss.Skill is SkillCustomizationEntity c ? c.ToSkillCode() : + ss.Skill is SkillCodeEntity sc ? (SkillCode)Activator.CreateInstance(sc.ToType())! : + throw new UnexpectedValueException(ss.Skill); + + code.SubSkills.Add((subCode, ss.Activation)); + } + + return code; + } + + static void ValidateNoCircularReferences(SkillCustomizationEntity entity) + { + using (new EntityCache(EntityCacheType.ForceNew)) + { + EntityCache.AddFullGraph(entity); + var allEntities = Database.RetrieveAll(); + + var graph = DirectedGraph.Generate( + allEntities, + e => + { + var subSkills = e.Is(entity) ? entity.SubSkills : e.SubSkills; + return subSkills + .Where(s => s.Skill is SkillCustomizationEntity) + .Select(s => (SkillCustomizationEntity)s.Skill) + .ToList(); + } + ); + + var problems = graph.FeedbackEdgeSet().Edges.ToList(); + if (problems.Count > 0) + throw new ApplicationException( + $"{problems.Count} cycle(s) found in AgentSkill graph:\n" + + problems.ToString(e => $" {e.From} → {e.To}", "\n")); + } + } + + public static SkillCode GetEffectiveSkillCode(this AgentSymbol agentSymbol) + { + return SkillCodeByAgent.Value.GetOrCreate(agentSymbol, s => + { + var skillCustomization = SkillCustomizationByAgent.Value.TryGetC(s); + if(skillCustomization != null) + skillCustomization.ToSkillCode(); + + var def = RegisteredAgents.GetOrThrow(agentSymbol); + + return def(); + }); + + } + + static SkillCustomizationEntity ToCustomizationEntity(this SkillCode code, AgentSymbol? agent) + { + var type = code.GetType(); + + var entity = new SkillCustomizationEntity + { + SkillCode = SkillCodeLogic.ToSkillCodeEntity(type), + Agent = agent, + ShortDescription = code.ShortDescription, + Instructions = code.OriginalInstructions, + }; + + foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var attr = pi.GetCustomAttribute(); + if (attr == null) continue; + + var currentStr = attr.ConvertValueToString(pi.GetValue(code), pi.PropertyType); + entity.Properties.Add(new SkillPropertyEmbedded + { + PropertyName = pi.Name, + Value = currentStr, + }); + } + + foreach (var (sub, activation) in code.SubSkills) + { + Entity skillLite = !sub.IsDefault() ? sub.ToCustomizationEntity(agent: null) : + SkillCodeLogic.ToSkillCodeEntity(sub.GetType()); + entity.SubSkills.Add(new SubSkillEmbedded { Skill = skillLite, Activation = activation }); + } + + return entity; + } + + +} + +[AttributeUsage(AttributeTargets.Property)] +public class SkillPropertyAttribute : Attribute +{ + public virtual object? ConvertFromString(string? value, Type targetType) + { + if (value == null) + return null; + + return ReflectionTools.ChangeType(value, targetType); + } + + public virtual string? ConvertValueToString(object? value, Type targetType) => value?.ToString(); + + public virtual string? ValidateValue(string? value, Type targetType) => null; + + public virtual string? ValueHint => null; +} + +[AttributeUsage(AttributeTargets.Property)] +public class SkillProperty_QueryListAttribute : SkillPropertyAttribute +{ + public override object? ConvertFromString(string? value, Type targetType) + { + if (value == null) + return null; + + return value + .Split(',') + .Select(k => QueryLogic.ToQueryName(k.Trim())) + .ToHashSet(); + } + + public override string? ConvertValueToString(object? value, Type targetType) + { + if (value is not System.Collections.IEnumerable enumerable) return value?.ToString(); + return enumerable.Cast().Select(q => QueryLogic.GetQueryEntity(q).Key).ToString(", "); + } + + public override string? ValidateValue(string? value, Type targetType) + { + if (value == null) + return null; + + var errors = value.Split(',') + .Select(k => k.Trim()) + .Where(k => k.HasText() && QueryLogic.ToQueryName(k) == null) + .ToList(); + + return errors.Any() + ? $"Unknown query key(s): {errors.ToString(", ")}" + : null; + } + + public override string? ValueHint => "Comma-separated query keys"; +} + +public enum SkillActivation +{ + Eager, + Lazy, +} + +/// +/// Marks a [McpServerTool] as a UI tool: the server never invokes its body. +/// The controller routes the call to the client via the $!AssistantUITool streaming command. +/// The method body must throw InvalidOperationException. +/// +[AttributeUsage(AttributeTargets.Method)] +public class UIToolAttribute : Attribute { } + +public static partial class SignumMcpServerBuilderExtensions +{ + public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, AgentSymbol useCase) + { + var sessionActivated = new ConcurrentDictionary>(); + + SkillCode GetRoot() => + AgentLogic.GetEffectiveSkillCode(useCase) + ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}"); + + IEnumerable GetActivated(SkillCode code, string? sessionId) => + sessionId != null && sessionActivated.TryGetValue(sessionId, out var s) ? s + : code.GetEagerSkillsRecursive().Select(s => s.Name).ToHashSet(); + + return builder + .WithHttpTransport(options => + { +#pragma warning disable MCPEXP002 + options.RunSessionHandler = async (httpContext, mcpServer, token) => + { + if (mcpServer.SessionId != null) + sessionActivated[mcpServer.SessionId] = GetRoot().GetEagerSkillsRecursive().Select(s => s.Name).ToHashSet(); + try { await mcpServer.RunAsync(token); } + finally + { + if (mcpServer.SessionId != null) + sessionActivated.TryRemove(mcpServer.SessionId, out _); + } + }; +#pragma warning restore MCPEXP002 + }) + .WithListToolsHandler(async (ctx, ct) => + { + var root = GetRoot(); + var activated = GetActivated(root, ctx.Server.SessionId); + var tools = activated + .Select(name => root.FindSkill(name)) + .OfType() + .SelectMany(s => s.GetMcpServerTools()) + .Select(t => t.ProtocolTool) + .ToList(); + + return new ListToolsResult { Tools = tools }; + }) + .WithCallToolHandler(async (ctx, ct) => + { + var toolName = ctx.Params!.Name; + var root = GetRoot(); + var activated = GetActivated(root, ctx.Server.SessionId); + + var tool = activated + .Select(name => root.FindSkill(name)) + .OfType() + .SelectMany(s => s.GetMcpServerTools()) + .FirstOrDefault(t => t.ProtocolTool.Name == toolName) + ?? throw new McpException($"Tool '{toolName}' not found"); + + CallToolResult result; + using (AgentLogic.IsMCP.Override(true)) + result = await tool.InvokeAsync(ctx, ct); + + if (toolName == nameof(IntroductionSkill.Describe) + && ctx.Params.Arguments?.TryGetValue("skillName", out var je) == true + && je.GetString() is { } skillName + && ctx.Server.SessionId is { } sessionId) + { + var newSkill = root.FindSkill(skillName); + if (newSkill != null && sessionActivated.TryGetValue(sessionId, out var skills)) + { + foreach (var s in newSkill.GetEagerSkillsRecursive()) + skills.Add(s.Name); + await ctx.Server.SendNotificationAsync(NotificationMethods.ToolListChangedNotification, ct); + } + } + + return result; + }); + } +} diff --git a/Extensions/Signum.Agent/AgentSkillEntity.cs b/Extensions/Signum.Agent/AgentSkillEntity.cs deleted file mode 100644 index 1185598527..0000000000 --- a/Extensions/Signum.Agent/AgentSkillEntity.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Signum.Agent; - -[EntityKind(EntityKind.SystemString, EntityData.Master), TicksColumn(false)] -public class AgentSkillCodeEntity : Entity -{ - [UniqueIndex] - public string FullClassName { get; set; } - - [AutoExpressionField] - public override string ToString() => As.Expression(() => FullClassName.AfterLast('.')); -} - -public class AgentUseCaseSymbol : Symbol -{ - private AgentUseCaseSymbol() { } -} - -[AutoInit] -public static class AgentUseCase -{ - public static AgentUseCaseSymbol DefaultChatbot = null!; - public static AgentUseCaseSymbol Summarizer = null!; -} - -[EntityKind(EntityKind.Main, EntityData.Master)] -public class AgentSkillEntity : Entity -{ - [UniqueIndex] - [StringLengthValidator(Min = 1, Max = 200)] - public string Name { get; set; } - - public AgentSkillCodeEntity SkillCode { get; set; } - - public bool Active { get; set; } = true; - - public AgentUseCaseSymbol? UseCase { get; set; } - - [StringLengthValidator(Min = 1, Max = 500)] - public string? ShortDescription { get; set; } - - [StringLengthValidator(MultiLine = true)] - public string? Instructions { get; set; } - - [BindParent] - public MList PropertyOverrides { get; set; } = new MList(); - - [BindParent] - public MList SubSkills { get; set; } = new MList(); - - [AutoExpressionField] - public override string ToString() => As.Expression(() => Name); - - protected override string? ChildPropertyValidation(ModifiableEntity sender, PropertyInfo pi) - { - if (sender is AgentSkillPropertyOverrideEmbedded po - && pi.Name == nameof(AgentSkillPropertyOverrideEmbedded.Value) - && SkillCode != null - && AgentSkillLogic.RegisteredCodes.TryGetValue(SkillCode.FullClassName, out var codeType)) - { - var propInfo = codeType.GetProperty(po.PropertyName, BindingFlags.Public | BindingFlags.Instance); - var attr = propInfo?.GetCustomAttribute(); - if (attr != null) - return attr.ValidateValue(po.Value, propInfo!.PropertyType); - } - - return base.ChildPropertyValidation(sender, pi); - } -} - -public class AgentSkillPropertyOverrideEmbedded : EmbeddedEntity -{ - [StringLengthValidator(Min = 1, Max = 200)] - public string PropertyName { get; set; } - - [StringLengthValidator(MultiLine = true)] - public string? Value { get; set; } -} - -public class AgentSkillSubSkillEmbedded : EmbeddedEntity -{ - // Can reference either an AgentSkillEntity (customised) or AgentSkillCodeEntity (default, no DB entity needed) - [ImplementedBy(typeof(AgentSkillEntity), typeof(AgentSkillCodeEntity))] - public Lite Skill { get; set; } - - public SkillActivation Activation { get; set; } -} - -[AutoInit] -public static class AgentSkillOperation -{ - public static ExecuteSymbol Save = null!; - public static DeleteSymbol Delete = null!; - public static ConstructSymbol.From CreateFromUseCase = null!; -} diff --git a/Extensions/Signum.Agent/AgentSkillLogic.cs b/Extensions/Signum.Agent/AgentSkillLogic.cs deleted file mode 100644 index 8382e7e4e1..0000000000 --- a/Extensions/Signum.Agent/AgentSkillLogic.cs +++ /dev/null @@ -1,630 +0,0 @@ -using Microsoft.Extensions.AI; -using Microsoft.Extensions.DependencyInjection; -using ModelContextProtocol; -using ModelContextProtocol.Protocol; -using ModelContextProtocol.Server; -using Signum.Agent.Skills; -using Signum.API; -using Signum.Engine.Sync; -using Signum.Utilities.DataStructures; -using Signum.Utilities.Reflection; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; - -namespace Signum.Agent; - -public static class AgentSkillLogic -{ - public static readonly AsyncThreadVariable IsMCP = Statics.ThreadVariable("IsMCP"); - - public static Dictionary RegisteredCodes = new(); - public static Dictionary> DefaultRoots = new(); - - public static ConversationSumarizerSkill ConversationSumarizerSkill = null!; - public static QuestionSumarizerSkill QuestionSumarizerSkill = null!; - - public static ResetLazy> RootsByUseCase = null!; - - public static void RegisterCode() where T : AgentSkillCode - { - RegisteredCodes[typeof(T).FullName!] = typeof(T); - } - - public static void RegisterDefaultRoot(AgentUseCaseSymbol useCase, Func factory) - { - DefaultRoots[useCase] = factory; - } - - public static void Start(SchemaBuilder sb) - { - if (sb.AlreadyDefined(MethodBase.GetCurrentMethod())) - return; - - RegisterCode(); - RegisterCode(); - - ConversationSumarizerSkill = new ConversationSumarizerSkill(); - QuestionSumarizerSkill = new QuestionSumarizerSkill(); - - SymbolLogic.Start(sb, () => [AgentUseCase.DefaultChatbot, AgentUseCase.Summarizer]); - - sb.Include() - .WithQuery(() => e => new { Entity = e, e.Id, e.FullClassName }); - - sb.Schema.Generating += Schema_Generating; - sb.Schema.Synchronizing += Schema_Synchronizing; - - sb.Include() - .WithSave(AgentSkillOperation.Save) - .WithDelete(AgentSkillOperation.Delete) - .WithQuery(() => e => new - { - Entity = e, - e.Id, - e.Name, - e.SkillCode, - e.Active, - e.UseCase, - e.ShortDescription, - }); - - new Graph.ConstructFrom(AgentSkillOperation.CreateFromUseCase) - { - Construct = (useCase, _) => - { - if (!DefaultRoots.TryGetValue(useCase, out var factory)) - return new AgentSkillEntity { UseCase = useCase }; - - var root = factory(); - var codeEntities = Database.Query().ToDictionary(e => e.FullClassName); - return ConvertToEntity(root, useCase, codeEntities); - } - }.Register(); - - sb.Schema.EntityEvents().Saving += entity => - { - if (!entity.IsNew && entity.SubSkills.IsGraphModified) - ValidateNoCircularReferences(entity); - }; - - RootsByUseCase = sb.GlobalLazy(() => - { - var allEntities = Database.Query().ToList(); - var allEntitiesById = allEntities.ToDictionary(e => e.Id); - var codeFullNames = Database.Query() - .Select(e => new { e.Id, e.FullClassName }) - .ToDictionary(e => e.Id, e => e.FullClassName); - - var fromDb = allEntities - .Where(e => e.UseCase != null && e.Active) - .GroupBy(e => e.UseCase!) - .ToDictionary( - g => g.Key, - g => ResolveCode(g.SingleEx(), allEntitiesById, codeFullNames) - ); - - // Fall back to registered default factories for use cases not in DB - foreach (var (useCase, factory) in DefaultRoots) - fromDb.TryAdd(useCase, factory()); - - return fromDb; - }, new InvalidateWith(typeof(AgentSkillEntity))); - } - - static SqlPreCommand? Schema_Generating() - { - var table = Schema.Current.Table(); - return GenerateCodeEntities() - .Select(e => table.InsertSqlSync(e)) - .Combine(Spacing.Simple); - } - - static SqlPreCommand? Schema_Synchronizing(Replacements replacements) - { - var table = Schema.Current.Table(); - var should = GenerateCodeEntities().ToDictionary(e => e.FullClassName); - var current = Administrator.TryRetrieveAll(replacements) - .ToDictionary(e => e.FullClassName); - - return Synchronizer.SynchronizeScript(Spacing.Double, should, current, - createNew: (_, s) => table.InsertSqlSync(s), - removeOld: (_, c) => table.DeleteSqlSync(c, e => e.FullClassName == c.FullClassName), - mergeBoth: (_, s, c) => table.UpdateSqlSync(c, e => e.FullClassName == c.FullClassName)); - } - - static List GenerateCodeEntities() => - RegisteredCodes.Keys.Select(fc => new AgentSkillCodeEntity { FullClassName = fc }).ToList(); - - public static AgentSkillCode ResolveCode( - AgentSkillEntity entity, - Dictionary allEntitiesById, - Dictionary codeFullNames) - { - var type = RegisteredCodes.GetOrThrow(entity.SkillCode.FullClassName, - $"AgentSkillCode type '{entity.SkillCode.FullClassName}' is not registered."); - - var code = (AgentSkillCode)Activator.CreateInstance(type)!; - code.InstanceName = entity.Name; - - if (entity.ShortDescription != null) - code.ShortDescription = entity.ShortDescription; - if (entity.Instructions != null) - code.OriginalInstructions = entity.Instructions; - - code.ApplyPropertyOverrides(entity); - - foreach (var ss in entity.SubSkills) - { - AgentSkillCode subCode; - if (ss.Skill.EntityType == typeof(AgentSkillEntity)) - { - var subEntity = allEntitiesById.GetOrThrow(ss.Skill.Id); - subCode = ResolveCode(subEntity, allEntitiesById, codeFullNames); - } - else - { - var fullClassName = codeFullNames.GetOrThrow(ss.Skill.Id); - var subType = RegisteredCodes.GetOrThrow(fullClassName, - $"AgentSkillCode type '{fullClassName}' is not registered."); - subCode = (AgentSkillCode)Activator.CreateInstance(subType)!; - } - code.SubSkills.Add((subCode, ss.Activation)); - } - - return code; - } - - static void ValidateNoCircularReferences(AgentSkillEntity entity) - { - using (new EntityCache(EntityCacheType.ForceNew)) - { - EntityCache.AddFullGraph(entity); - var allEntities = Database.RetrieveAll(); - - var graph = DirectedGraph.Generate( - allEntities, - e => - { - var subSkills = e.Is(entity) ? entity.SubSkills : e.SubSkills; - return subSkills - .Where(s => s.Skill.EntityType == typeof(AgentSkillEntity)) - .Select(s => (AgentSkillEntity)s.Skill.RetrieveAndRemember()) - .ToList(); - } - ); - - var problems = graph.FeedbackEdgeSet().Edges.ToList(); - if (problems.Count > 0) - throw new ApplicationException( - $"{problems.Count} cycle(s) found in AgentSkill graph:\n" + - problems.ToString(e => $" {e.From.Name} → {e.To.Name}", "\n")); - } - } - - public static AgentSkillCode? GetRootForUseCase(AgentUseCaseSymbol symbol) => - RootsByUseCase.Value.TryGetC(symbol); - -static AgentSkillEntity ConvertToEntity(AgentSkillCode code, AgentUseCaseSymbol? useCase, - Dictionary codeEntities) - { - var type = code.GetType(); - var defaultCode = (AgentSkillCode)Activator.CreateInstance(type)!; - - var entity = new AgentSkillEntity - { - Name = code.Name, - SkillCode = codeEntities.GetOrThrow(type.FullName!), - Active = true, - UseCase = useCase, - ShortDescription = code.ShortDescription != defaultCode.ShortDescription ? code.ShortDescription : null, - Instructions = code.OriginalInstructions != defaultCode.OriginalInstructions ? code.OriginalInstructions : null, - }; - - foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var attr = pi.GetCustomAttribute(); - if (attr == null) continue; - - var currentStr = attr.ConvertValueToString(pi.GetValue(code), pi.PropertyType); - var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); - if (currentStr != defaultStr && currentStr != null) - entity.PropertyOverrides.Add(new AgentSkillPropertyOverrideEmbedded - { - PropertyName = pi.Name, - Value = currentStr, - }); - } - - foreach (var (sub, activation) in code.SubSkills) - { - Lite skillLite; - if (!sub.IsDefault()) - { - var subEntity = ConvertToEntity(sub, null, codeEntities).Save(); - skillLite = (Lite)(object)subEntity.ToLite(); - } - else - { - skillLite = (Lite)(object)codeEntities.GetOrThrow(sub.GetType().FullName!).ToLite(); - } - entity.SubSkills.Add(new AgentSkillSubSkillEmbedded { Skill = skillLite, Activation = activation }); - } - - return entity; - } - - public static SkillCodeInfo GetSkillCodeInfo(string fullClassName) - { - if (!RegisteredCodes.TryGetValue(fullClassName, out var type)) - throw new KeyNotFoundException($"AgentSkillCode type '{fullClassName}' is not registered."); - - var instance = (AgentSkillCode)Activator.CreateInstance(type)!; - - var properties = type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(pi => new { pi, attr = pi.GetCustomAttribute() }) - .Where(x => x.attr != null) - .Select(x => new SkillPropertyMeta - { - PropertyName = x.pi.Name, - AttributeName = x.attr!.GetType().Name.Before("Attribute"), - ValueHint = x.attr.ValueHint, - PropertyType = x.pi.PropertyType.TypeName(), - }) - .ToList(); - - return new SkillCodeInfo - { - DefaultShortDescription = instance.ShortDescription, - DefaultInstructions = instance.OriginalInstructions, - Properties = properties, - }; - } -} - -public class SkillCodeInfo -{ - public string DefaultShortDescription { get; set; } = null!; - public string DefaultInstructions { get; set; } = null!; - public List Properties { get; set; } = null!; -} - -public class SkillPropertyMeta -{ - public string PropertyName { get; set; } = null!; - public string AttributeName { get; set; } = null!; - public string? ValueHint { get; set; } - public string PropertyType { get; set; } = null!; -} - -[AttributeUsage(AttributeTargets.Property)] -public class AgentSkillPropertyAttribute : Attribute -{ - public virtual object? ConvertFromString(string? value, Type targetType) - { - if (value == null) - return null; - - return ReflectionTools.ChangeType(value, targetType); - } - - public virtual string? ConvertValueToString(object? value, Type targetType) => value?.ToString(); - - public virtual string? ValidateValue(string? value, Type targetType) => null; - - public virtual string? ValueHint => null; -} - -[AttributeUsage(AttributeTargets.Property)] -public class AgentSkillProperty_QueryListAttribute : AgentSkillPropertyAttribute -{ - public override object? ConvertFromString(string? value, Type targetType) - { - if (value == null) - return null; - - return value - .Split(',') - .Select(k => QueryLogic.ToQueryName(k.Trim())) - .ToHashSet(); - } - - public override string? ConvertValueToString(object? value, Type targetType) - { - if (value is not System.Collections.IEnumerable enumerable) return value?.ToString(); - return enumerable.Cast().Select(q => QueryLogic.GetQueryEntity(q).Key).ToString(", "); - } - - public override string? ValidateValue(string? value, Type targetType) - { - if (value == null) - return null; - - var errors = value.Split(',') - .Select(k => k.Trim()) - .Where(k => k.HasText() && QueryLogic.ToQueryName(k) == null) - .ToList(); - - return errors.Any() - ? $"Unknown query key(s): {errors.ToString(", ")}" - : null; - } - - public override string? ValueHint => "Comma-separated query keys"; -} - -public abstract class AgentSkillCode -{ - public string? InstanceName { get; internal set; } - public string Name => InstanceName ?? this.GetType().Name.Before("Skill"); - - public string ShortDescription { get; set; } = ""; - public Func IsAllowed { get; set; } = () => true; - public Dictionary>? Replacements; - - public static string SkillsDirectory = Path.Combine( - Path.GetDirectoryName(typeof(AgentSkillCode).Assembly.Location)!, "Skills"); - - string? originalInstructions; - public string OriginalInstructions - { - get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } - set { originalInstructions = value; } - } - public bool IsDefault() - { - if (SubSkills.Count > 0) return false; - - var defaultCode = (AgentSkillCode)Activator.CreateInstance(GetType())!; - if (ShortDescription != defaultCode.ShortDescription) return false; - if (OriginalInstructions != defaultCode.OriginalInstructions) return false; - - foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var attr = pi.GetCustomAttribute(); - if (attr == null) continue; - var currentStr = attr.ConvertValueToString(pi.GetValue(this), pi.PropertyType); - var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); - if (currentStr != defaultStr) return false; - } - - return true; - } - - // Populated from DB at resolve time, or from code when building a default tree for a factory. - public List<(AgentSkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); - - public AgentSkillCode WithSubSkill(AgentSkillCode sub, SkillActivation activation = SkillActivation.Lazy) - { - SubSkills.Add((sub, activation)); - return this; - } - - public string GetInstruction(object? context) - { - var text = OriginalInstructions; - if (!Replacements.IsNullOrEmpty()) - text = text.Replace(Replacements.SelectDictionary(k => k, v => v(context))); - - if (SubSkills.Any()) - { - var sb = new StringBuilder(text); - foreach (var (sub, activation) in SubSkills) - { - sb.AppendLineLF("# Skill " + sub.Name); - sb.AppendLineLF("**Summary**: " + sub.ShortDescription); - sb.AppendLineLF(); - if (activation == SkillActivation.Eager) - sb.AppendLineLF(sub.GetInstruction(null)); - else - sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); - } - return sb.ToString(); - } - - return text; - } - - public void ApplyPropertyOverrides(AgentSkillEntity entity) - { - foreach (var po in entity.PropertyOverrides) - { - var pi = this.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .FirstOrDefault(p => p.Name == po.PropertyName - && p.GetCustomAttribute() != null); - - if (pi == null) continue; - - var attr = pi.GetCustomAttribute()!; - var value = attr.ConvertFromString(po.Value, pi.PropertyType); - pi.SetValue(this, value); - } - } - - public AgentSkillCode? FindSkill(string name) - { - if (this.Name == name) return this; - foreach (var (sub, _) in SubSkills) - { - var found = sub.FindSkill(name); - if (found != null) return found; - } - return null; - } - - public AITool? FindTool(string toolName) - { - var tool = GetTools().FirstOrDefault(t => t.Name.Equals(toolName, StringComparison.InvariantCultureIgnoreCase)); - if (tool != null) return tool; - foreach (var (sub, _) in SubSkills) - { - var found = sub.FindTool(toolName); - if (found != null) return found; - } - return null; - } - - public IEnumerable GetSkillsRecursive() - { - yield return this; - foreach (var (sub, _) in SubSkills) - foreach (var s in sub.GetSkillsRecursive()) - yield return s; - } - - public IEnumerable GetEagerSkillsRecursive() - { - yield return this; - foreach (var (sub, activation) in SubSkills) - if (activation == SkillActivation.Eager) - foreach (var s in sub.GetEagerSkillsRecursive()) - yield return s; - } - - public IEnumerable GetToolsRecursive() - { - var list = GetTools().ToList(); - foreach (var (sub, activation) in SubSkills) - if (activation == SkillActivation.Eager) - list.AddRange(sub.GetToolsRecursive()); - return list; - } - - IEnumerable? cachedTools; - internal IEnumerable GetTools() - { - return (cachedTools ??= this.GetType() - .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Where(m => m.GetCustomAttribute() != null) - .Select(m => - { - Type delType = Expression.GetDelegateType( - m.GetParameters().Select(a => a.ParameterType).And(m.ReturnType).ToArray()); - Delegate del = m.IsStatic - ? Delegate.CreateDelegate(delType, m) - : Delegate.CreateDelegate(delType, this, m); - string? description = m.GetCustomAttribute()?.Description; - return (AITool)AIFunctionFactory.Create(del, m.Name, description, GetJsonSerializerOptions()); - }) - .ToList()); - } - - internal IEnumerable GetMcpServerTools() => - GetTools().Select(t => McpServerTool.Create((AIFunction)t, new McpServerToolCreateOptions - { - SerializerOptions = GetJsonSerializerOptions(), - })); - - static JsonSerializerOptions JsonSerializationOptions = new JsonSerializerOptions - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }.AddSignumJsonConverters(); - - public virtual JsonSerializerOptions GetJsonSerializerOptions() => JsonSerializationOptions; -} - -public enum SkillActivation -{ - Eager, - Lazy, -} - -/// -/// Marks a [McpServerTool] as a UI tool: the server never invokes its body. -/// The controller routes the call to the client via the $!AssistantUITool streaming command. -/// The method body must throw InvalidOperationException. -/// -[AttributeUsage(AttributeTargets.Method)] -public class UIToolAttribute : Attribute { } - -public static partial class SignumMcpServerBuilderExtensions -{ - public static IMcpServerBuilder WithSignumSkill(this IMcpServerBuilder builder, AgentUseCaseSymbol? useCase = null) - { - useCase ??= AgentUseCase.DefaultChatbot; - - var sessionActivated = new ConcurrentDictionary>(); - - AgentSkillCode GetRoot() => - AgentSkillLogic.GetRootForUseCase(useCase) - ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}"); - - HashSet InitialActivated() => - GetRoot().GetEagerSkillsRecursive().Select(s => s.Name).ToHashSet(); - - IEnumerable GetActivated(string? sessionId) => - sessionId != null && sessionActivated.TryGetValue(sessionId, out var s) - ? s - : InitialActivated(); - - return builder - .WithHttpTransport(options => - { -#pragma warning disable MCPEXP002 - options.RunSessionHandler = async (httpContext, mcpServer, token) => - { - if (mcpServer.SessionId != null) - sessionActivated[mcpServer.SessionId] = InitialActivated(); - try { await mcpServer.RunAsync(token); } - finally - { - if (mcpServer.SessionId != null) - sessionActivated.TryRemove(mcpServer.SessionId, out _); - } - }; -#pragma warning restore MCPEXP002 - }) - .WithListToolsHandler(async (ctx, ct) => - { - var root = GetRoot(); - var activated = GetActivated(ctx.Server.SessionId); - var tools = activated - .Select(name => root.FindSkill(name)) - .OfType() - .SelectMany(s => s.GetMcpServerTools()) - .Select(t => t.ProtocolTool) - .ToList(); - - return new ListToolsResult { Tools = tools }; - }) - .WithCallToolHandler(async (ctx, ct) => - { - var toolName = ctx.Params!.Name; - var root = GetRoot(); - var activated = GetActivated(ctx.Server.SessionId); - - var tool = activated - .Select(name => root.FindSkill(name)) - .OfType() - .SelectMany(s => s.GetMcpServerTools()) - .FirstOrDefault(t => t.ProtocolTool.Name == toolName) - ?? throw new McpException($"Tool '{toolName}' not found"); - - CallToolResult result; - using (AgentSkillLogic.IsMCP.Override(true)) - result = await tool.InvokeAsync(ctx, ct); - - if (toolName == nameof(IntroductionSkill.Describe) - && ctx.Params.Arguments?.TryGetValue("skillName", out var je) == true - && je.GetString() is { } skillName - && ctx.Server.SessionId is { } sessionId) - { - var newSkill = root.FindSkill(skillName); - if (newSkill != null && sessionActivated.TryGetValue(sessionId, out var skills)) - { - foreach (var s in newSkill.GetEagerSkillsRecursive()) - skills.Add(s.Name); - await ctx.Server.SendNotificationAsync(NotificationMethods.ToolListChangedNotification, ct); - } - } - - return result; - }); - } -} diff --git a/Extensions/Signum.Agent/ChatbotController.cs b/Extensions/Signum.Agent/ChatbotController.cs index 05c5d26aef..34c3afbf8a 100644 --- a/Extensions/Signum.Agent/ChatbotController.cs +++ b/Extensions/Signum.Agent/ChatbotController.cs @@ -13,9 +13,9 @@ namespace Signum.Agent; public class ChatbotController : Controller { - [HttpGet("api/agentSkill/skillCodeInfo/{skillCode}")] - public SkillCodeInfo GetSkillCodeInfo(string skillCode) => - AgentSkillLogic.GetSkillCodeInfo(skillCode); + [HttpGet("api/agentSkill/skillCodeInfo/{skillCodeName}")] + public DefaultSkillCodeInfo GetSkillCodeInfo(string skillCodeName) => + SkillCodeLogic.GetDefaultSkillCodeInfo(skillCodeName); [HttpPost("api/chatbot/feedback/{messageId}")] public void SetFeedback(int messageId, [FromBody] SetFeedbackRequest request) @@ -86,7 +86,7 @@ public async Task AskQuestionAsync(CancellationToken ct) Session = session.ToLite(), SessionTitle = session.Title, LanguageModel = session.LanguageModel.RetrieveFromCache(), - RootSkill = AgentSkillLogic.GetRootForUseCase(AgentUseCase.DefaultChatbot), + RootSkill = AgentLogic.GetEffectiveSkillCode(DefaultAgent.Chatbot), Messages = systemAndSummaries.Concat(remainingMessages).ToList(), }; } @@ -173,7 +173,7 @@ ChatSessionEntity GetOrCreateSession(string? sessionID) ConversationHistory CreateNewConversationHistory(ChatSessionEntity session) { - var rootSkill = AgentSkillLogic.GetRootForUseCase(AgentUseCase.DefaultChatbot) + var rootSkill = AgentLogic.GetEffectiveSkillCode(DefaultAgent.Chatbot) ?? throw new InvalidOperationException("No active AgentSkillEntity with UseCase = DefaultChatbot"); return new ConversationHistory diff --git a/Extensions/Signum.Agent/ChatbotLogic.cs b/Extensions/Signum.Agent/ChatbotLogic.cs index 720d256973..f98cd804cb 100644 --- a/Extensions/Signum.Agent/ChatbotLogic.cs +++ b/Extensions/Signum.Agent/ChatbotLogic.cs @@ -116,8 +116,7 @@ public static async Task SumarizeConversation(List me conversationText.AppendLine($"{roleName}: {content}"); } - var skill = AgentSkillLogic.ConversationSumarizerSkill; - var prompt = skill.GetInstruction(conversationText.ToString()); + var prompt = DefaultAgent.ConversationSumarizer.GetEffectiveSkillCode().GetInstruction(conversationText.ToString()); var client = LanguageModelLogic.GetChatClient(languageModel); var options = LanguageModelLogic.ChatOptions(languageModel, []); var cr = await client.GetResponseAsync(prompt, options, cancellationToken: ct); @@ -126,7 +125,7 @@ public static async Task SumarizeConversation(List me public static async Task SumarizeTitle(ConversationHistory history, CancellationToken ct) { - var prompt = AgentSkillLogic.QuestionSumarizerSkill.GetInstruction(history); + var prompt = DefaultAgent.QuestionSummarizer.GetEffectiveSkillCode().GetInstruction(history); var client = LanguageModelLogic.GetChatClient(history.LanguageModel); var options = LanguageModelLogic.ChatOptions(history.LanguageModel, []); var cr = await client.GetResponseAsync(prompt, options, cancellationToken: ct); @@ -335,7 +334,7 @@ public static string FormatToolError(string toolName, Exception e, IDictionary RunHeadlessAsync( string prompt, - AgentUseCaseSymbol useCase, + AgentSymbol useCase, Lite? languageModel = null, IAgentOutput? output = null, CancellationToken ct = default) @@ -345,7 +344,7 @@ public static async Task RunHeadlessAsync( var modelLite = languageModel ?? LanguageModelLogic.DefaultLanguageModel.Value ?? throw new InvalidOperationException($"No default {nameof(ChatbotLanguageModelEntity)} configured."); - var rootSkill = AgentSkillLogic.GetRootForUseCase(useCase) + var rootSkill = AgentLogic.GetEffectiveSkillCode(useCase) ?? throw new InvalidOperationException($"No active AgentSkillEntity with UseCase = {useCase.Key}."); var session = new ChatSessionEntity @@ -421,7 +420,7 @@ public class ConversationHistory public ChatbotLanguageModelEntity LanguageModel; public List Messages; public string? SessionTitle { get; internal set; } - public AgentSkillCode? RootSkill { get; set; } + public SkillCode? RootSkill { get; set; } public List GetMessages() => Messages.Select(ToChatMessage).ToList(); @@ -506,7 +505,7 @@ public List GetTools() return activatedSkills .Select(name => RootSkill.FindSkill(name)) - .OfType() + .OfType() .SelectMany(skill => skill.GetTools()) .ToList(); } diff --git a/Extensions/Signum.Agent/Message.tsx b/Extensions/Signum.Agent/Message.tsx index a82de52be9..0a3a5fb366 100644 --- a/Extensions/Signum.Agent/Message.tsx +++ b/Extensions/Signum.Agent/Message.tsx @@ -27,8 +27,8 @@ export const Message: React.NamedExoticComponent<{ msg: ChatMessageEntity; toolR }, (a, b) => a.msg.id != null && a.toolResponses == b.toolResponses); -export function looksLikeJson(text: string) { - return text && (text.trim().startsWith("{") || text.trim().startsWith("[")); +export function looksLikeJson(text: string) : boolean { + return text != null && (text.trim().startsWith("{") || text.trim().startsWith("[")); } export function SystemMessage(p: { msg: ChatMessageEntity }): React.ReactElement { @@ -204,7 +204,7 @@ export function ToolResponseBlock(p: { msg: ChatMessageEntity }): React.ReactEle ); } -export function MarkdownOrJson(p: { content: string | null | undefined, formatJson?: boolean }) { +export function MarkdownOrJson(p: { content: string | null | undefined, formatJson?: boolean }) : JSX.Element { if (!p.content) return {p.content + ""}; @@ -218,7 +218,7 @@ export function MarkdownOrJson(p: { content: string | null | undefined, formatJs ); } -export function tryParseJsonString(str: string) { +export function tryParseJsonString(str: string): string { try { if (str.startsWith("\"") && str.endsWith("\"")) { return JSON.parse(str); diff --git a/Extensions/Signum.Agent/Signum.Agent.d.ts b/Extensions/Signum.Agent/Signum.Agent.d.ts deleted file mode 100644 index 1511c374a6..0000000000 --- a/Extensions/Signum.Agent/Signum.Agent.d.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { MessageKey, Type, EnumType } from '../../Signum/React/Reflection'; -import * as Entities from '../../Signum/React/Signum.Entities'; -import * as Basics from '../../Signum/React/Signum.Basics'; -import * as Operations from '../../Signum/React/Signum.Operations'; -import * as Authorization from '../Signum.Authorization/Signum.Authorization'; -export declare const AgentSkillCodeEntity: Type; -export interface AgentSkillCodeEntity extends Entities.Entity { - Type: "AgentSkillCode"; - fullClassName: string; -} -export declare const AgentUseCaseSymbol: Type; -export interface AgentUseCaseSymbol extends Basics.Symbol { - Type: "AgentUseCase"; -} -export declare namespace AgentUseCase { - const DefaultChatbot: AgentUseCaseSymbol; - const Summarizer: AgentUseCaseSymbol; -} -export declare const AgentSkillEntity: Type; -export interface AgentSkillEntity extends Entities.Entity { - Type: "AgentSkill"; - name: string; - skillCode: AgentSkillCodeEntity; - active: boolean; - useCase: AgentUseCaseSymbol | null; - shortDescription: string | null; - instructions: string | null; - propertyOverrides: Entities.MList; - subSkills: Entities.MList; -} -export declare namespace AgentSkillOperation { - const Save: Operations.ExecuteSymbol; - const Delete: Operations.DeleteSymbol; -} -export declare const AgentSkillPropertyOverrideEmbedded: Type; -export interface AgentSkillPropertyOverrideEmbedded extends Entities.EmbeddedEntity { - Type: "AgentSkillPropertyOverrideEmbedded"; - propertyName: string; - value: string | null; -} -export declare const AgentSkillSubSkillEmbedded: Type; -export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { - Type: "AgentSkillSubSkillEmbedded"; - skill: Entities.Lite; - activation: SkillActivation; -} -export declare const SkillActivation: EnumType; -export type SkillActivation = "Eager" | "Lazy"; -export interface ToolCallEmbedded { - _response?: ChatMessageEntity; -} -export declare const ChatbotConfigurationEmbedded: Type; -export interface ChatbotConfigurationEmbedded extends Entities.EmbeddedEntity { - Type: "ChatbotConfigurationEmbedded"; - openAIAPIKey: string | null; - anthropicAPIKey: string | null; - geminiAPIKey: string | null; - mistralAPIKey: string | null; - githubModelsToken: string | null; - ollamaUrl: string | null; -} -export declare const ChatbotLanguageModelEntity: Type; -export interface ChatbotLanguageModelEntity extends Entities.Entity { - Type: "ChatbotLanguageModel"; - provider: LanguageModelProviderSymbol; - model: string; - temperature: number | null; - maxTokens: number | null; - isDefault: boolean; -} -export declare namespace ChatbotLanguageModelOperation { - const Save: Operations.ExecuteSymbol; - const MakeDefault: Operations.ExecuteSymbol; - const Delete: Operations.DeleteSymbol; -} -export declare namespace ChatbotMessage { - const OpenSession: MessageKey; - const NewSession: MessageKey; - const Send: MessageKey; - const TypeAMessage: MessageKey; - const InitialInstruction: MessageKey; -} -export declare namespace ChatbotPermission { - const UseChatbot: Basics.PermissionSymbol; -} -export declare const ChatbotUICommand: EnumType; -export type ChatbotUICommand = "System" | "SessionId" | "SessionTitle" | "QuestionId" | "MessageId" | "AssistantAnswer" | "AssistantTool" | "AssistantUITool" | "Tool" | "Exception"; -export declare const ChatMessageEntity: Type; -export interface ChatMessageEntity extends Entities.Entity { - Type: "ChatMessage"; - chatSession: Entities.Lite; - creationDate: string; - role: ChatMessageRole; - content: string | null; - toolCalls: Entities.MList; - toolCallID: string | null; - toolID: string | null; - exception: Entities.Lite | null; -} -export declare namespace ChatMessageOperation { - const Save: Operations.ExecuteSymbol; - const Delete: Operations.DeleteSymbol; -} -export declare const ChatMessageRole: EnumType; -export type ChatMessageRole = "System" | "User" | "Assistant" | "Tool"; -export declare const ChatSessionEntity: Type; -export interface ChatSessionEntity extends Entities.Entity { - Type: "ChatSession"; - title: string | null; - languageModel: Entities.Lite; - user: Entities.Lite; - startDate: string; -} -export declare namespace ChatSessionOperation { - const Delete: Operations.DeleteSymbol; -} -export declare const EmbeddingsLanguageModelEntity: Type; -export interface EmbeddingsLanguageModelEntity extends Entities.Entity { - Type: "EmbeddingsLanguageModel"; - provider: LanguageModelProviderSymbol; - model: string; - dimensions: number | null; - isDefault: boolean; -} -export declare namespace EmbeddingsLanguageModelOperation { - const Save: Operations.ExecuteSymbol; - const MakeDefault: Operations.ExecuteSymbol; - const Delete: Operations.DeleteSymbol; -} -export declare namespace LanguageModelProviders { - const OpenAI: LanguageModelProviderSymbol; - const Gemini: LanguageModelProviderSymbol; - const Anthropic: LanguageModelProviderSymbol; - const Mistral: LanguageModelProviderSymbol; - const GithubModels: LanguageModelProviderSymbol; - const Ollama: LanguageModelProviderSymbol; -} -export declare const LanguageModelProviderSymbol: Type; -export interface LanguageModelProviderSymbol extends Basics.Symbol { - Type: "LanguageModelProvider"; -} -export declare const ToolCallEmbedded: Type; -export interface ToolCallEmbedded extends Entities.EmbeddedEntity { - Type: "ToolCallEmbedded"; - callId: string; - toolId: string; - arguments: string; -} -//# sourceMappingURL=Signum.Agent.d.ts.map \ No newline at end of file diff --git a/Extensions/Signum.Agent/Signum.Agent.ts b/Extensions/Signum.Agent/Signum.Agent.ts index a771659435..c037697a9c 100644 --- a/Extensions/Signum.Agent/Signum.Agent.ts +++ b/Extensions/Signum.Agent/Signum.Agent.ts @@ -12,53 +12,9 @@ export interface ToolCallEmbedded { _response?: ChatMessageEntity } -export const AgentSkillCodeEntity: Type = new Type("AgentSkillCode"); -export interface AgentSkillCodeEntity extends Entities.Entity { - Type: "AgentSkillCode"; - fullClassName: string; -} - -export const AgentSkillEntity: Type = new Type("AgentSkill"); -export interface AgentSkillEntity extends Entities.Entity { - Type: "AgentSkill"; - name: string; - skillCode: AgentSkillCodeEntity; - active: boolean; - useCase: AgentUseCaseSymbol | null; - shortDescription: string | null; - instructions: string | null; - propertyOverrides: Entities.MList; - subSkills: Entities.MList; -} - -export namespace AgentSkillOperation { - export const Save : Operations.ExecuteSymbol = registerSymbol("Operation", "AgentSkillOperation.Save"); - export const Delete : Operations.DeleteSymbol = registerSymbol("Operation", "AgentSkillOperation.Delete"); - export const CreateFromUseCase : Operations.ConstructSymbol_From = registerSymbol("Operation", "AgentSkillOperation.CreateFromUseCase"); -} - -export const AgentSkillPropertyOverrideEmbedded: Type = new Type("AgentSkillPropertyOverrideEmbedded"); -export interface AgentSkillPropertyOverrideEmbedded extends Entities.EmbeddedEntity { - Type: "AgentSkillPropertyOverrideEmbedded"; - propertyName: string; - value: string | null; -} - -export const AgentSkillSubSkillEmbedded: Type = new Type("AgentSkillSubSkillEmbedded"); -export interface AgentSkillSubSkillEmbedded extends Entities.EmbeddedEntity { - Type: "AgentSkillSubSkillEmbedded"; - skill: Entities.Lite; - activation: SkillActivation; -} - -export namespace AgentUseCase { - export const DefaultChatbot : AgentUseCaseSymbol = registerSymbol("AgentUseCase", "AgentUseCase.DefaultChatbot"); - export const Summarizer : AgentUseCaseSymbol = registerSymbol("AgentUseCase", "AgentUseCase.Summarizer"); -} - -export const AgentUseCaseSymbol: Type = new Type("AgentUseCase"); -export interface AgentUseCaseSymbol extends Basics.Symbol { - Type: "AgentUseCase"; +export const AgentSymbol: Type = new Type("Agent"); +export interface AgentSymbol extends Basics.Symbol { + Type: "Agent"; } export const ChatbotConfigurationEmbedded: Type = new Type("ChatbotConfigurationEmbedded"); @@ -178,6 +134,12 @@ export namespace ChatSessionOperation { export const Delete : Operations.DeleteSymbol = registerSymbol("Operation", "ChatSessionOperation.Delete"); } +export namespace DefaultAgent { + export const Chatbot : AgentSymbol = registerSymbol("Agent", "DefaultAgent.Chatbot"); + export const QuestionSummarizer : AgentSymbol = registerSymbol("Agent", "DefaultAgent.QuestionSummarizer"); + export const ConversationSumarizer : AgentSymbol = registerSymbol("Agent", "DefaultAgent.ConversationSumarizer"); +} + export const EmbeddingsLanguageModelEntity: Type = new Type("EmbeddingsLanguageModel"); export interface EmbeddingsLanguageModelEntity extends Entities.Entity { Type: "EmbeddingsLanguageModel"; @@ -213,6 +175,43 @@ export type SkillActivation = "Eager" | "Lazy"; +export const SkillCodeEntity: Type = new Type("SkillCode"); +export interface SkillCodeEntity extends Entities.Entity { + Type: "SkillCode"; + className: string; +} + +export const SkillCustomizationEntity: Type = new Type("SkillCustomization"); +export interface SkillCustomizationEntity extends Entities.Entity { + Type: "SkillCustomization"; + skillCode: SkillCodeEntity; + agent: AgentSymbol | null; + shortDescription: string | null; + instructions: string | null; + properties: Entities.MList; + subSkills: Entities.MList; +} + +export namespace SkillCustomizationOperation { + export const Save : Operations.ExecuteSymbol = registerSymbol("Operation", "SkillCustomizationOperation.Save"); + export const Delete : Operations.DeleteSymbol = registerSymbol("Operation", "SkillCustomizationOperation.Delete"); + export const CreateFromAgent : Operations.ConstructSymbol_From = registerSymbol("Operation", "SkillCustomizationOperation.CreateFromAgent"); +} + +export const SkillPropertyEmbedded: Type = new Type("SkillPropertyEmbedded"); +export interface SkillPropertyEmbedded extends Entities.EmbeddedEntity { + Type: "SkillPropertyEmbedded"; + propertyName: string; + value: string | null; +} + +export const SubSkillEmbedded: Type = new Type("SubSkillEmbedded"); +export interface SubSkillEmbedded extends Entities.EmbeddedEntity { + Type: "SubSkillEmbedded"; + skill: Entities.Entity; + activation: SkillActivation; +} + export const ToolCallEmbedded: Type = new Type("ToolCallEmbedded"); export interface ToolCallEmbedded extends Entities.EmbeddedEntity { Type: "ToolCallEmbedded"; diff --git a/Extensions/Signum.Agent/SkillCode.cs b/Extensions/Signum.Agent/SkillCode.cs new file mode 100644 index 0000000000..50582f85aa --- /dev/null +++ b/Extensions/Signum.Agent/SkillCode.cs @@ -0,0 +1,194 @@ +using Microsoft.Extensions.AI; +using ModelContextProtocol.Server; +using Signum.API; +using System.ComponentModel; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; + +namespace Signum.Agent; + +public abstract class SkillCode +{ + public SkillCode() + { + if (SkillCodeLogic.IsAutoRegister) + SkillCodeLogic.Register(this.GetType()); + else + { + if (!SkillCodeLogic.RegisteredCodes.ContainsKey(this.GetType().Name)) + throw new InvalidOperationException($"Type '{this.GetType().Name}' must be registered in SkillCodeLogic.Register<{this.GetType().TypeName()}>()"); + } + } + + public string Name => this.GetType().Name; + + public Lite? Customization { get; internal set; } + + public string ShortDescription { get; set; } = ""; + public Func IsAllowed { get; set; } = () => true; + public Dictionary>? Replacements; + + public static string SkillsDirectory = Path.Combine( + Path.GetDirectoryName(typeof(SkillCode).Assembly.Location)!, "Skills"); + + string? originalInstructions; + public string OriginalInstructions + { + get { return originalInstructions ??= File.ReadAllText(Path.Combine(SkillsDirectory, this.GetType().Name.Before("Skill") + ".md")); } + set { originalInstructions = value; } + } + public bool IsDefault() + { + if (SubSkills.Count > 0) return false; + + var defaultCode = (SkillCode)Activator.CreateInstance(GetType())!; + if (ShortDescription != defaultCode.ShortDescription) return false; + if (OriginalInstructions != defaultCode.OriginalInstructions) return false; + + foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var attr = pi.GetCustomAttribute(); + if (attr == null) continue; + var currentStr = attr.ConvertValueToString(pi.GetValue(this), pi.PropertyType); + var defaultStr = attr.ConvertValueToString(pi.GetValue(defaultCode), pi.PropertyType); + if (currentStr != defaultStr) return false; + } + + return true; + } + + // Populated from DB at resolve time, or from code when building a default tree for a factory. + public List<(SkillCode Code, SkillActivation Activation)> SubSkills { get; } = new(); + + public SkillCode WithSubSkill(SkillActivation activation, SkillCode sub) + { + SubSkills.Add((sub, activation)); + return this; + } + + public string GetInstruction(object? context) + { + var text = OriginalInstructions; + if (!Replacements.IsNullOrEmpty()) + text = text.Replace(Replacements.SelectDictionary(k => k, v => v(context))); + + if (SubSkills.Any()) + { + var sb = new StringBuilder(text); + foreach (var (sub, activation) in SubSkills) + { + sb.AppendLineLF("# Skill " + sub.Name); + sb.AppendLineLF("**Summary**: " + sub.ShortDescription); + sb.AppendLineLF(); + if (activation == SkillActivation.Eager) + sb.AppendLineLF(sub.GetInstruction(null)); + else + sb.AppendLineLF("Use the tool 'describe' to get more information about this skill and discover additional tools."); + } + return sb.ToString(); + } + + return text; + } + + public void ApplyPropertyOverrides(SkillCustomizationEntity entity) + { + foreach (var po in entity.Properties) + { + var pi = this.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(p => p.Name == po.PropertyName + && p.GetCustomAttribute() != null); + + if (pi == null) continue; + + var attr = pi.GetCustomAttribute()!; + var value = attr.ConvertFromString(po.Value, pi.PropertyType); + pi.SetValue(this, value); + } + } + + public SkillCode? FindSkill(string name) + { + if (this.Name == name) return this; + foreach (var (sub, _) in SubSkills) + { + var found = sub.FindSkill(name); + if (found != null) return found; + } + return null; + } + + public AITool? FindTool(string toolName) + { + var tool = GetTools().FirstOrDefault(t => t.Name.Equals(toolName, StringComparison.InvariantCultureIgnoreCase)); + if (tool != null) return tool; + foreach (var (sub, _) in SubSkills) + { + var found = sub.FindTool(toolName); + if (found != null) return found; + } + return null; + } + + public IEnumerable GetSkillsRecursive() + { + yield return this; + foreach (var (sub, _) in SubSkills) + foreach (var s in sub.GetSkillsRecursive()) + yield return s; + } + + public IEnumerable GetEagerSkillsRecursive() + { + yield return this; + foreach (var (sub, activation) in SubSkills) + if (activation == SkillActivation.Eager) + foreach (var s in sub.GetEagerSkillsRecursive()) + yield return s; + } + + public IEnumerable GetToolsRecursive() + { + var list = GetTools().ToList(); + foreach (var (sub, activation) in SubSkills) + if (activation == SkillActivation.Eager) + list.AddRange(sub.GetToolsRecursive()); + return list; + } + + IEnumerable? cachedTools; + internal IEnumerable GetTools() + { + return (cachedTools ??= this.GetType() + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Where(m => m.GetCustomAttribute() != null) + .Select(m => + { + Type delType = Expression.GetDelegateType( + m.GetParameters().Select(a => a.ParameterType).And(m.ReturnType).ToArray()); + Delegate del = m.IsStatic + ? Delegate.CreateDelegate(delType, m) + : Delegate.CreateDelegate(delType, this, m); + string? description = m.GetCustomAttribute()?.Description; + return (AITool)AIFunctionFactory.Create(del, m.Name, description, GetJsonSerializerOptions()); + }) + .ToList()); + } + + internal IEnumerable GetMcpServerTools() => + GetTools().Select(t => McpServerTool.Create((AIFunction)t, new McpServerToolCreateOptions + { + SerializerOptions = GetJsonSerializerOptions(), + })); + + static JsonSerializerOptions JsonSerializationOptions = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }.AddSignumJsonConverters(); + + public virtual JsonSerializerOptions GetJsonSerializerOptions() => JsonSerializationOptions; +} diff --git a/Extensions/Signum.Agent/SkillCodeLogic.cs b/Extensions/Signum.Agent/SkillCodeLogic.cs new file mode 100644 index 0000000000..e809dc2743 --- /dev/null +++ b/Extensions/Signum.Agent/SkillCodeLogic.cs @@ -0,0 +1,148 @@ +using Signum.Engine.Sync; +using System.Collections.Frozen; + +namespace Signum.Agent; + +public static class SkillCodeLogic +{ + public static ResetLazy> TypeToEntity = null!; + public static ResetLazy> EntityToType = null!; + + public static Dictionary RegisteredCodes = new(); + + internal static void Register(Type type) + { + if (!typeof(SkillCode).IsAssignableFrom(type)) + throw new InvalidOperationException($"Type '{type.FullName}' must derive from SkillCode"); + + if(RegisteredCodes.TryGetValue(type.Name, out var already) && already != type) + throw new InvalidOperationException($"Type '{type.FullName}' is already registered with a different type."); + + RegisteredCodes[type.Name!] = type; + } + + public static void Register() + where T : SkillCode => Register(typeof(T)); + + public static void Start(SchemaBuilder sb) + { + if (sb.AlreadyDefined(MethodInfo.GetCurrentMethod())) + return; + + sb.Schema.Generating += Schema_Generating; + sb.Schema.Synchronizing += Schema_Synchronizing; + sb.Include() + .WithQuery(() => e => new + { + Entity = e, + e.Id, + e.ClassName + }); + + + TypeToEntity = sb.GlobalLazy(() => + { + var dbAtentCodes = Database.RetrieveAll(); + return EnumerableExtensions.JoinRelaxed( + dbAtentCodes, + RegisteredCodes.Values, + entity => entity.ClassName, + type => type!.Name!, + (entity, type) => KeyValuePair.Create(type, entity), + "caching " + nameof(SkillCodeEntity)) + .ToFrozenDictionaryEx(); + }, new InvalidateWith(typeof(SkillCodeEntity))); + + sb.Schema.Initializing += () => TypeToEntity.Load(); + + EntityToType = sb.GlobalLazy(() => TypeToEntity.Value.Inverse().ToFrozenDictionaryEx(), + new InvalidateWith(typeof(SkillCodeEntity))); + } + + public static Type ToType( this SkillCodeEntity codeEntity) + { + return EntityToType.Value.GetOrThrow(codeEntity); + } + + public static SkillCodeEntity ToSkillCodeEntity(Type type) + { + return TypeToEntity.Value.GetOrThrow(type); + } + + static SqlPreCommand? Schema_Generating() + { + var table = Schema.Current.Table(); + return GenerateCodeEntities() + .Select(e => table.InsertSqlSync(e)) + .Combine(Spacing.Simple); + } + + static SqlPreCommand? Schema_Synchronizing(Replacements replacements) + { + var table = Schema.Current.Table(); + var should = GenerateCodeEntities().ToDictionary(e => e.ClassName); + var current = Administrator.TryRetrieveAll(replacements) + .ToDictionary(e => e.ClassName); + + return Synchronizer.SynchronizeScript(Spacing.Double, should, current, + createNew: (_, s) => table.InsertSqlSync(s), + removeOld: (_, c) => table.DeleteSqlSync(c, e => e.ClassName == c.ClassName), + mergeBoth: (_, s, c) => table.UpdateSqlSync(c, e => e.ClassName == c.ClassName)); + } + + static List GenerateCodeEntities() => + RegisteredCodes.Values.Select(type => new SkillCodeEntity { ClassName = type.Name! }).ToList(); + + + public static DefaultSkillCodeInfo GetDefaultSkillCodeInfo(string skillCodeName) + { + if (!SkillCodeLogic.RegisteredCodes.TryGetValue(skillCodeName, out var type)) + throw new KeyNotFoundException($"AgentSkillCode type '{skillCodeName}' is not registered."); + + var instance = (SkillCode)Activator.CreateInstance(type)!; + + var properties = type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(pi => new { pi, attr = pi.GetCustomAttribute() }) + .Where(x => x.attr != null) + .Select(x => new DefaultSkillCodeProperty + { + PropertyName = x.pi.Name, + AttributeName = x.attr!.GetType().Name.Before("Attribute"), + ValueHint = x.attr.ValueHint, + PropertyType = x.pi.PropertyType.TypeName(), + }) + .ToList(); + + return new DefaultSkillCodeInfo + { + DefaultShortDescription = instance.ShortDescription, + DefaultInstructions = instance.OriginalInstructions, + Properties = properties, + }; + } + + public static bool IsAutoRegister; + internal static IDisposable AutoRegister() + { + IsAutoRegister = true; + return new Disposable(() => IsAutoRegister = false); + } + + +} + +public class DefaultSkillCodeInfo +{ + public string DefaultShortDescription { get; set; } = null!; + public string DefaultInstructions { get; set; } = null!; + public List Properties { get; set; } = null!; +} + +public class DefaultSkillCodeProperty +{ + public string PropertyName { get; set; } = null!; + public string AttributeName { get; set; } = null!; + public string? ValueHint { get; set; } + public string PropertyType { get; set; } = null!; +} diff --git a/Extensions/Signum.Agent/SkillCustomizationEntity.cs b/Extensions/Signum.Agent/SkillCustomizationEntity.cs new file mode 100644 index 0000000000..9d66bb03d5 --- /dev/null +++ b/Extensions/Signum.Agent/SkillCustomizationEntity.cs @@ -0,0 +1,100 @@ +namespace Signum.Agent; + +[EntityKind(EntityKind.SystemString, EntityData.Master), TicksColumn(false)] +public class SkillCodeEntity : Entity +{ + [UniqueIndex] + public string ClassName { get; set; } + + [AutoExpressionField] + public override string ToString() => As.Expression(() => ClassName); +} + +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] +public class AgentSymbol : Symbol +{ + private AgentSymbol() { } + + public AgentSymbol(Type declaringType, string fieldName) : + base(declaringType, fieldName) + { + } +} + +[AutoInit] +public static class DefaultAgent +{ + public static AgentSymbol Chatbot; + public static AgentSymbol QuestionSummarizer; + public static AgentSymbol ConversationSumarizer; +} + +[EntityKind(EntityKind.Main, EntityData.Master)] +public class SkillCustomizationEntity : Entity +{ + public SkillCodeEntity SkillCode { get; set; } + + [UniqueIndex] + public AgentSymbol? Agent { get; set; } + + [StringLengthValidator(Min = 1, Max = 500)] + public string? ShortDescription { get; set; } + + [StringLengthValidator(MultiLine = true)] + public string? Instructions { get; set; } + + [BindParent] + public MList Properties { get; set; } = new MList(); + + [BindParent] + public MList SubSkills { get; set; } = new MList(); + + [AutoExpressionField] + public override string ToString() => As.Expression(() => IsNew ? this.BaseToString() : SkillCode.ToString()); + + protected override string? ChildPropertyValidation(ModifiableEntity sender, PropertyInfo pi) + { + if (sender is SkillPropertyEmbedded po + && pi.Name == nameof(SkillPropertyEmbedded.Value) + && SkillCode != null) + { + var propInfo = SkillCode.ToType().GetProperty(po.PropertyName, BindingFlags.Public | BindingFlags.Instance); + if (propInfo == null) + return $"Skill {SkillCode} has not property {po.PropertyName}"; + + var attr = propInfo?.GetCustomAttribute(); + if (propInfo == null || attr == null) + return $"Property {po.PropertyName} of type {SkillCode} has not AgentSkillProperty"; + + return attr.ValidateValue(po.Value, propInfo!.PropertyType); + } + + return base.ChildPropertyValidation(sender, pi); + } +} + +public class SkillPropertyEmbedded : EmbeddedEntity +{ + [StringLengthValidator(Min = 1, Max = 200)] + public string PropertyName { get; set; } + + [StringLengthValidator(MultiLine = true)] + public string? Value { get; set; } +} + +public class SubSkillEmbedded : EmbeddedEntity +{ + // Can reference either an AgentSkillEntity (customised) or AgentSkillCodeEntity (default, no DB entity needed) + [ImplementedBy(typeof(SkillCustomizationEntity), typeof(SkillCodeEntity))] + public Entity Skill { get; set; } + + public SkillActivation Activation { get; set; } +} + +[AutoInit] +public static class SkillCustomizationOperation +{ + public static ExecuteSymbol Save = null!; + public static DeleteSymbol Delete = null!; + public static ConstructSymbol.From CreateFromAgent = null!; +} diff --git a/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs b/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs index 16f167c5cd..41d97e6d94 100644 --- a/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs +++ b/Extensions/Signum.Agent/Skills/AutocompleteSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class AutocompleteSkill : AgentSkillCode +public class AutocompleteSkill : SkillCode { public AutocompleteSkill() { diff --git a/Extensions/Signum.Agent/Skills/ChartSkill.cs b/Extensions/Signum.Agent/Skills/ChartSkill.cs index 539c42ccd3..f48d6795c3 100644 --- a/Extensions/Signum.Agent/Skills/ChartSkill.cs +++ b/Extensions/Signum.Agent/Skills/ChartSkill.cs @@ -11,7 +11,7 @@ namespace Signum.Agent.Skills; -public class ChartSkill : AgentSkillCode +public class ChartSkill : SkillCode { public ChartSkill() { diff --git a/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs b/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs index 894f6fc4c1..f08346805e 100644 --- a/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs +++ b/Extensions/Signum.Agent/Skills/ConfirmUISkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class ConfirmUISkill : AgentSkillCode +public class ConfirmUISkill : SkillCode { public ConfirmUISkill() { diff --git a/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs b/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs index 3334017950..db509bd8b6 100644 --- a/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs +++ b/Extensions/Signum.Agent/Skills/ConversationSumarizerSkill.cs @@ -2,7 +2,7 @@ namespace Signum.Agent.Skills; -public class ConversationSumarizerSkill : AgentSkillCode +public class ConversationSumarizerSkill : SkillCode { public ConversationSumarizerSkill() { diff --git a/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs b/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs index 032c729966..bdefd33a5f 100644 --- a/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs +++ b/Extensions/Signum.Agent/Skills/CurrentServerContextSkill.cs @@ -5,7 +5,7 @@ namespace Signum.Agent.Skills; -public class CurrentServerContextSkill : AgentSkillCode +public class CurrentServerContextSkill : SkillCode { public static Func? UrlLeft; diff --git a/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs b/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs index 5b3af43424..58e7024f6f 100644 --- a/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs +++ b/Extensions/Signum.Agent/Skills/EntityUrlSkill.cs @@ -1,6 +1,6 @@ namespace Signum.Agent.Skills; -public class EntityUrlSkill : AgentSkillCode +public class EntityUrlSkill : SkillCode { public EntityUrlSkill() { diff --git a/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs b/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs index ceed091cc8..0e087ab056 100644 --- a/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs +++ b/Extensions/Signum.Agent/Skills/GetUIContextSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class GetUIContextSkill : AgentSkillCode +public class GetUIContextSkill : SkillCode { public GetUIContextSkill() { diff --git a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs index 90fcc8e5a8..de3c69df5b 100644 --- a/Extensions/Signum.Agent/Skills/IntroductionSkill.cs +++ b/Extensions/Signum.Agent/Skills/IntroductionSkill.cs @@ -6,7 +6,7 @@ namespace Signum.Agent.Skills; -public class IntroductionSkill : AgentSkillCode +public class IntroductionSkill : SkillCode { public IntroductionSkill() { diff --git a/Extensions/Signum.Agent/Skills/OperationSkill.cs b/Extensions/Signum.Agent/Skills/OperationSkill.cs index d00b9d1410..0c651d8480 100644 --- a/Extensions/Signum.Agent/Skills/OperationSkill.cs +++ b/Extensions/Signum.Agent/Skills/OperationSkill.cs @@ -6,7 +6,7 @@ namespace Signum.Agent.Skills; -public class OperationSkill : AgentSkillCode +public class OperationSkill : SkillCode { public OperationSkill() { diff --git a/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs b/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs index d9e627b9c8..2b3e5256ed 100644 --- a/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs +++ b/Extensions/Signum.Agent/Skills/QuestionSumarizerSkill.cs @@ -3,7 +3,7 @@ namespace Signum.Agent.Skills; -public class QuestionSumarizerSkill : AgentSkillCode +public class QuestionSumarizerSkill : SkillCode { public QuestionSumarizerSkill() { diff --git a/Extensions/Signum.Agent/Skills/RetrieveSkill.cs b/Extensions/Signum.Agent/Skills/RetrieveSkill.cs index 0ad1174d79..3ef0e57f33 100644 --- a/Extensions/Signum.Agent/Skills/RetrieveSkill.cs +++ b/Extensions/Signum.Agent/Skills/RetrieveSkill.cs @@ -4,7 +4,7 @@ namespace Signum.Agent.Skills; -public class RetrieveSkill : AgentSkillCode +public class RetrieveSkill : SkillCode { public RetrieveSkill() { diff --git a/Extensions/Signum.Agent/Skills/Search.md b/Extensions/Signum.Agent/Skills/Search.md index 53dcee2fec..415b553bcc 100644 --- a/Extensions/Signum.Agent/Skills/Search.md +++ b/Extensions/Signum.Agent/Skills/Search.md @@ -203,7 +203,9 @@ IMPORTANT: Always set the appropiate `columnOptionsMode`; when grouping use `Rep Each column has: * `token`: the expression to use, can not use `Any`, `All`. * `displayName`: optional, if not specified the default name will be used. -* `summaryToken`: optional, only used to shown and aggregate in the header of the column. Can be used even if the `FindOptions` does not set `groupResults`. You can not aggregate twice (avoid `Count.Sum`, just use `Count`), but you can sum the number of elements in a collection (`Friends.Count.Sum`). +* `summaryToken`: optional, executes a separated query with the same filters to shown and aggregate in the header of the column. Can be used even if the `FindOptions` does not set `groupResults`. IMPORTANT: + * You can not aggregate twice, like `Count.Sum`, bacause `Count` is an aggegate, just use `Count`. + * But you can sum the number of elements in a collection, like `Friends.Count.Sum`, because `Friends.Count` is a property of the `Friends` collection. * `hiddenColumn`: optional, if true the column will not be shown, only usefull for hiding the real grouping key if `groupResults` is true. * `combineRows`: optional, if specified consecutive rows with the same value in this column will be combined in one row with rowspan in the html table. `EqualValue` compares similar values, `EqualEntity` compares the entity ids. diff --git a/Extensions/Signum.Agent/Skills/SearchSkill.cs b/Extensions/Signum.Agent/Skills/SearchSkill.cs index aec9a94f22..0345b81145 100644 --- a/Extensions/Signum.Agent/Skills/SearchSkill.cs +++ b/Extensions/Signum.Agent/Skills/SearchSkill.cs @@ -14,16 +14,13 @@ namespace Signum.Agent.Skills; -public class SearchSkill : AgentSkillCode +public class SearchSkill : SkillCode { - public Func InlineQueryName = q => false; + [SkillProperty_QueryList] + public HashSet InlineQueryName { get; set; } = new HashSet(); - public SearchSkill(params HashSet queries) : this(q => queries.Contains(q)) + public SearchSkill() { - } - public SearchSkill(Func inlineQueryName) - { - InlineQueryName = inlineQueryName; ShortDescription = "Explores the database schema and queries any information in the database"; IsAllowed = () => true; Replacements = new Dictionary>() @@ -34,7 +31,7 @@ public SearchSkill(Func inlineQueryName) .GroupBy(a => a is Type t? t.Namespace : a is Enum e ? e.GetType().Namespace : "Unknown") .ToString(gr => { - var inlineQueries = gr.Where(InlineQueryName).ToString(qn => + var inlineQueries = gr.Where(InlineQueryName.Contains).ToString(qn => { var imp = QueryLogic.Queries.GetEntityImplementations(qn); var impStr = imp.Types.Only() == qn as Type ? "" : $" (ImplementedBy {imp.Types.ToString(t => t.Name, ", ")})"; diff --git a/Extensions/Signum.Agent/Templates/ChatMarkdown.tsx b/Extensions/Signum.Agent/Templates/ChatMarkdown.tsx index 791c5cc526..0e0de5d302 100644 --- a/Extensions/Signum.Agent/Templates/ChatMarkdown.tsx +++ b/Extensions/Signum.Agent/Templates/ChatMarkdown.tsx @@ -5,27 +5,27 @@ import remarkGfm from 'remark-gfm' import { toAbsoluteUrl } from '@framework/AppContext'; -export default function ChatMarkdown(p: { content: string }){ +export default function ChatMarkdown(p: { content: string }): JSX.Element { return {p.content}; } - function renderTable({ node, children, ...props }: React.PropsWithChildren> & { node?: any }): React.ReactNode { - return {children}
; - } +function renderTable({ node, children, ...props }: React.PropsWithChildren> & { node?: any }): React.ReactNode { + return {children}
; +} - export function renderLink({ node, href, children, ...props }: React.PropsWithChildren> & { node?: any }): React.ReactNode { - debugger; - if (href && href.startsWith("/")) - return {children}; +export function renderLink({ node, href, children, ...props }: React.PropsWithChildren> & { node?: any }): React.ReactNode { + debugger; + if (href && href.startsWith("/")) + return {children}; - var origin = document.location.origin + toAbsoluteUrl("~/"); - if (href && href.startsWith(origin)) - return {children}; + var origin = document.location.origin + toAbsoluteUrl("~/"); + if (href && href.startsWith(origin)) + return {children}; - return ( - - {children} - - ); - } + return ( + + {children} + + ); +} diff --git a/Extensions/Signum.Agent/Templates/AgentSkill.tsx b/Extensions/Signum.Agent/Templates/SkillCustomization.tsx similarity index 63% rename from Extensions/Signum.Agent/Templates/AgentSkill.tsx rename to Extensions/Signum.Agent/Templates/SkillCustomization.tsx index 18f2419426..80f1cf6040 100644 --- a/Extensions/Signum.Agent/Templates/AgentSkill.tsx +++ b/Extensions/Signum.Agent/Templates/SkillCustomization.tsx @@ -1,14 +1,14 @@ import * as React from 'react' import { CheckboxLine, EntityCombo, EntityLine, EntityTable, EnumLine, TextBoxLine } from '@framework/Lines' import { TypeContext } from '@framework/TypeContext' -import { AgentSkillEntity, AgentSkillPropertyOverrideEmbedded, AgentSkillSubSkillEmbedded } from '../Signum.Agent' -import { AgentSkillClient, SkillPropertyMeta } from '../AgentSkillClient' +import { SkillCustomizationEntity } from '../Signum.Agent' import { useAPI, useForceUpdate } from '@framework/Hooks' import { MarkdownLine } from '@framework/Lines/MarkdownLine' import { DiffDocument } from '../../Signum.DiffLog/Templates/DiffDocument' import { LinkButton } from '@framework/Basics/LinkButton' +import { AgentClient, SkillPropertyMeta } from '../AgentClient' -export default function AgentSkill(p: { ctx: TypeContext }): React.JSX.Element { +export default function SkillCustomization(p: { ctx: TypeContext }): React.JSX.Element { const ctx = p.ctx; const ctx4 = ctx.subCtx({ labelColumns: 4 }); const forceUpdate = useForceUpdate(); @@ -16,7 +16,7 @@ export default function AgentSkill(p: { ctx: TypeContext }): R const skillCode = ctx.value.skillCode; const skillCodeInfo = useAPI( - () => skillCode ? AgentSkillClient.API.getSkillCodeInfo(skillCode.fullClassName) : Promise.resolve(null), + () => skillCode ? AgentClient.API.getSkillCodeInfo(skillCode.className) : Promise.resolve(null), [skillCode] ); @@ -24,16 +24,14 @@ export default function AgentSkill(p: { ctx: TypeContext }): R
- e.name)} /> e.skillCode)} onChange={() => { ctx.value.shortDescription = null; ctx.value.instructions = null; - ctx.value.propertyOverrides = []; + ctx.value.properties = []; forceUpdate(); }} /> - e.active)} inlineCheckbox /> - e.useCase)} /> + e.agent)} />
e.shortDescription)} @@ -45,18 +43,13 @@ export default function AgentSkill(p: { ctx: TypeContext }): R -
- Sub-Skills - e.subSkills)} columns={[ + e.subSkills)} avoidFieldSet="h5" columns={[ { property: e => e.activation, template: ectx => e.activation)} /> }, { property: e => e.skill, template: ectx => e.skill)} /> }, ]} /> -
{skillCodeInfo && skillCodeInfo.properties.length > 0 && ( -
- Property Overrides - e.propertyOverrides)} columns={[ + e.properties)} avoidFieldSet="h5" columns={[ { property: e => e.propertyName, template: ectx => ( @@ -67,52 +60,43 @@ export default function AgentSkill(p: { ctx: TypeContext }): R }, { property: e => e.value, - template: ectx => e.value)} properties={skillCodeInfo.properties} propertyName={ectx.value.propertyName} /> + template: ectx => e.value)} + properties={skillCodeInfo.properties} + propertyName={ectx.value.propertyName} /> }, ]} /> -
)}
); } function InstructionsField(p: { - ctx: TypeContext, + ctx: TypeContext, info: { defaultInstructions: string } | null | undefined }): React.JSX.Element { const [showDiff, setShowDiff] = React.useState(false); const ctx = p.ctx; - const label = ( - - Instructions - {p.info && ( + const button = p.info && ( setShowDiff(v => !v)}> {showDiff ? "Show Editor" : "Show Diff"} - )} - - ); + ); - if (showDiff && p.info) { return (
- - +
+ {button} +
+ {showDiff && p.info ?: + e.instructions)} /> }
); - } - - return ( - e.instructions)} - helpText={p.info && ctx.value.instructions == null ? "Using default from code (.md file)" : undefined} - label={label as any} - /> - ); + } function PropertyValueControl(p: { @@ -126,7 +110,7 @@ function PropertyValueControl(p: { return ; } - const factory = AgentSkillClient.getPropertyValueControl(meta.attributeName); + const factory = AgentClient.getPropertyValueControl(meta.attributeName); if (factory) { return factory(p.ctx, meta); } diff --git a/Extensions/Signum.Chart/GoogleMapScripts/Markermap.tsx b/Extensions/Signum.Chart/GoogleMapScripts/Markermap.tsx index 825d2e9406..80b46c0564 100644 --- a/Extensions/Signum.Chart/GoogleMapScripts/Markermap.tsx +++ b/Extensions/Signum.Chart/GoogleMapScripts/Markermap.tsx @@ -1,4 +1,3 @@ -/// import * as React from 'react' import * as d3 from 'd3' import { Navigator } from '@framework/Navigator'; diff --git a/Extensions/Signum.Dynamic/View/DynamicViewComponent.tsx b/Extensions/Signum.Dynamic/View/DynamicViewComponent.tsx index b80ef9711f..6c05340281 100644 --- a/Extensions/Signum.Dynamic/View/DynamicViewComponent.tsx +++ b/Extensions/Signum.Dynamic/View/DynamicViewComponent.tsx @@ -22,6 +22,7 @@ import { DynamicViewEntity, DynamicViewOperation } from '../Signum.Dynamic.Views export interface DynamicViewComponentProps { ctx: TypeContext; initialDynamicView: DynamicViewEntity; + ref?: React.Ref; //...extraProps } @@ -31,7 +32,6 @@ export interface DynamicViewComponentState { selectedNode: DesignerNode; dynamicView: DynamicViewEntity; viewOverrides?: ViewOverride[]; - } export default function DynamicViewComponent(p: DynamicViewComponentProps): React.JSX.Element | null { @@ -47,7 +47,7 @@ export default function DynamicViewComponent(p: DynamicViewComponentProps): Reac const viewOverrides = useAPI(() => Navigator.viewDispatcher.getViewOverrides(p.ctx.value.Type), []); function getZeroNode() { - var { ctx, initialDynamicView, ...extraProps } = p; + var { ctx, initialDynamicView, ref, ...extraProps } = p; var context: DesignerContext = { onClose: handleClose, diff --git a/Extensions/Signum.Workflow/Case/CaseFrameModal.tsx b/Extensions/Signum.Workflow/Case/CaseFrameModal.tsx index 651e1d6497..74a65f99fc 100644 --- a/Extensions/Signum.Workflow/Case/CaseFrameModal.tsx +++ b/Extensions/Signum.Workflow/Case/CaseFrameModal.tsx @@ -44,7 +44,7 @@ interface CaseFrameModalState { var modalCount = 0; -export function CaseFrameModal(p: CaseFrameModalProps) { +export function CaseFrameModal(p: CaseFrameModalProps): JSX.Element { const [state, setState] = useStateWithPromise(undefined); const [show, setShow] = React.useState(true); diff --git a/Extensions/Signum.Workflow/Workflow/Workflow.tsx b/Extensions/Signum.Workflow/Workflow/Workflow.tsx index de883a9dd6..247a00827e 100644 --- a/Extensions/Signum.Workflow/Workflow/Workflow.tsx +++ b/Extensions/Signum.Workflow/Workflow/Workflow.tsx @@ -28,7 +28,7 @@ export interface WorkflowHandle { getSvg(): Promise; } -export function Workflow(p: WorkflowProps) { +export function Workflow(p: WorkflowProps) : JSX.Element { const bpmnModelerComponentRef = React.useRef(null);