| 1 | using System.Collections.Concurrent; |
| 2 | using System.Text.Json; |
| 3 | |
| 4 | namespace AgentNotesMcp.Status; |
| 5 | |
| 6 | internal static class AgentNotesToolCallRingBuffer |
| 7 | { |
| 8 | internal const int DefaultCapacity = 64; |
| 9 | |
| 10 | private static readonly ConcurrentQueue<ToolCallEntry> Queue = new(); |
| 11 | |
| 12 | internal static void Record( |
| 13 | string toolName, |
| 14 | IReadOnlyDictionary<string, JsonElement> args, |
| 15 | string resultText, |
| 16 | bool isError, |
| 17 | long durationMs) |
| 18 | { |
| 19 | var entry = new ToolCallEntry( |
| 20 | DateTimeOffset.UtcNow, |
| 21 | toolName, |
| 22 | TryGetWorkspacePath(args), |
| 23 | isError, |
| 24 | durationMs, |
| 25 | Preview(resultText)); |
| 26 | |
| 27 | Queue.Enqueue(entry); |
| 28 | while (Queue.Count > DefaultCapacity && Queue.TryDequeue(out _)) |
| 29 | { |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | internal static IReadOnlyList<ToolCallEntry> Snapshot() |
| 34 | { |
| 35 | var list = Queue.ToArray(); |
| 36 | if (list.Length <= 1) |
| 37 | return list; |
| 38 | |
| 39 | Array.Reverse(list); |
| 40 | return list; |
| 41 | } |
| 42 | |
| 43 | internal static void ClearForTests() |
| 44 | { |
| 45 | while (Queue.TryDequeue(out _)) |
| 46 | { |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | private static string? TryGetWorkspacePath(IReadOnlyDictionary<string, JsonElement> args) |
| 51 | { |
| 52 | if (!args.TryGetValue("workspace_path", out var el)) |
| 53 | return null; |
| 54 | |
| 55 | var path = el.GetString(); |
| 56 | return string.IsNullOrWhiteSpace(path) ? null : path.Trim(); |
| 57 | } |
| 58 | |
| 59 | private static string Preview(string text) |
| 60 | { |
| 61 | if (string.IsNullOrEmpty(text)) |
| 62 | return ""; |
| 63 | |
| 64 | var oneLine = text.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' '); |
| 65 | const int max = 120; |
| 66 | return oneLine.Length <= max ? oneLine : oneLine[..max] + "…"; |
| 67 | } |
| 68 | |
| 69 | internal sealed record ToolCallEntry( |
| 70 | DateTimeOffset AtUtc, |
| 71 | string ToolName, |
| 72 | string? WorkspacePath, |
| 73 | bool IsError, |
| 74 | long DurationMs, |
| 75 | string ResultPreview); |
| 76 | } |
| 77 | |