feat: surface Copilot usage and quota data across the UI

Add three layers of usage visibility:

- ChatPane footer: premium request count, AIU consumed, and quota
  remaining below the existing context-window bar
- Settings / CopilotStatusCard: on-demand account quota section with
  progress bars, overage indicators, and reset dates fetched via
  the new get-quota sidecar command
- Activity Panel: per-agent token/cost/duration totals on each agent
  row and a Session Usage summary section between agents and timeline

Backend (sidecar):
- New get-quota command using SDK account.getQuota RPC
- New assistant-usage turn-scoped event from SDK assistant.usage
- QuotaSnapshotMapper for both typed and untyped SDK quota payloads
- DTOs: GetQuotaCommandDto, QuotaSnapshotDto, AccountQuotaResultEventDto,
  AssistantUsageEventDto

Frontend:
- Shared types: AssistantUsageEvent, QuotaSnapshot, GetQuotaCommand
- IPC bridge: getQuota channel, assistant-usage event dispatch
- State: SessionRequestUsageMap accumulator with per-agent breakdown
- 8 new tests for accumulator logic and formatting helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-29 00:46:09 +01:00
co-authored by Copilot
parent 48efbf36f9
commit 92832c6116
26 changed files with 978 additions and 18 deletions
+25
View File
@@ -17,6 +17,8 @@ import type { InteractionMode, MessageMode } from '@shared/contracts/sidecar';
import type { ChatMessageAttachment } from '@shared/domain/attachment';
import { getAttachmentDisplayName, isImageAttachment } from '@shared/domain/attachment';
import type { SessionUsageState } from '@renderer/lib/sessionActivity';
import type { SessionRequestUsageState, AgentUsageAccumulator } from '@renderer/lib/sessionActivity';
import { formatNanoAiu } from '@renderer/lib/sessionActivity';
import type { ActiveSubagent } from '@renderer/lib/subagentTracker';
import {
findModel,
@@ -46,6 +48,7 @@ interface ChatPaneProps {
mcpProbingServerIds?: string[];
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
sessionUsage?: SessionUsageState;
sessionRequestUsage?: SessionRequestUsageState;
activeSubagents?: ReadonlyArray<ActiveSubagent>;
terminalOpen?: boolean;
terminalRunning?: boolean;
@@ -75,6 +78,7 @@ export function ChatPane({
mcpProbingServerIds,
runtimeTools,
sessionUsage,
sessionRequestUsage,
activeSubagents,
terminalOpen,
terminalRunning,
@@ -777,6 +781,27 @@ export function ChatPane({
{Math.round((sessionUsage.currentTokens / sessionUsage.tokenLimit) * 100)}% context
</span>
</div>
{sessionRequestUsage && sessionRequestUsage.requestCount > 0 && (
<div className="mt-0.5 flex items-center gap-1 text-[10px] text-zinc-600">
<span className="tabular-nums">
{sessionRequestUsage.requestCount} premium request{sessionRequestUsage.requestCount === 1 ? '' : 's'}
</span>
{sessionRequestUsage.totalNanoAiu > 0 && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">{formatNanoAiu(sessionRequestUsage.totalNanoAiu)} AIU</span>
</>
)}
{sessionRequestUsage.latestQuotaSnapshots?.premium_interactions && (
<>
<span className="text-zinc-700">·</span>
<span className="tabular-nums">
{Math.round(sessionRequestUsage.latestQuotaSnapshots.premium_interactions.remainingPercentage)}% quota
</span>
</>
)}
</div>
)}
</div>
)}
</div>