diff --git a/src/OneWare.Copilot/CopilotModule.cs b/src/OneWare.Copilot/CopilotModule.cs index ceb8289d0..63ab035e1 100644 --- a/src/OneWare.Copilot/CopilotModule.cs +++ b/src/OneWare.Copilot/CopilotModule.cs @@ -162,13 +162,13 @@ ABSOLUTE PROHIBITIONS [ new PackageVersion() { - Version = "1.0.37", + Version = "1.0.54", Targets = [ new PackageTarget() { Target = "win-x64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-win32-x64.zip", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-win32-x64.zip", AutoSetting = [ new PackageAutoSetting @@ -181,7 +181,7 @@ ABSOLUTE PROHIBITIONS new PackageTarget() { Target = "win-arm64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-win32-arm64.zip", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-win32-arm64.zip", AutoSetting = [ new PackageAutoSetting @@ -194,7 +194,7 @@ ABSOLUTE PROHIBITIONS new PackageTarget() { Target = "linux-x64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-linux-x64.tar.gz", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-linux-x64.tar.gz", AutoSetting = [ new PackageAutoSetting @@ -207,7 +207,7 @@ ABSOLUTE PROHIBITIONS new PackageTarget() { Target = "linux-arm64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-linux-arm64.tar.gz", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-linux-arm64.tar.gz", AutoSetting = [ new PackageAutoSetting @@ -220,7 +220,7 @@ ABSOLUTE PROHIBITIONS new PackageTarget() { Target = "osx-x64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-darwin-x64.tar.gz", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-darwin-x64.tar.gz", AutoSetting = [ new PackageAutoSetting @@ -233,7 +233,7 @@ ABSOLUTE PROHIBITIONS new PackageTarget() { Target = "osx-arm64", - Url = "https://github.com/github/copilot-cli/releases/download/v1.0.37/copilot-darwin-arm64.tar.gz", + Url = "https://github.com/github/copilot-cli/releases/download/v1.0.54/copilot-darwin-arm64.tar.gz", AutoSetting = [ new PackageAutoSetting diff --git a/src/OneWare.Copilot/OneWare.Copilot.csproj b/src/OneWare.Copilot/OneWare.Copilot.csproj index c487a8b5a..4be4bc897 100644 --- a/src/OneWare.Copilot/OneWare.Copilot.csproj +++ b/src/OneWare.Copilot/OneWare.Copilot.csproj @@ -8,10 +8,11 @@ true + $(NoWarn);GHCP001 - + \ No newline at end of file diff --git a/src/OneWare.Copilot/Services/CopilotChatService.cs b/src/OneWare.Copilot/Services/CopilotChatService.cs index 5382ec2a3..0084b73f7 100644 --- a/src/OneWare.Copilot/Services/CopilotChatService.cs +++ b/src/OneWare.Copilot/Services/CopilotChatService.cs @@ -1,13 +1,16 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using Avalonia.Controls; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DynamicData; -using GitHub.Copilot.SDK; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using OneWare.Copilot.Models; using OneWare.Copilot.ViewModels; @@ -36,6 +39,20 @@ public sealed class CopilotChatService( private string? _requestedSessionId; private readonly HashSet _allowedPermissionScopes = new(StringComparer.OrdinalIgnoreCase); + // Usage tracking + public long LastInputTokens { get; private set => SetProperty(ref field, value); } + public long LastOutputTokens { get; private set => SetProperty(ref field, value); } + public long? LastReasoningTokens { get; private set => SetProperty(ref field, value); } + public long SessionTotalRequests { get; private set => SetProperty(ref field, value); } + public long SessionTotalInputTokens { get; private set => SetProperty(ref field, value); } + public long SessionTotalOutputTokens { get; private set => SetProperty(ref field, value); } + public long ContextCurrentTokens { get; private set => SetProperty(ref field, value); } + public long ContextTokenLimit { get; private set => SetProperty(ref field, value); } + public double? QuotaRemainingPercent { get; private set => SetProperty(ref field, value); } + public bool QuotaIsUnlimited { get; private set => SetProperty(ref field, value); } + public DateTimeOffset? QuotaResetDate { get; private set => SetProperty(ref field, value); } + public bool HasUsageData { get; private set => SetProperty(ref field, value); } + private static readonly Regex DeviceLoginUrlRegex = new(@"https?://\S+", RegexOptions.Compiled); private static readonly Regex DeviceLoginCodeRegex = new(@"\bcode\s+([A-Z0-9\-]+)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -197,8 +214,8 @@ public async Task InitializeAsync() _client = new CopilotClient(new CopilotClientOptions() { - Cwd = paths.ProjectsDirectory, - CliPath = cliPath + WorkingDirectory = paths.ProjectsDirectory, + Connection = RuntimeConnection.ForStdio(cliPath, []) }); bool isAuthenticated; @@ -279,7 +296,7 @@ private async Task InitializeSessionAsync() if (string.IsNullOrWhiteSpace(sessionId)) { - var tools = toolProvider.GetTools(); + var tools = toolProvider.GetTools().Cast().ToList(); _session = await _client.CreateSessionAsync(new SessionConfig { Model = SelectedModel.Id, @@ -304,7 +321,7 @@ private async Task InitializeSessionAsync() { Streaming = true, IncludeSubAgentStreamingEvents = false, - Tools = toolProvider.GetTools(), + Tools = toolProvider.GetTools().Cast().ToList(), OnPermissionRequest = OnPermissionRequestAsync, OnUserInputRequest = OnUserInputRequestAsync }); @@ -320,7 +337,7 @@ private async Task InitializeSessionAsync() return; } - _subscription = _session.On(HandleSessionEvent); + _subscription = _session.On(HandleSessionEvent); } private string BuildSystemMessage() @@ -429,6 +446,23 @@ private async Task DisposeSessionAsync() } CurrentSessionId = null; + ResetUsageStats(); + } + + private void ResetUsageStats() + { + LastInputTokens = 0; + LastOutputTokens = 0; + LastReasoningTokens = null; + SessionTotalRequests = 0; + SessionTotalInputTokens = 0; + SessionTotalOutputTokens = 0; + ContextCurrentTokens = 0; + ContextTokenLimit = 0; + QuotaRemainingPercent = null; + QuotaIsUnlimited = false; + QuotaResetDate = null; + HasUsageData = false; } private void HandleSessionEvent(SessionEvent evt) @@ -478,10 +512,29 @@ private void HandleSessionEvent(SessionEvent evt) case SessionIdleEvent: EventReceived?.Invoke(this, new ChatIdleEvent()); break; + case AssistantUsageEvent usage: + UpdateUsageFromAssistantEvent(usage.Data); + break; + case SessionUsageInfoEvent info: + ContextCurrentTokens = info.Data.CurrentTokens; + ContextTokenLimit = info.Data.TokenLimit; + break; } } - private Task OnPermissionRequestAsync( + private void UpdateUsageFromAssistantEvent(AssistantUsageData data) + { + LastInputTokens = data.InputTokens ?? 0; + LastOutputTokens = data.OutputTokens ?? 0; + LastReasoningTokens = data.ReasoningTokens is > 0 ? data.ReasoningTokens : null; + SessionTotalRequests++; + SessionTotalInputTokens += data.InputTokens ?? 0; + SessionTotalOutputTokens += data.OutputTokens ?? 0; + + HasUsageData = true; + } + + private Task OnPermissionRequestAsync( PermissionRequest request, PermissionInvocation invocation) { @@ -496,7 +549,7 @@ private Task OnPermissionRequestAsync( return Task.FromResult(CreateAllowPermissionResult()); } - var responseSource = new TaskCompletionSource( + var responseSource = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); var context = BuildPermissionContext(request, invocation); @@ -611,22 +664,14 @@ private static void AddDetail(ICollection details, string label, string? details.Add($"{label}: `{trimmed}`"); } - private static PermissionRequestResult CreateAllowPermissionResult() + private static PermissionDecision CreateAllowPermissionResult() { - return new PermissionRequestResult - { - Kind = PermissionRequestResultKind.Approved, - Rules = null - }; + return PermissionDecision.ApproveOnce(); } - private static PermissionRequestResult CreateDenyPermissionResult() + private static PermissionDecision CreateDenyPermissionResult() { - return new PermissionRequestResult - { - Kind = PermissionRequestResultKind.Rejected, - Rules = null - }; + return PermissionDecision.Reject(""); } private static bool IsCustomToolPermissionRequest(PermissionRequest request) diff --git a/src/OneWare.Copilot/Views/CopilotChatExtensionView.axaml b/src/OneWare.Copilot/Views/CopilotChatExtensionView.axaml index 5e1d3a2b0..bd1933b43 100644 --- a/src/OneWare.Copilot/Views/CopilotChatExtensionView.axaml +++ b/src/OneWare.Copilot/Views/CopilotChatExtensionView.axaml @@ -7,40 +7,138 @@ xmlns:behaviors="clr-namespace:OneWare.Essentials.Behaviors;assembly=OneWare.Essentials" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OneWare.Copilot.Views.CopilotChatExtensionView" x:DataType="services:CopilotChatService"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file