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
@@ -127,6 +127,10 @@ internal sealed class CopilotTurnExecutionState
_pendingEvents.Enqueue(CreateHookLifecycleEvent(agent, "end", hookEnd.Data));
}
break;
case AssistantUsageEvent assistantUsage:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateAssistantUsageEvent(agent, assistantUsage.Data));
break;
case SessionUsageInfoEvent usageInfo:
ActiveAgent = agent;
_pendingEvents.Enqueue(CreateUsageEvent(agent, usageInfo.Data));
@@ -410,6 +414,29 @@ internal sealed class CopilotTurnExecutionState
};
}
private AssistantUsageEventDto CreateAssistantUsageEvent(
AgentIdentity agent,
AssistantUsageData? data)
{
return new AssistantUsageEventDto
{
Type = "assistant-usage",
RequestId = _command.RequestId,
SessionId = _command.SessionId,
AgentId = agent.AgentId,
AgentName = agent.AgentName,
Model = data?.Model ?? string.Empty,
InputTokens = data?.InputTokens,
OutputTokens = data?.OutputTokens,
CacheReadTokens = data?.CacheReadTokens,
CacheWriteTokens = data?.CacheWriteTokens,
Cost = data?.Cost,
Duration = data?.Duration,
TotalNanoAiu = data?.CopilotUsage?.TotalNanoAiu,
QuotaSnapshots = QuotaSnapshotMapper.MapOrNull(data?.QuotaSnapshots),
};
}
private SessionUsageEventDto CreateUsageEvent(AgentIdentity agent, SessionUsageInfoData? data)
{
return new SessionUsageEventDto