< Summary

Information
Class: UIBlazor.Services.SkillService
Assembly: UIBlazor
File(s): /home/runner/work/InvAit/InvAit/UIBlazor/Services/SkillService.cs
Tag: 14_22728831704
Line coverage
74%
Covered lines: 71
Uncovered lines: 24
Coverable lines: 95
Total lines: 183
Line coverage: 74.7%
Branch coverage
67%
Covered branches: 27
Total branches: 40
Branch coverage: 67.5%
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()71.42%1414100%
FormatSkillsForSystemPrompt(...)100%66100%
RefreshCacheAsync()100%11100%
LoadSkillContentMarkDownAsync()0%7280%

File(s)

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

#LineLine coverage
 1namespace UIBlazor.Services;
 2
 103public class SkillService(IVsBridge vsBridge) : ISkillService
 4{
 5    private List<SkillMetadata>? _skillsCache;
 106    private readonly Dictionary<string, SkillContent> _contentCache = new();
 107    private DateTime _lastCacheUpdate = DateTime.MinValue;
 8
 9    /// <summary>
 10    /// Получить метаданные всех скиллов (кешируется)
 11    /// Вызывается при старте и при изменении файлов
 12    /// </summary>
 13    public async Task<List<SkillMetadata>> GetSkillsMetadataAsync()
 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);
 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)
 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);
 1663        if (!result.Success)
 64        {
 165            return null;
 66        }
 67
 68        try
 69        {
 1570            var contentJson = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.Result);
 1571            if (contentJson == null) return null;
 72
 1573            var skillContent = new SkillContent
 1574            {
 1575                Name = contentJson.GetValueOrDefault("name", default).GetString() ?? "",
 1576                Description = contentJson.GetValueOrDefault("description", default).GetString() ?? "",
 1577                Content = contentJson.GetValueOrDefault("content", default).GetString() ?? "",
 1578                Resources = [.. contentJson.GetValueOrDefault("resources", default)
 1579                    .EnumerateArray()
 280                    .Select(r => r.GetString() ?? "")
 281                    .Where(r => !string.IsNullOrEmpty(r))]
 1582            };
 83
 84            // Кешируем содержимое (максимум 10 скиллов в кеше)
 1385            if (_contentCache.Count >= 10)
 86            {
 287                var oldestKey = _contentCache.Keys.First();
 288                _contentCache.Remove(oldestKey);
 89            }
 1390            _contentCache[skillName] = skillContent;
 91
 1392            return skillContent;
 93        }
 294        catch
 95        {
 296            return null;
 97        }
 1898    }
 99
 100    /// <summary>
 101    /// Форматирует список скиллов для добавления в системный промпт
 102    /// Только название и описание (триггеры для активации)
 103    /// </summary>
 104    public string FormatSkillsForSystemPrompt(List<SkillMetadata> skills)
 105    {
 3106        if (skills == null || skills.Count == 0)
 107        {
 2108            return string.Empty;
 109        }
 110
 1111        var sb = new StringBuilder();
 1112        sb.AppendLine("## Available Skills");
 1113        sb.AppendLine();
 1114        sb.AppendLine("You have access to the following skills. Skills are specialized instructions that you can activat
 1115        sb.AppendLine();
 116
 4117        foreach (var skill in skills)
 118        {
 1119            sb.AppendLine($"");
 1120            sb.AppendLine($"""
 1121                           - **{skill.Name}**: {skill.Description}
 1122                           To read instructions:
 1123                           <function name="{BasicEnum.ReadSkillContent}">
 1124                           {skill.Name}
 1125                           </function>
 1126                           """);
 1127            sb.AppendLine();
 128        }
 129
 1130        sb.AppendLine($"When you need detailed instructions from a skill, use `{BasicEnum.ReadSkillContent}` tool to loa
 131
 1132        return sb.ToString();
 133    }
 134
 135    /// <summary>
 136    /// Принудительно обновить кеш скиллов
 137    /// При изменении файлов (через FileSystemWatcher)
 138    /// </summary>
 139    public async Task RefreshCacheAsync()
 140    {
 1141        _lastCacheUpdate = DateTime.MinValue; // Сбрасываем кеш
 1142        _contentCache.Clear(); // Очищаем кеш содержимого
 1143        await GetSkillsMetadataAsync(); // Перезагружаем метаданные
 1144    }
 145
 146    public async Task<VsToolResult> LoadSkillContentMarkDownAsync(IReadOnlyDictionary<string, object> args)
 147    {
 0148        var skillName = args.GetString("param1");
 0149        if (string.IsNullOrEmpty(skillName))
 150        {
 0151            return new VsToolResult
 0152            {
 0153                Success = false,
 0154                Result = "Skill name is required parameter!"
 0155            };
 156        }
 0157        var skillContent = await LoadSkillContentAsync(skillName);
 158
 0159        if (skillContent == null)
 160        {
 0161            return new VsToolResult
 0162            {
 0163                Success = false,
 0164                Result = "Skill content is empty..."
 0165            };
 166        }
 0167        var sb = new StringBuilder();
 0168        sb.AppendLine();
 0169        sb.AppendLine($"## Skill {skillName}");
 0170        sb.AppendLine(skillContent.Content);
 171
 172        // TODO тут надо почитать как это работает и зачем
 0173        if (skillContent.Resources.Count > 0) {
 0174            sb.AppendLine($"### Skill Resources");
 0175            foreach (var resource in skillContent.Resources)
 176            {
 0177                sb.AppendLine($"- {resource}");
 178            }
 179        }
 180
 0181        return new VsToolResult { Result = sb.ToString() };
 0182    }
 183}