From 7ebc7fff8dd1f299d273f8e0f667508208792c76 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:31:51 +0000 Subject: [PATCH] fix(usage): show Copilot premium request quota The usage source discarded quota snapshots, leaving the card with only a plan and reset date. Publish finite premium usage through the existing window interface and retain its remaining request count. Co-authored-by: Ryan Swift --- .../copilot-usage-source/server/usage.test.ts | 87 +++++++++++++++++++ plugins/copilot-usage-source/server/usage.ts | 39 ++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/plugins/copilot-usage-source/server/usage.test.ts b/plugins/copilot-usage-source/server/usage.test.ts index 8333f28828..def2a2d0c2 100644 --- a/plugins/copilot-usage-source/server/usage.test.ts +++ b/plugins/copilot-usage-source/server/usage.test.ts @@ -171,3 +171,90 @@ it.each([401, 403])("reports an existing login rejected with HTTP %i", async (st else process.env["COPILOT_TOKEN"] = previous; } }); + +it.each([ + { remaining: 1500, percentRemaining: 100, usedPct: 0 }, + { remaining: 1125, percentRemaining: 75, usedPct: 25 }, + { remaining: 0, percentRemaining: 0, usedPct: 100 }, +])( + "reports premium quota with $remaining requests remaining", + async ({ remaining, percentRemaining, usedPct }) => { + const report = await fetchPremiumQuota({ + entitlement: 1500, + remaining, + percent_remaining: percentRemaining, + unlimited: false, + }); + expect(report).toMatchObject({ + status: "available", + planLabel: "individual", + windows: [ + { + id: "premium_interactions", + label: "Premium requests", + usedPct, + remainingPct: percentRemaining, + resetsAt: "2026-12-01T00:00:00.000Z", + }, + ], + details: expect.arrayContaining([ + { + id: "premium_remaining", + label: "Premium requests remaining", + value: `${remaining} / 1500`, + }, + ]), + }); + }, +); + +it.each([ + undefined, + null, + { unlimited: true, percent_remaining: 100, remaining: 1500, entitlement: 1500 }, +])( + "does not invent a finite quota for absent or unlimited premium snapshots (%j)", + async (premium) => { + const report = await fetchPremiumQuota(premium); + expect(report).toMatchObject({ + status: "available", + windows: [], + details: [{ id: "reset", label: "Quota reset", value: "2026-12-01" }], + }); + }, +); + +it("reports a percentage without inventing missing counts and falls back to the legacy reset", async () => { + const report = await fetchPremiumQuota({ percent_remaining: 60, unlimited: false }, null); + expect(report).toMatchObject({ + status: "available", + windows: [{ usedPct: 40, remainingPct: 60, resetsAt: "2026-12-01" }], + details: [{ id: "reset", label: "Quota reset", value: "2026-12-01" }], + }); +}); + +async function fetchPremiumQuota( + premium: unknown, + utcReset: string | null = "2026-12-01T00:00:00.000Z", +) { + const { writeFile } = await import("node:fs/promises"); + const directory = mkdtempSync(join(tmpdir(), "copilot-premium-")); + const locator = join(directory, "hosts.yml"); + try { + await writeFile(locator, "github.com:\n oauth_token: fixture-copilot\n"); + return await fetchUsage({ store: "file", locator }, async () => + jsonResponse({ + copilot_plan: "individual", + quota_reset_date: "2026-12-01", + quota_reset_date_utc: utcReset, + quota_snapshots: { + premium_interactions: premium, + chat: { unlimited: true, percent_remaining: 100 }, + completions: { unlimited: true, percent_remaining: 100 }, + }, + }), + ); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} diff --git a/plugins/copilot-usage-source/server/usage.ts b/plugins/copilot-usage-source/server/usage.ts index 78ae61bc9f..b7159c6970 100644 --- a/plugins/copilot-usage-source/server/usage.ts +++ b/plugins/copilot-usage-source/server/usage.ts @@ -5,6 +5,8 @@ import { join } from "node:path"; import { z } from "zod"; import { unavailable, + windowFromUsedPct, + type UsageWindow, type UsageAccount, type UsageReport, type UsageDetail, @@ -18,6 +20,19 @@ const ApiOptionalStringSchema = z.preprocess( const CopilotUsageResponseSchema = z.object({ copilot_plan: ApiOptionalStringSchema, quota_reset_date: ApiOptionalStringSchema, + quota_reset_date_utc: ApiOptionalStringSchema, + quota_snapshots: z + .object({ + premium_interactions: z + .object({ + entitlement: z.number().nonnegative().nullish(), + remaining: z.number().nonnegative().nullish(), + percent_remaining: z.number().min(0).max(100).nullish(), + unlimited: z.boolean().optional(), + }) + .nullish(), + }) + .nullish(), }); async function readToken(input: UsageInput): Promise { @@ -72,10 +87,32 @@ export async function fetchUsage( ? [{ id: "reset", label: "Quota reset", value: resp.quota_reset_date }] : []; + const windows: UsageWindow[] = []; + const premium = resp.quota_snapshots?.premium_interactions; + if (premium && !premium.unlimited) { + if (premium.percent_remaining != null) { + windows.push( + windowFromUsedPct({ + id: "premium_interactions", + label: "Premium requests", + utilizationPct: 100 - premium.percent_remaining, + resetsAt: resp.quota_reset_date_utc || resp.quota_reset_date, + }), + ); + } + if (premium.remaining != null && premium.entitlement != null) { + details.push({ + id: "premium_remaining", + label: "Premium requests remaining", + value: `${premium.remaining} / ${premium.entitlement}`, + }); + } + } + return { status: "available", planLabel: resp.copilot_plan || undefined, - windows: [], + windows, balances: [], details, };