diff options
| author | Adam Malczewski <[email protected]> | 2026-05-20 22:32:41 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-20 22:32:41 +0900 |
| commit | c0c55ed7c03188f264ff2c27b9982f2d57d092cc (patch) | |
| tree | 04b871b83e6e1481c8b24b59aa3acc0e15b44f02 /packages/core/src/credentials/copilot.ts | |
| parent | 8151447758e6826a578363758a755c6cebd1c05f (diff) | |
| download | dispatch-c0c55ed7c03188f264ff2c27b9982f2d57d092cc.tar.gz dispatch-c0c55ed7c03188f264ff2c27b9982f2d57d092cc.zip | |
feat: key usage panel — display usage data for Claude, OpenCode, and Copilot
Adds 'Key Usage' to the sidebar dropdown with per-provider live usage:
- Claude: 5-hour and weekly utilization bars with reset timestamps
(normalizes Anthropic's 0-100% API response to 0-1 internally)
- OpenCode: Scrapes usage from workspace page via OPENCODE_COOKIE
session cookie, mapping key IDs to OPENCODE_WS1_ID/OPENCODE_WS2_ID
- Copilot: Fetches from api.github.com/copilot_internal/user with
entitlement/remaining/quota reset tracking
New files: opencode.ts, copilot.ts (usage fetchers), KeyUsage.svelte
New route: GET /models/key-usage?keyId=X dispatches by provider
Diffstat (limited to 'packages/core/src/credentials/copilot.ts')
| -rw-r--r-- | packages/core/src/credentials/copilot.ts | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/packages/core/src/credentials/copilot.ts b/packages/core/src/credentials/copilot.ts new file mode 100644 index 0000000..8baf6f4 --- /dev/null +++ b/packages/core/src/credentials/copilot.ts @@ -0,0 +1,64 @@ +// ─── GitHub Copilot Usage Tracking ─────────────────────────── +// Uses the internal GitHub Copilot user endpoint (same one the +// official VS Code Copilot extension calls). + +export interface CopilotUsageReport { + tokensConsumed?: number; + tokensRemaining?: number; + percentUsed?: number; // 0-100 + resetAt?: number; // Unix timestamp ms + plan?: string; +} + +export async function fetchCopilotUsage( + token: string, + _baseUrl: string, +): Promise<CopilotUsageReport | null> { + const url = "https://api.github.com/copilot_internal/user"; + const headers: Record<string, string> = { + authorization: `Bearer ${token}`, + accept: "application/json", + }; + + try { + const response = await fetch(url, { headers }); + if (!response.ok) return null; + + const data = (await response.json()) as Record<string, unknown>; + const plan = typeof data.copilot_plan === "string" ? data.copilot_plan : undefined; + + const resetDate = typeof data.quota_reset_date === "string" ? data.quota_reset_date : undefined; + const resetAt = resetDate ? Date.parse(resetDate) : undefined; + + const qs = data.quota_snapshots as Record<string, unknown> | undefined; + const pi = qs?.premium_interactions as Record<string, unknown> | undefined; + + const entitlement = typeof pi?.entitlement === "number" ? pi.entitlement : undefined; + const remaining = typeof pi?.remaining === "number" ? pi.remaining : undefined; + const percentRemaining = + typeof pi?.percent_remaining === "number" ? pi.percent_remaining : undefined; + + if (entitlement === undefined && remaining === undefined) { + return null; + } + + const tokensConsumed = + entitlement !== undefined && remaining !== undefined ? entitlement - remaining : undefined; + const percentUsed = + percentRemaining !== undefined + ? Math.round((100 - percentRemaining) * 100) / 100 + : tokensConsumed !== undefined && entitlement !== undefined && entitlement > 0 + ? Math.round((tokensConsumed / entitlement) * 10000) / 100 + : undefined; + + return { + tokensConsumed, + tokensRemaining: remaining, + percentUsed, + resetAt: resetAt && !Number.isNaN(resetAt) ? resetAt : undefined, + plan, + }; + } catch { + return null; + } +} |
