summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/credentials/copilot.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-20 22:32:41 +0900
committerAdam Malczewski <[email protected]>2026-05-20 22:32:41 +0900
commitc0c55ed7c03188f264ff2c27b9982f2d57d092cc (patch)
tree04b871b83e6e1481c8b24b59aa3acc0e15b44f02 /packages/core/src/credentials/copilot.ts
parent8151447758e6826a578363758a755c6cebd1c05f (diff)
downloaddispatch-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.ts64
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;
+ }
+}