< Summary

Information
Class: UIBlazor.Services.SkillService
Assembly: UIBlazor
File(s): /home/runner/work/InvAit/InvAit/UIBlazor/Services/SkillService.cs
Tag: 71_26091983037
Line coverage
71%
Covered lines: 59
Uncovered lines: 23
Coverable lines: 82
Total lines: 164
Line coverage: 71.9%
Branch coverage
80%
Covered branches: 21
Total branches: 26
Branch coverage: 80.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetSkillsMetadataAsync()85.71%1414100%
LoadSkillContentAsync()87.5%8885%
FormatSkillsForSystemPrompt(...)100%44100%
RefreshCacheAsync()100%11100%
LoadSkillContentMarkDownAsync()0%2040%

File(s)

/home/runner/work/InvAit/InvAit/UIBlazor/Services/SkillService.cs

#LineLine coverage
 1namespace UIBlazor.Services;
 2
 93public class SkillService(IVsBridge vsBridge) : ISkillService
 4{
 5    private List<SkillMetadata>? _skillsCache;
 96    private readonly Dictionary<string, SkillContent> _contentCache = new();
 97    private DateTime _lastCacheUpdate = DateTime.MinValue;
 8
 9    /// <summary>
 10    /// Получить метаданные всех скиллов (кешируется)
 11    /// Вызывается при старте и при изменении файлов
 12    /// </summary>
 13    public async Task<List<SkillMetadata>> GetSkillsMetadataAsync(CancellationToken cancellationToken)
 14    {
 15        // Проверяем кеш (обновляем раз в 2 минуты или по запросу)
 616        if (_skillsCache != null && (DateTime.UtcNow - _lastCacheUpdate).TotalMinutes < 2)
 17        {
 118            return _skillsCache;
 19        }
 20
 521        var result = await vsBridge.ExecuteToolAsync(BasicEnum.GetSkillsMetadata, cancellationToken: cancellationToken);
 522        if (!result.Success)
 23        {
 124            return _skillsCache ?? [];
 25        }
 26
 27        try
 28        {
 429            var metadataJson = JsonSerializer.Deserialize<List<Dictionary<string, string>>>(result.Result);
 730            _skillsCache = metadataJson?.Select(m => new SkillMetadata
 731            {
 732                Name = m.GetValueOrDefault("name", ""),
 733                Description = m.GetValueOrDefault("description", "")
 734            }).ToList() ?? [];
 35
 336            _lastCacheUpdate = DateTime.UtcNow;
 337            return _skillsCache;
 38        }
 139        catch
 40        {
 141            return _skillsCache ?? [];
 42        }
 643    }
 44
 45    /// <summary>
 46    /// Загрузить полное содержимое скилла (с кешированием)
 47    /// Вызывается только когда агент активирует скилл
 48    /// </summary>
 49    public async Task<SkillContent?> LoadSkillContentAsync(string skillName, CancellationToken cancellationToken)
 50    {
 51        // Проверяем кеш содержимого
 1852        if (_contentCache.TryGetValue(skillName, out var cachedContent))
 53        {
 254            return cachedContent;
 55        }
 56
 1657        var args = new Dictionary<string, object>
 1658        {
 1659            { "param1", skillName }
 1660        };
 61
 1662        var result = await vsBridge.ExecuteToolAsync(BasicEnum.ReadSkillContent, args, cancellationToken);
 1663        if (!result.Success)
 64        {
 165            return null;
 66        }
 67
 68        try
 69        {
 1570            var skillContent = JsonUtils.Deserialize<SkillContent>(result.Result);
 1571            if (skillContent == null)
 072                return null;
 73
 74            // Кешируем содержимое (максимум 10 скиллов в кеше)
 1575            if (_contentCache.Count >= 10)
 76            {
 277                var oldestKey = _contentCache.Keys.First();
 278                _contentCache.Remove(oldestKey);
 79            }
 1580            _contentCache[skillName] = skillContent;
 81
 1582            return skillContent;
 83        }
 084        catch
 85        {
 086            return null;
 87        }
 1888    }
 89
 90    /// <summary>
 91    /// Форматирует список скиллов для добавления в системный промпт
 92    /// Только название и описание (триггеры для активации)
 93    /// </summary>
 94    public string FormatSkillsForSystemPrompt(List<SkillMetadata> skills)
 95    {
 296        if (skills.Count == 0)
 97        {
 198            return string.Empty;
 99        }
 100
 1101        var sb = new StringBuilder();
 1102        sb.AppendLine("## Available Skills");
 1103        sb.AppendLine();
 1104        sb.AppendLine("You have access to the following skills. Skills are specialized instructions that you can activat
 1105        sb.AppendLine();
 106
 4107        foreach (var skill in skills)
 108        {
 1109            sb.AppendLine();
 1110            sb.AppendLine($"""
 1111                           - **{skill.Name}**: {skill.Description}
 1112                           To read instructions:
 1113                           <function name="{BasicEnum.ReadSkillContent}">
 1114                           {skill.Name}
 1115                           </function>
 1116                           """);
 1117            sb.AppendLine();
 118        }
 119
 1120        sb.AppendLine($"When you need detailed instructions from a skill, use `{BasicEnum.ReadSkillContent}` tool to loa
 121
 1122        return sb.ToString();
 123    }
 124
 125    /// <summary>
 126    /// Принудительно обновить кеш скиллов
 127    /// При изменении файлов (через FileSystemWatcher)
 128    /// </summary>
 129    public async Task RefreshCacheAsync(CancellationToken cancellationToken)
 130    {
 1131        _lastCacheUpdate = DateTime.MinValue; // Сбрасываем кеш
 1132        _contentCache.Clear(); // Очищаем кеш содержимого
 1133        await GetSkillsMetadataAsync(cancellationToken); // Перезагружаем метаданные
 1134    }
 135
 136    public async Task<VsToolResult> LoadSkillContentMarkDownAsync(IReadOnlyDictionary<string, object> args, Cancellation
 137    {
 0138        var skillName = args.GetString("param1");
 0139        if (string.IsNullOrEmpty(skillName))
 140        {
 0141            return new VsToolResult
 0142            {
 0143                Success = false,
 0144                Result = "Skill name is required parameter!"
 0145            };
 146        }
 0147        var skillContent = await LoadSkillContentAsync(skillName, cancellationToken);
 148
 0149        if (skillContent == null)
 150        {
 0151            return new VsToolResult
 0152            {
 0153                Success = false,
 0154                Result = "Skill content is empty..."
 0155            };
 156        }
 0157        var sb = new StringBuilder();
 0158        sb.AppendLine();
 0159        sb.AppendLine($"## Skill {skillName}");
 0160        sb.AppendLine(skillContent.Content);
 161
 0162        return new VsToolResult { Result = sb.ToString() };
 0163    }
 164}