fix(usage): derive window names and unify pin selection (#5903)

* fix(usage): derive window names and unify pin selection

Quota slots do not identify a period. Derive numeric windows and IDs from reported duration and quota scope; keep unknown lengths neutral. Resolve source defaults and explicit pins through one model, with a persisted empty selection distinct from defaults.

* fix(plugins): declare the duration helper host requirement

* fix(usage): verify persisted selections at the storage boundary
This commit is contained in:
Mohamed Boudra
2026-10-02 11:18:13 +01:00
committed by GitHub
parent 6c985769be
commit 73d25317a4
27 changed files with 815 additions and 172 deletions
+3 -1
View File
@@ -188,7 +188,9 @@ promise for completion: equal results, including equal discovery timestamps, emi
## Usage sources
See the [public usage source reference](../public-docs/plugins/reference.md#usage-sources) for the
contract, account identity, login fallback, and read-only credential rules.
contract, account and window identity, provider-derived period names, login fallback, and
read-only credential rules. Usage adapters own the interpretation of provider fields; the app
renders their names and resolves pins without provider-specific duration guesses.
---
+58 -13
View File
@@ -15,6 +15,8 @@ import {
} from "../support/helpers/usage-reports";
import {
installLoginUsage,
installCodexWindowUsage,
expectCodexReportedWindows,
openUsage,
refreshLoginUsage,
hoverUsageWindow,
@@ -312,29 +314,26 @@ test("expired login refreshes to windows with visible pin toggles", async ({ pag
).toHaveCount(0);
await expectPinnedUsage(page, ["31% 5h", "54% wk"]);
const row = page.getByRole("checkbox", { name: /^Pin Claude Weekly, / });
await expect(row.getByTestId("usage-pin-glyph-unpinned")).toHaveCSS("opacity", "0");
await expect(row).toBeChecked();
await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "0");
await hoverUsageWindow(page, "Weekly");
await expect(page.getByText("Pin", { exact: true })).toBeVisible();
await expect(row.getByTestId("usage-pin-glyph-unpinned")).toHaveCSS("opacity", "1");
await expect(row.getByTestId("usage-pin-glyph-unpinned").locator("svg")).toHaveAttribute(
"fill",
"none",
);
await qaScreenshot(page, "usage-pin-hover");
await togglePin(page.getByTestId("usage-report-login-journey:account"), "Claude", "Weekly");
await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "1");
await expect(row.getByTestId("usage-pin-glyph-pinned").locator("svg")).not.toHaveAttribute(
"fill",
"none",
);
await expectPinnedUsage(page, ["54% wk"]);
await qaScreenshot(page, "usage-pin-selected");
await qaScreenshot(page, "usage-pin-hover");
await togglePin(page.getByTestId("usage-report-login-journey:account"), "Claude", "Weekly");
await expect(row.getByTestId("usage-pin-glyph-unpinned")).toHaveCSS("opacity", "1");
await expect(row.getByTestId("usage-pin-glyph-unpinned").locator("svg")).toHaveAttribute(
"fill",
"none",
);
await expectPinnedUsage(page, ["31% 5h"]);
await qaScreenshot(page, "usage-pin-unselected");
await togglePin(page.getByTestId("usage-report-login-journey:account"), "Claude", "Weekly");
await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "1");
await expectPinnedUsage(page, ["31% 5h", "54% wk"]);
} finally {
await fixture.cleanup();
@@ -348,11 +347,57 @@ test("compact usage rows always show the pin glyph", async ({ page }) => {
await gotoAppShell(page);
await openUsage(page);
const card = page.getByTestId("usage-report-login-journey:account");
await expect(card.getByTestId("usage-pin-glyph-unpinned")).toHaveCount(2);
await expect(card.getByTestId("usage-pin-glyph-unpinned").nth(0)).toHaveCSS("opacity", "1");
await expect(card.getByTestId("usage-pin-glyph-unpinned").nth(1)).toHaveCSS("opacity", "1");
await expect(card.getByTestId("usage-pin-glyph-pinned")).toHaveCount(2);
await expect(card.getByTestId("usage-pin-glyph-pinned").nth(0)).toHaveCSS("opacity", "1");
await expect(card.getByTestId("usage-pin-glyph-pinned").nth(1)).toHaveCSS("opacity", "1");
await qaScreenshot(page, "usage-pin-compact");
} finally {
await fixture.cleanup();
}
});
for (const shape of ["seven-day-only", "Spark"] as const) {
test(`Codex ${shape} windows use provider durations on the card and sidebar`, async ({
page,
}) => {
const reset = Math.floor(Date.now() / 1000) + 5 * 86400;
const source = await installCodexWindowUsage({
rate_limit: {
primary_window: { used_percent: 11, limit_window_seconds: 604800, reset_at: reset },
secondary_window: null,
},
additional_rate_limits:
shape === "Spark"
? [
{
limit_name: "GPT-5.3-Codex-Spark",
metered_feature: "codex_bengalfox",
rate_limit: {
primary_window: {
used_percent: 0,
limit_window_seconds: 18000,
reset_at: Math.floor(Date.now() / 1000) + 3600,
},
secondary_window: {
used_percent: 0,
limit_window_seconds: 604800,
reset_at: reset,
},
},
},
]
: [],
});
try {
await page.setViewportSize({ width: 1440, height: 900 });
await gotoAppShell(page);
await expect(usageItem(page)).toBeVisible();
await usageItem(page).click();
await qaScreenshot(page, `codex-${shape}-card-and-summary`);
await expectCodexReportedWindows(page, shape);
await qaScreenshot(page, `codex-${shape}-pins`);
} finally {
await source.cleanup();
}
});
}
@@ -114,6 +114,11 @@ test.describe("Usage item", () => {
await gotoAppShell(page);
await usageItem(page).click();
await expectOnUsageScreen(page);
await qaScreenshot(page, "default-pins-card");
await expect(pinRow(screen, "Claude", "Session")).toBeChecked();
await expect(pinRow(screen, "Claude", "Weekly")).toBeChecked();
await expect(pinRow(screen, "Codex", "Session")).toBeChecked();
await expect(pinRow(screen, "Codex", "Weekly")).toBeChecked();
});
await test.step("pinning Claude 5-hour and Codex weekly shows both in the Usage item", async () => {
@@ -122,9 +127,13 @@ test.describe("Usage item", () => {
await expect(page.locator('[data-testid="usage-host-filter-trigger"]:visible')).toHaveCount(
0,
);
await togglePin(screen, "Claude", "Session");
await expectPinnedUsage(page, ["31% 5h"]);
await togglePin(screen, "Codex", "Weekly");
await togglePin(screen, "Claude", "Weekly");
await expectPinnedUsage(page, ["31% 5h", "7% 5h", "12% wk"]);
await expect(pinRow(screen, "Claude", "Session")).toBeChecked();
await expect(pinRow(screen, "Codex", "Session")).toBeChecked();
await expect(pinRow(screen, "Codex", "Weekly")).toBeChecked();
await qaScreenshot(page, "default-pins-after-one-toggle");
await togglePin(screen, "Codex", "Session");
await expectPinnedUsage(page, ["31% 5h", "12% wk"]);
await expect(usageItem(page)).not.toHaveText("Usage");
await qaScreenshot(page, "desktop-footer-pins", { kind: "footer" });
@@ -226,16 +235,18 @@ test.describe("Usage item", () => {
await expect(screen.getByText("Claude", { exact: true })).toBeVisible({ timeout: 10_000 });
});
await test.step("unpinning both brings back default windows, and that survives a reload", async () => {
await test.step("unpinning everything stays empty after a reload", async () => {
await togglePin(screen, "Claude", "Session");
await togglePin(screen, "Codex", "Weekly");
await expectPinnedUsage(page, ["69% 5h", "46% wk", "93% 5h", "88% wk"]);
await expectNoUsageItem(page);
await page.reload();
await expect(screen.getByText("88% left")).toBeVisible({ timeout: 10_000 });
await expectPinnedUsage(page, ["69% 5h", "46% wk", "93% 5h", "88% wk"]);
await gotoAppShell(page);
await usageItem(page).click();
await expectOnUsageScreen(page);
await expectNoUsageItem(page);
await expect(pinRow(screen, "Claude", "Session")).not.toBeChecked();
await expect(pinRow(screen, "Claude", "Weekly")).not.toBeChecked();
await expect(pinRow(screen, "Codex", "Session")).not.toBeChecked();
await expect(pinRow(screen, "Codex", "Weekly")).not.toBeChecked();
await qaScreenshot(page, "explicit-empty-pins-after-reload");
});
await test.step("Settings > Sidebar lists the Usage item", async () => {
@@ -1,8 +1,9 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { cp, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import type { UsageReport } from "@getpaseo/protocol/messages";
import type { Page } from "@playwright/test";
import { expect, type Page } from "@playwright/test";
import { expectPinnedUsage, pinRow, usageItem } from "./usage-sidebar-item";
import { connectNewWorkspaceDaemonClient } from "./new-workspace";
import { pluginRequirements } from "./plugin-fixture";
@@ -61,6 +62,73 @@ export async function refreshLoginUsage(page: Page) {
export async function hoverUsageWindow(page: Page, window: string) {
await page
.getByRole("checkbox", { name: new RegExp(`^Pin Claude ${window}, `) })
.getByTestId("usage-pin-glyph-unpinned")
.getByTestId(/usage-pin-glyph-/)
.hover();
}
/** Exercises the built-in Codex adapter through the real plugin loader and daemon transport. */
export async function installCodexWindowUsage(payload: unknown) {
const directory = await mkdtemp(path.join(tmpdir(), "usage-codex-windows-"));
const client = await connectNewWorkspaceDaemonClient({ ownProjects: false });
const previous = await client.getDaemonConfig();
const cleanup = async () => {
try {
await client.removePlugin("codex-window-qa");
await client.patchDaemonConfig({ pluginsEnabled: previous.config.pluginsEnabled ?? false });
} finally {
await client.close();
await rm(directory, { recursive: true, force: true });
}
};
try {
const source = path.resolve(__dirname, "../../../../../plugins/codex-usage-source");
await cp(path.join(source, "server"), path.join(directory, "server"), { recursive: true });
await cp(path.join(source, "shared"), path.join(directory, "shared"), { recursive: true });
await writeFile(
path.join(directory, "auth.json"),
JSON.stringify({ tokens: { access_token: "fixture-codex" } }),
);
await writeFile(path.join(directory, "payload.json"), JSON.stringify(payload));
await writeFile(
path.join(directory, "paseo-plugin.json"),
JSON.stringify({ id: "codex-window-qa", requirements: pluginRequirements }),
);
await writeFile(
path.join(directory, "index.server.ts"),
`
import {readFile} from "node:fs/promises";
import {fetchUsage, discover} from "./server/usage.js";
import {inputSchema} from "./shared/input.js";
export default function contribute(server) {
server.registerUsageSource({id: "codex-window-qa", label: "Codex", input: inputSchema,
discover: () => discover({home: ${JSON.stringify(directory)}, env: {CODEX_HOME: ${JSON.stringify(directory)}}}),
fetch: (input) => fetchUsage(input, async () => Response.json(JSON.parse(await readFile(${JSON.stringify(path.join(directory, "payload.json"))}, "utf8")))),
});
return () => {};
}`,
);
await client.patchDaemonConfig({ pluginsEnabled: true });
await client.installPluginSource({ source: directory });
return { cleanup };
} catch (error) {
await cleanup();
throw error;
}
}
export async function expectCodexReportedWindows(page: Page, shape: "seven-day-only" | "Spark") {
await expectPinnedUsage(page, ["11% wk"]);
await expect(pinRow(page.locator("body"), "Codex", "Weekly")).toBeChecked();
await expect(page.getByText("Session", { exact: true })).toHaveCount(0);
if (shape === "Spark") {
const fiveHour = pinRow(page.locator("body"), "Codex", "GPT-5.3-Codex-Spark · 5-hour");
const weekly = pinRow(page.locator("body"), "Codex", "GPT-5.3-Codex-Spark · Weekly");
await expect(fiveHour).not.toBeChecked();
await expect(weekly).not.toBeChecked();
await fiveHour.click();
await expect(fiveHour).toBeChecked();
await expect(usageItem(page)).toHaveAccessibleName(
/Codex Weekly 11% used, Codex GPT-5.3-Codex-Spark · 5-hour 0% used/,
);
}
}
@@ -263,10 +263,10 @@ describe("loadAppSettingsFromStorage", () => {
]);
});
it("loads usage preferences with pins in order and defaults a fresh device to none", async () => {
it("loads legacy usage pins in order and defaults a fresh device to source defaults", async () => {
expect((await loadAppSettingsFromStorage(makeDeps())).usage).toEqual({
displayAs: "used",
pinned: [],
pins: null,
serverId: null,
});
@@ -287,7 +287,7 @@ describe("loadAppSettingsFromStorage", () => {
expect((await loadAppSettingsFromStorage(deps)).usage).toEqual({
displayAs: "remaining",
pinned: [
pins: [
{ sourceId: "codex", windowId: "weekly" },
{ sourceId: "claude", windowId: "five-hour" },
],
@@ -295,6 +295,21 @@ describe("loadAppSettingsFromStorage", () => {
});
});
it("loads an explicitly empty usage selection without restoring defaults", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
[APP_SETTINGS_KEY]: JSON.stringify({
usage: { displayAs: "remaining", pins: [], serverId: "server" },
}),
}),
});
expect((await loadAppSettingsFromStorage(deps)).usage).toEqual({
displayAs: "remaining",
pins: [],
serverId: "server",
});
});
it("keeps valid usage preferences when one field is malformed", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
@@ -310,7 +325,7 @@ describe("loadAppSettingsFromStorage", () => {
expect((await loadAppSettingsFromStorage(deps)).usage).toEqual({
displayAs: "used",
pinned: [{ sourceId: "claude", windowId: "weekly" }],
pins: [{ sourceId: "claude", windowId: "weekly" }],
serverId: null,
});
});
+14 -5
View File
@@ -1,5 +1,7 @@
import { useCallback, useMemo } from "react";
import { useAppSettings } from "@/hooks/use-settings";
import { useUsageHostId } from "./hosts";
import { useUsageHostReports } from "./queries";
import {
isUsagePinned,
setUsageDisplayAs,
@@ -18,9 +20,14 @@ export interface UsageDisplay {
}
/** The device's usage preferences, and the commands that change them. */
export function useUsagePreferences(): { preferences: UsagePreferences; display: UsageDisplay } {
export function useUsagePreferences(serverId?: string): {
preferences: UsagePreferences;
display: UsageDisplay;
} {
const { settings, updateSettings } = useAppSettings();
const preferences = settings.usage;
const defaultServerId = useUsageHostId();
const reports = useUsageHostReports(serverId ?? defaultServerId);
const setDisplayAs = useCallback(
(displayAs: UsageDisplayAs) => {
void updateSettings((current) => ({ usage: setUsageDisplayAs(current.usage, displayAs) }));
@@ -29,18 +36,20 @@ export function useUsagePreferences(): { preferences: UsagePreferences; display:
);
const togglePin = useCallback(
(pin: UsagePin) => {
void updateSettings((current) => ({ usage: toggleUsagePin(current.usage, pin) }));
void updateSettings((current) => ({
usage: toggleUsagePin({ preferences: current.usage, pin, reports }),
}));
},
[updateSettings],
[updateSettings, reports],
);
const display = useMemo<UsageDisplay>(
() => ({
displayAs: preferences.displayAs,
setDisplayAs,
isPinned: (pin) => isUsagePinned(preferences, pin),
isPinned: (pin) => isUsagePinned(preferences, pin, reports),
togglePin,
}),
[preferences, setDisplayAs, togglePin],
[preferences, reports, setDisplayAs, togglePin],
);
return { preferences, display };
}
@@ -6,7 +6,7 @@ import { UsageSection } from "./usage-section";
/** A host's usage reports, for its settings page. */
export function HostUsageSection({ serverId }: { serverId: string }) {
const { view, refresh } = useHostUsage(serverId);
const { display } = useUsagePreferences();
const { display } = useUsagePreferences(serverId);
return (
<UsageSection
serverId={serverId}
+9 -8
View File
@@ -46,10 +46,10 @@ function windows(reports: UsageReportEntry[], prefs: UsagePreferences): PinnedUs
}
function preferences(
pinned: UsagePreferences["pinned"],
pinned: UsagePreferences["pins"],
displayAs: UsagePreferences["displayAs"] = "used",
): UsagePreferences {
return { displayAs, pinned, serverId: null };
return { displayAs, pins: pinned, serverId: null };
}
describe("choosePinnedUsageLayout", () => {
@@ -73,7 +73,7 @@ describe("choosePinnedUsageLayout", () => {
});
describe("resolvePinnedUsage", () => {
it("defaults to the first window with a percent for each source account", () => {
it("defaults to source-wide pins collected from each account", () => {
const work = report({
sourceId: "claude",
sourceLabel: "Claude",
@@ -89,12 +89,13 @@ describe("resolvePinnedUsage", () => {
windows: [{ id: "empty", label: "Empty" }],
});
expect(
windows([claude, codex, work, empty], preferences([])).map((item) => [
windows([claude, codex, work, empty], preferences(null)).map((item) => [
item.key,
item.percentText,
]),
).toEqual([
["claude:default/five-hour", "31%"],
["claude:default/weekly", "80%"],
["claude:work/weekly", "90%"],
["codex:default/weekly", "12%"],
]);
@@ -107,7 +108,7 @@ describe("resolvePinnedUsage", () => {
(item) => item.key,
),
).toEqual(["claude:default/weekly"]);
expect(windows(reports, preferences([])).map((item) => item.key)).toEqual([
expect(windows(reports, preferences(null)).map((item) => item.key)).toEqual([
"claude:default/five-hour",
"codex:default/weekly",
]);
@@ -176,7 +177,7 @@ describe("resolvePinnedUsage", () => {
],
});
expect(windows([marked, codex], preferences([])).map((item) => item.key)).toEqual([
expect(windows([marked, codex], preferences(null)).map((item) => item.key)).toEqual([
"claude:default/five-hour",
"claude:default/weekly",
"codex:default/weekly",
@@ -189,7 +190,7 @@ describe("resolvePinnedUsage", () => {
sourceLabel: "OpenCode Go",
windows: [{ id: "rolling", label: "Rolling", shortLabel: "", usedPct: 21 }],
});
const items = windows([opencode], preferences([]));
const items = windows([opencode], preferences(null));
expect(items.map((item) => item.shortLabel)).toEqual([""]);
});
@@ -268,5 +269,5 @@ it("keeps unavailable and error reports out of the sidebar summary", () => {
...codex,
report: { status: "error", error: "Store deleted" },
};
expect(resolvePinnedUsage([expired, failed], preferences([]))).toEqual([]);
expect(resolvePinnedUsage([expired, failed], preferences(null))).toEqual([]);
});
+22 -32
View File
@@ -1,8 +1,8 @@
import { formatPct } from "./format";
import { displayPercent } from "./model";
import type { UsagePreferences } from "./preferences";
import { effectiveUsagePins, type UsagePreferences } from "./preferences";
import { windowTone } from "./tone";
import type { UsageReportEntry, UsageTone, UsageWindow } from "./types";
import type { UsageReportEntry, UsageTone } from "./types";
/** One summary window of one account, as the sidebar Usage item shows it. */
export interface PinnedUsageWindow {
@@ -61,24 +61,6 @@ function groupBySource(reports: readonly UsageReportEntry[]): UsageReportEntry[]
return sourceIds.flatMap((sourceId) => reports.filter((entry) => entry.sourceId === sourceId));
}
/**
* Pins replace defaults. Without pins, an account shows the windows its source marks as summary,
* or its first window with a percent when the source marks none.
*/
function summaryWindows(entry: UsageReportEntry, preferences: UsagePreferences): UsageWindow[] {
if (entry.report.status !== "available") return [];
const withPercent = entry.report.windows.filter(
(window) => displayPercent(window, preferences.displayAs) !== null,
);
if (preferences.pinned.length === 0) {
const marked = withPercent.filter((window) => window.summary);
return marked.length > 0 ? marked : withPercent.slice(0, 1);
}
return withPercent.filter((window) =>
preferences.pinned.some((pin) => pin.sourceId === entry.sourceId && pin.windowId === window.id),
);
}
/**
* The sidebar Usage item's summary: one group per account, grouped by source, each with its
* windows in its report's order. Accounts with nothing to show are left out.
@@ -87,20 +69,28 @@ export function resolvePinnedUsage(
reports: readonly UsageReportEntry[],
preferences: UsagePreferences,
): PinnedUsageSource[] {
const pins = effectiveUsagePins(preferences, reports);
const meaning = preferences.displayAs === "remaining" ? "left" : "used";
return groupBySource(reports).flatMap((entry) => {
const windows = summaryWindows(entry, preferences).map((window) => {
const percent = displayPercent(window, preferences.displayAs) ?? 0;
const percentText = formatPct(percent);
return {
key: `${entry.id}/${window.id}`,
label: `${describe(entry, window.label)} ${percentText} ${meaning}`,
shortLabel: window.shortLabel ?? window.label,
percent,
percentText,
tone: windowTone(window),
};
});
if (entry.report.status !== "available") return [];
const windows = entry.report.windows
.filter(
(window) =>
displayPercent(window, preferences.displayAs) !== null &&
pins.some((pin) => pin.sourceId === entry.sourceId && pin.windowId === window.id),
)
.map((window) => {
const percent = displayPercent(window, preferences.displayAs) ?? 0;
const percentText = formatPct(percent);
return {
key: `${entry.id}/${window.id}`,
label: `${describe(entry, window.label)} ${percentText} ${meaning}`,
shortLabel: window.shortLabel ?? window.label,
percent,
percentText,
tone: windowTone(window),
};
});
if (windows.length === 0) return [];
return [{ key: entry.id, icon: entry.icon ?? null, windows }];
});
+91 -18
View File
@@ -13,45 +13,118 @@ const codexWeekly = { sourceId: "codex", windowId: "weekly" };
describe("usage preferences", () => {
it("pins windows in the order the user pins them", () => {
const pinned = toggleUsagePin(
toggleUsagePin(DEFAULT_USAGE_PREFERENCES, codexWeekly),
claudeFiveHour,
);
const pinned = toggleUsagePin({
preferences: toggleUsagePin({
preferences: DEFAULT_USAGE_PREFERENCES,
pin: codexWeekly,
reports: [],
}),
pin: claudeFiveHour,
reports: [],
});
expect(pinned.pinned).toEqual([codexWeekly, claudeFiveHour]);
expect(isUsagePinned(pinned, { sourceId: "codex", windowId: "weekly" })).toBe(true);
expect(isUsagePinned(pinned, { sourceId: "codex", windowId: "five-hour" })).toBe(false);
expect(pinned.pins).toEqual([codexWeekly, claudeFiveHour]);
expect(isUsagePinned(pinned, { sourceId: "codex", windowId: "weekly" }, [])).toBe(true);
expect(isUsagePinned(pinned, { sourceId: "codex", windowId: "five-hour" }, [])).toBe(false);
});
it("unpins a pinned window and keeps the others in order", () => {
const three = [codexWeekly, claudeFiveHour, { sourceId: "claude", windowId: "weekly" }];
const unpinned = toggleUsagePin(
{ displayAs: "used", pinned: three, serverId: null },
claudeFiveHour,
);
const unpinned = toggleUsagePin({
preferences: { displayAs: "used", pins: three, serverId: null },
pin: claudeFiveHour,
reports: [],
});
expect(unpinned.pinned).toEqual([codexWeekly, { sourceId: "claude", windowId: "weekly" }]);
expect(unpinned.pins).toEqual([codexWeekly, { sourceId: "claude", windowId: "weekly" }]);
});
it("switches between used and remaining without touching pins", () => {
const preferences = { displayAs: "used" as const, pinned: [codexWeekly], serverId: null };
const preferences = { displayAs: "used" as const, pins: [codexWeekly], serverId: null };
expect(setUsageDisplayAs(preferences, "remaining")).toEqual({
displayAs: "remaining",
pinned: [codexWeekly],
pins: [codexWeekly],
serverId: null,
});
});
it("remembers the picked host without touching pins", () => {
expect(setUsageHost({ ...DEFAULT_USAGE_PREFERENCES, pinned: [codexWeekly] }, "server")).toEqual(
{ displayAs: "used", pinned: [codexWeekly], serverId: "server" },
);
expect(setUsageHost({ ...DEFAULT_USAGE_PREFERENCES, pins: [codexWeekly] }, "server")).toEqual({
displayAs: "used",
pins: [codexWeekly],
serverId: "server",
});
});
it("reads preferences saved before hosts could be picked as no pick", () => {
expect(UsagePreferencesSchema.parse({ displayAs: "remaining", pinned: [codexWeekly] })).toEqual(
{ displayAs: "remaining", pinned: [codexWeekly], serverId: null },
{ displayAs: "remaining", pins: [codexWeekly], serverId: null },
);
});
});
it("default sidebar windows are pinned on cards and the first toggle removes only that row", () => {
const reports = [
{
id: "claude:default",
sourceId: "claude",
sourceLabel: "Claude",
account: {},
fetchedAt: "2026-10-02T00:00:00Z",
report: {
status: "available" as const,
windows: [
{ id: "five_hour", label: "5-hour", summary: true, usedPct: 31 },
{ id: "weekly", label: "Weekly", summary: true, usedPct: 54 },
],
},
},
{
id: "codex:default",
sourceId: "codex",
sourceLabel: "Codex",
account: {},
fetchedAt: "2026-10-02T00:00:00Z",
report: {
status: "available" as const,
windows: [{ id: "five_hour", label: "5-hour", summary: true, usedPct: 7 }],
},
},
];
const pins = [
{ sourceId: "claude", windowId: "five_hour" },
{ sourceId: "claude", windowId: "weekly" },
{ sourceId: "codex", windowId: "five_hour" },
];
for (const pin of pins) expect(isUsagePinned(DEFAULT_USAGE_PREFERENCES, pin, reports)).toBe(true);
const changed = toggleUsagePin({
preferences: DEFAULT_USAGE_PREFERENCES,
pin: pins[1]!,
reports,
});
expect(isUsagePinned(changed, pins[1]!, reports)).toBe(false);
expect(isUsagePinned(changed, pins[0]!, reports)).toBe(true);
expect(isUsagePinned(changed, pins[2]!, reports)).toBe(true);
const empty = toggleUsagePin({
preferences: toggleUsagePin({ preferences: changed, pin: pins[0]!, reports }),
pin: pins[2]!,
reports,
});
const restored = UsagePreferencesSchema.parse(JSON.parse(JSON.stringify(empty)));
for (const pin of pins) expect(isUsagePinned(restored, pin, reports)).toBe(false);
});
it("reads old selections without losing display or host and preserves an explicit empty selection", () => {
for (const [input, pins] of [
[{ pinned: [] }, null],
[{ pinned: [codexWeekly] }, [codexWeekly]],
[{ pins: [], pinned: [codexWeekly] }, []],
[{ pins: null, pinned: [codexWeekly] }, null],
[{ pins: "bad", pinned: [codexWeekly] }, null],
] as const) {
expect(
UsagePreferencesSchema.parse({ displayAs: "remaining", serverId: "host", ...input }),
).toEqual({ displayAs: "remaining", serverId: "host", pins });
}
});
+58 -12
View File
@@ -1,4 +1,6 @@
import { z } from "zod";
import { displayPercent } from "./model";
import type { UsageReportEntry } from "./types";
/** Whether percentages read as the share used or the share left. */
export type UsageDisplayAs = "used" | "remaining";
@@ -9,42 +11,86 @@ export interface UsagePin {
windowId: string;
}
/** Device-local. `pinned` is in display order. */
/** Device-local. `pins` is in selection order. */
export interface UsagePreferences {
displayAs: UsageDisplayAs;
pinned: UsagePin[];
/** null follows source defaults; an array is the user's selection, including none. */
pins: UsagePin[] | null;
/** The host the user picked to show usage for; null until they pick one. */
serverId: string | null;
}
export const DEFAULT_USAGE_PREFERENCES: UsagePreferences = {
displayAs: "used",
pinned: [],
pins: null,
serverId: null,
};
const pinsSchema = z.array(z.object({ sourceId: z.string(), windowId: z.string() }));
export const UsagePreferencesSchema = z
.object({
displayAs: z.enum(["used", "remaining"]).catch("used"),
pinned: z.array(z.object({ sourceId: z.string(), windowId: z.string() })).catch([]),
pins: pinsSchema.nullable().optional().catch(null),
// COMPAT(usagePinSelection): added in v0.11, remove after 2027-04-02.
// Old [] meant defaults. Read it only when the new selection is absent.
pinned: pinsSchema.optional().catch([]),
serverId: z.string().nullable().catch(null),
})
.transform(({ displayAs, pins, pinned, serverId }): UsagePreferences => {
const legacyPins = pinned && pinned.length > 0 ? pinned : null;
return { displayAs, pins: pins === undefined ? legacyPins : pins, serverId };
})
.catch(DEFAULT_USAGE_PREFERENCES);
function samePin(a: UsagePin, b: UsagePin): boolean {
return a.sourceId === b.sourceId && a.windowId === b.windowId;
}
export function isUsagePinned(preferences: UsagePreferences, pin: UsagePin): boolean {
return preferences.pinned.some((pinned) => samePin(pinned, pin));
/** The only owner of default and customized pin selection, shared by every usage surface. */
export function effectiveUsagePins(
preferences: UsagePreferences,
reports: readonly UsageReportEntry[],
): UsagePin[] {
if (preferences.pins !== null) return preferences.pins;
const pins: UsagePin[] = [];
for (const entry of reports) {
if (entry.report.status !== "available") continue;
const withPercent = entry.report.windows.filter(
(window) => displayPercent(window, preferences.displayAs) !== null,
);
const marked = withPercent.filter((window) => window.summary);
for (const window of marked.length > 0 ? marked : withPercent.slice(0, 1)) {
const pin = { sourceId: entry.sourceId, windowId: window.id };
if (!pins.some((existing) => samePin(existing, pin))) pins.push(pin);
}
}
return pins;
}
/** Unpins a pinned window; pins any other at the end, so the Usage item grows to the right. */
export function toggleUsagePin(preferences: UsagePreferences, pin: UsagePin): UsagePreferences {
const pinned = isUsagePinned(preferences, pin)
? preferences.pinned.filter((existing) => !samePin(existing, pin))
: [...preferences.pinned, { sourceId: pin.sourceId, windowId: pin.windowId }];
return { ...preferences, pinned };
export function isUsagePinned(
preferences: UsagePreferences,
pin: UsagePin,
reports: readonly UsageReportEntry[],
): boolean {
return effectiveUsagePins(preferences, reports).some((pinned) => samePin(pinned, pin));
}
/** The first edit snapshots the displayed defaults before toggling one window. */
export function toggleUsagePin({
preferences,
pin,
reports,
}: {
preferences: UsagePreferences;
pin: UsagePin;
reports: readonly UsageReportEntry[];
}): UsagePreferences {
const selected = effectiveUsagePins(preferences, reports);
const pins = selected.some((existing) => samePin(existing, pin))
? selected.filter((existing) => !samePin(existing, pin))
: [...selected, { sourceId: pin.sourceId, windowId: pin.windowId }];
return { ...preferences, pins };
}
export function setUsageDisplayAs(
+21
View File
@@ -4,6 +4,7 @@ import {
toneFromUsedPct,
usedPctOf,
windowFromUsedPct,
windowFromReportedDuration,
} from "./usage.js";
test("shared usage tones preserve the app threshold contract", () => {
@@ -84,3 +85,23 @@ describe("balanceToneFromRemaining", () => {
expect(balanceToneFromRemaining(null)).toBe("default");
});
});
test.each([
[18000, "five_hour", "5-hour", "5h"],
[604800, "weekly", "Weekly", "wk"],
[7200, "7200s", "2-hour", "2h"],
[86400, "86400s", "1-day", "1d"],
[90, "90s", "90-second", "90s"],
[null, "primary", "Primary limit", ""],
])(
"duration %s owns the window's identity and both names",
(durationSeconds, id, label, shortLabel) => {
expect(
windowFromReportedDuration({
durationSeconds: durationSeconds as number | null,
unknown: { id: "primary", label: "Primary limit", shortLabel: "" },
utilizationPct: 11,
}),
).toMatchObject({ id, label, shortLabel, usedPct: 11 });
},
);
+49
View File
@@ -96,6 +96,55 @@ export function windowFromUsedPct(input: {
return window;
}
/**
* Numeric provider windows have one identity and vocabulary, independent of response slots.
* Pass null when the provider omits the duration; reset countdowns are not window lengths.
* Named API fields (weekly, monthly, etc.) use windowFromUsedPct instead.
*/
export function windowFromReportedDuration(input: {
durationSeconds: number | null;
/** Stable quota identity and provider name for a model- or feature-scoped limit. */
scope?: { id: string; label: string };
/** Neutral identity and names when the provider does not report a positive duration. */
unknown: { id: string; label: string; shortLabel: string };
utilizationPct: number | null | undefined;
resetsAt?: string | null;
summary?: boolean;
tone?: UsageWindow["tone"];
}): UsageWindow {
const duration = input.durationSeconds;
const name =
duration !== null && Number.isFinite(duration) && duration > 0
? durationWindowName(duration)
: input.unknown;
const scope = input.scope;
return windowFromUsedPct({
id: scope ? `${scope.id}:${name.id}` : name.id,
label: scope ? `${scope.label} · ${name.label}` : name.label,
shortLabel: scope
? `${scope.label}${name.shortLabel ? ` ${name.shortLabel}` : ""}`
: name.shortLabel,
utilizationPct: input.utilizationPct,
resetsAt: input.resetsAt,
summary: input.summary,
tone: input.tone,
});
}
function durationWindowName(seconds: number): { id: string; label: string; shortLabel: string } {
if (seconds === 604800) return { id: "weekly", label: "Weekly", shortLabel: "wk" };
const id = seconds === 18000 ? "five_hour" : `${seconds}s`;
const units = [
[86400, "day", "d"],
[3600, "hour", "h"],
[60, "minute", "m"],
[1, "second", "s"],
] as const;
const unit = units.find(([size]) => seconds % size === 0) ?? units[units.length - 1]!;
const amount = seconds / unit[0];
return { id, label: `${amount}-${unit[1]}`, shortLabel: `${amount}${unit[2]}` };
}
/**
* The tone scale for anything measured against a known limit, windows and balances alike.
*
@@ -1,5 +1,6 @@
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { z } from "zod";
import { windowFromReportedDuration } from "@getpaseo/plugin/server/usage";
let fetches = 0;
export default function contribute(server: PluginServerContext) {
@@ -32,7 +33,13 @@ export default function contribute(server: PluginServerContext) {
fetches++;
return {
status: "available",
windows: [{ id: "count", label: "Count", usedPct: fetches }],
windows: [
windowFromReportedDuration({
durationSeconds: 604800,
unknown: { id: "unknown", label: "Limit", shortLabel: "" },
utilizationPct: fetches,
}),
],
};
},
});
@@ -1,5 +1,6 @@
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { z } from "zod";
import { windowFromReportedDuration } from "@getpaseo/plugin/server/usage";
let fetches = 0;
export default function contribute(server: PluginServerContext) {
@@ -32,7 +33,13 @@ export default function contribute(server: PluginServerContext) {
fetches++;
return {
status: "available",
windows: [{ id: "count", label: "Count", usedPct: fetches }],
windows: [
windowFromReportedDuration({
durationSeconds: 604800,
unknown: { id: "unknown", label: "Limit", shortLabel: "" },
utilizationPct: fetches,
}),
],
};
},
});
@@ -57,6 +57,13 @@ test("lists built-in and subprocess usage; validates input and isolates fetch er
await client.installDirectoryPlugin(subprocessDirectory, "fixture-directory");
const both = await client.listUsageReports({ forceRefresh: true });
expect(both.reports).toHaveLength(8);
for (const id of ["fixture:one", "fixture-directory:one"]) {
expect(both.reports.find((entry) => entry.id === id)?.report.windows[0]).toMatchObject({
id: "weekly",
label: "Weekly",
shortLabel: "wk",
});
}
await client.patchDaemonConfig({ pluginsEnabled: false });
await expect.poll(async () => (await client.listUsageReports()).reports.length).toBe(4);
} finally {
+1 -1
View File
@@ -1 +1 @@
{ "id": "codex-usage-source", "requirements": { "paseo": ">=0.9.2" } }
{ "id": "codex-usage-source", "requirements": { "paseo": ">=0.11.0" } }
+128 -9
View File
@@ -39,7 +39,9 @@ function response(headers: HeadersInit, accountId?: string): Promise<Response> {
new Response(
JSON.stringify({
plan_type: "plus",
rate_limit: { primary_window: { used_percent: 30, reset_at: 1700000000 } },
rate_limit: {
primary_window: { used_percent: 30, limit_window_seconds: 18000, reset_at: 1700000000 },
},
}),
{ status: 200 },
),
@@ -62,7 +64,7 @@ test("explicit route reads Codex auth and preserves the usage request", async ()
expect(report).toMatchObject({
status: "available",
planLabel: "plus",
windows: [{ id: "session", usedPct: 30 }],
windows: [{ id: "five_hour", usedPct: 30 }],
});
} finally {
await rm(home, { recursive: true, force: true });
@@ -95,8 +97,8 @@ test("coerces credit balance and marks a 96 percent window dangerous", async ()
new Response(
JSON.stringify({
rate_limit: {
primary_window: { used_percent: 12 },
secondary_window: { used_percent: 96 },
primary_window: { used_percent: 12, limit_window_seconds: 18000 },
secondary_window: { used_percent: 96, limit_window_seconds: 604800 },
},
credits: { balance: "0" },
}),
@@ -105,7 +107,7 @@ test("coerces credit balance and marks a 96 percent window dangerous", async ()
);
expect(report.windows).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: "session", tone: "ok" }),
expect.objectContaining({ id: "five_hour", tone: "ok" }),
expect.objectContaining({ id: "weekly", tone: "danger" }),
]),
);
@@ -121,8 +123,8 @@ test("summarizes the session and weekly windows by default, not code review", as
new Response(
JSON.stringify({
rate_limit: {
primary_window: { used_percent: 12 },
secondary_window: { used_percent: 40 },
primary_window: { used_percent: 12, limit_window_seconds: 18000 },
secondary_window: { used_percent: 40, limit_window_seconds: 604800 },
},
code_review_rate_limit: { primary_window: { used_percent: 5 } },
}),
@@ -130,9 +132,9 @@ test("summarizes the session and weekly windows by default, not code review", as
),
);
expect(report.windows.map((window) => [window.id, window.summary ?? false])).toEqual([
["session", true],
["five_hour", true],
["weekly", true],
["code_review", false],
["code_review:unknown_primary", false],
]);
});
@@ -260,3 +262,120 @@ test.each([401, 403])("reports a Codex login rejected with HTTP %i", async (stat
await fetchUsage(authInput(fixtureHome), async () => new Response(null, { status })),
).toEqual({ status: "unavailable", problem: { kind: "rejected", status, refreshedBy: "codex" } });
});
// Issue #4165: the primary slot contains the only weekly window.
test("names a seven-day-only primary window from its reported duration", async () => {
const report = await fetchUsage(authInput(fixtureHome), async () =>
Response.json({
rate_limit: {
primary_window: { used_percent: 1, limit_window_seconds: 604800 },
secondary_window: null,
},
}),
);
expect(report).toMatchObject({
status: "available",
windows: [{ id: "weekly", label: "Weekly", shortLabel: "wk", usedPct: 1 }],
});
});
test("reports every named additional limit with its own duration identity", async () => {
const report = await fetchUsage(authInput(fixtureHome), async () =>
Response.json({
additional_rate_limits: [
{
limit_name: "GPT-5.3-Codex-Spark",
metered_feature: "codex_bengalfox",
rate_limit: {
primary_window: { used_percent: 0, limit_window_seconds: 18000 },
secondary_window: { used_percent: 0, limit_window_seconds: 604800 },
},
},
{
limit_name: "Another limit",
metered_feature: "another",
rate_limit: {
primary_window: { used_percent: 4, limit_window_seconds: 7200 },
},
},
],
}),
);
expect(report).toMatchObject({
status: "available",
windows: [
{
id: "limit:codex_bengalfox:five_hour",
label: "GPT-5.3-Codex-Spark · 5-hour",
shortLabel: "GPT-5.3-Codex-Spark 5h",
},
{
id: "limit:codex_bengalfox:weekly",
label: "GPT-5.3-Codex-Spark · Weekly",
shortLabel: "GPT-5.3-Codex-Spark wk",
},
{ id: "limit:another:7200s", label: "Another limit · 2-hour" },
],
});
});
test("a weekly pin means weekly after the provider moves it between slots", async () => {
const { resolvePinnedUsage } = await import("../../../packages/app/src/usage/pinned.js");
const preferences = {
displayAs: "used" as const,
serverId: null,
pins: [{ sourceId: "codex", windowId: "weekly" }],
};
for (const primaryWeekly of [false, true]) {
const weekly = { used_percent: 11, limit_window_seconds: 604800 };
const report = await fetchUsage(authInput(fixtureHome), async () =>
Response.json({
rate_limit: {
primary_window: primaryWeekly ? weekly : { used_percent: 4, limit_window_seconds: 18000 },
secondary_window: primaryWeekly ? null : weekly,
},
}),
);
const entry = {
id: "codex:account",
sourceId: "codex",
sourceLabel: "Codex",
account: { key: "account" },
fetchedAt: "2026-10-02T00:00:00Z",
report,
};
expect(resolvePinnedUsage([entry], preferences)).toMatchObject([
{ windows: [{ key: "codex:account/weekly", percentText: "11%", shortLabel: "wk" }] },
]);
// The old slot ID is ambiguous: do not silently retarget a saved Session pin.
expect(
resolvePinnedUsage([entry], {
...preferences,
pins: [{ sourceId: "codex", windowId: "session" }],
}),
).toEqual([]);
}
});
test("unknown lengths and code review never get a duration from their slot", async () => {
const report = await fetchUsage(authInput(fixtureHome), async () =>
Response.json({
rate_limit: {
primary_window: { used_percent: 11 },
secondary_window: { used_percent: 22, limit_window_seconds: null },
},
code_review_rate_limit: {
primary_window: { used_percent: 3, limit_window_seconds: 604800 },
secondary_window: { used_percent: 4, limit_window_seconds: 18000 },
},
}),
);
expect(report).toMatchObject({
windows: [
{ id: "unknown_primary", label: "Primary limit", shortLabel: "" },
{ id: "unknown_secondary", label: "Secondary limit", shortLabel: "" },
{ id: "code_review:weekly", label: "Code review · Weekly", shortLabel: "Code review wk" },
{ id: "code_review:five_hour", label: "Code review · 5-hour", shortLabel: "Code review 5h" },
],
});
});
+68 -29
View File
@@ -7,7 +7,7 @@ import {
unavailable,
type UsageAccount,
toneFromUsedPct,
windowFromUsedPct,
windowFromReportedDuration,
type UsageReport,
type UsageWindow,
} from "@getpaseo/plugin/server/usage";
@@ -24,14 +24,29 @@ const authSchema = z.object({
.optional(),
});
const number = z.coerce.number().finite();
const windowSchema = z.object({ used_percent: number.optional(), reset_at: number.optional() });
const windowSchema = z.object({
used_percent: number.optional(),
reset_at: number.optional(),
limit_window_seconds: number.nullish(),
});
const rateLimitSchema = z.object({
primary_window: windowSchema.nullish(),
secondary_window: windowSchema.nullish(),
});
const responseSchema = z.object({
plan_type: z.string().optional(),
email: z.string().optional(),
rate_limit: z
.object({ primary_window: windowSchema.nullish(), secondary_window: windowSchema.nullish() })
rate_limit: rateLimitSchema.nullish(),
additional_rate_limits: z
.array(
z.object({
limit_name: z.string().optional(),
metered_feature: z.string().optional(),
rate_limit: rateLimitSchema.nullish(),
}),
)
.nullish(),
code_review_rate_limit: z.object({ primary_window: windowSchema.nullish() }).nullish(),
code_review_rate_limit: rateLimitSchema.nullish(),
credits: z.object({ balance: number.optional() }).nullish(),
});
@@ -90,17 +105,37 @@ export async function readAuth(
};
}
function usageWindow(
spec: { id: string; label: string; shortLabel: string; summary?: boolean },
value: z.infer<typeof windowSchema> | null | undefined,
): UsageWindow | null {
if (!value) return null;
const usedPct = value.used_percent ?? 0;
return windowFromUsedPct({
...spec,
utilizationPct: usedPct,
resetsAt: value.reset_at != null ? new Date(value.reset_at * 1000).toISOString() : null,
tone: toneFromUsedPct(usedPct),
function usageWindows({
rateLimit,
scope,
summary = false,
}: {
rateLimit: z.infer<typeof rateLimitSchema> | null | undefined;
scope?: { id: string; label: string };
summary?: boolean;
}): UsageWindow[] {
if (!rateLimit) return [];
return (["primary_window", "secondary_window"] as const).flatMap((slot) => {
const value = rateLimit[slot];
if (!value) return [];
const primary = slot === "primary_window";
// Codex does not always report a length. A slot is then only an unknown limit,
// never evidence of a five-hour or weekly period.
return [
windowFromReportedDuration({
durationSeconds: value.limit_window_seconds ?? null,
unknown: {
id: primary ? "unknown_primary" : "unknown_secondary",
label: primary ? "Primary limit" : "Secondary limit",
shortLabel: "",
},
scope,
summary,
utilizationPct: value.used_percent,
resetsAt: value.reset_at != null ? new Date(value.reset_at * 1000).toISOString() : null,
tone: toneFromUsedPct(value.used_percent),
}),
];
});
}
@@ -135,19 +170,23 @@ export async function fetchUsage(
if (text.trim().startsWith("<")) throw new Error("Codex usage API returned HTML");
const usage = responseSchema.parse(JSON.parse(text));
const windows = [
usageWindow(
{ id: "session", label: "Session", shortLabel: "5h", summary: true },
usage.rate_limit?.primary_window,
),
usageWindow(
{ id: "weekly", label: "Weekly", shortLabel: "wk", summary: true },
usage.rate_limit?.secondary_window,
),
usageWindow(
{ id: "code_review", label: "Code review", shortLabel: "review" },
usage.code_review_rate_limit?.primary_window,
),
].filter((window): window is UsageWindow => window !== null);
...usageWindows({ rateLimit: usage.rate_limit, summary: true }),
...(usage.additional_rate_limits ?? []).flatMap((limit) => {
const identity = limit.metered_feature || limit.limit_name;
if (!identity) return []; // An unnamed limit has no stable quota identity.
return usageWindows({
rateLimit: limit.rate_limit,
scope: {
id: `limit:${encodeURIComponent(identity)}`,
label: limit.limit_name || identity,
},
});
}),
...usageWindows({
rateLimit: usage.code_review_rate_limit,
scope: { id: "code_review", label: "Code review" },
}),
];
const balance = usage.credits?.balance;
return {
status: "available",
@@ -326,3 +326,46 @@ it.each([401, 403])("reports an existing login rejected with HTTP %i", async (st
else process.env["GROK_TOKEN"] = previous;
}
});
it.each([undefined, "USAGE_PERIOD_TYPE_DAILY", "USAGE_PERIOD_TYPE_UNKNOWN_WEEKLY"])(
"does not invent a monthly period for %s",
async (type) => {
const directory = mkdtempSync(join(tmpdir(), "grok-period-"));
try {
const locator = join(directory, "auth.json");
writeFileSync(locator, JSON.stringify({ access_token: "fixture" }));
const report = await fetchUsage({ store: "file", locator }, async () =>
Response.json({ config: { creditUsagePercent: 11, currentPeriod: { type } } }),
);
expect(report).toMatchObject({
status: "available",
windows: [
{
id: type ? `period:${type}` : "period:unknown",
label: type ?? "Current period",
shortLabel: "",
},
],
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
},
);
it("does not call credits monthly when no monthly limit was reported", async () => {
const directory = mkdtempSync(join(tmpdir(), "grok-credits-"));
try {
const locator = join(directory, "auth.json");
writeFileSync(locator, JSON.stringify({ access_token: "fixture" }));
const report = await fetchUsage({ store: "file", locator }, async () =>
Response.json({ config: { used: { val: 11 } } }),
);
expect(report).toMatchObject({
status: "available",
balances: [{ id: "credits", label: "Credits", used: 11 }],
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
+16 -10
View File
@@ -74,15 +74,14 @@ export function extractGrokTokenFromAuth(auth: unknown): string | null {
return null;
}
function grokMonthlyCreditBalance(
response: z.infer<typeof GrokUsageResponseSchema>,
): UsageBalance | null {
function grokCreditBalance(response: z.infer<typeof GrokUsageResponseSchema>): UsageBalance | null {
const limit = response.config?.monthlyLimit?.val ?? null;
const used = response.config?.used?.val ?? response.usage?.creditUsage ?? null;
if (limit === null && used === null) return null;
return {
id: "monthly_credits",
label: "Monthly credits",
// Only the named monthly allowance establishes a monthly credit bucket.
id: limit === null ? "credits" : "monthly_credits",
label: limit === null ? "Credits" : "Monthly credits",
used,
remaining: limit !== null && used !== null ? Math.max(0, limit - used) : null,
limit,
@@ -95,11 +94,18 @@ function grokUsageWindow(response: z.infer<typeof GrokUsageResponseSchema>): Usa
const percent = response.config?.creditUsagePercent;
if (typeof percent !== "number") return null;
const period = response.config?.currentPeriod;
const weekly = (period?.type ?? "").toUpperCase().includes("WEEKLY");
const names = {
USAGE_PERIOD_TYPE_WEEKLY: { id: "weekly", label: "Weekly", shortLabel: "wk" },
USAGE_PERIOD_TYPE_MONTHLY: { id: "monthly", label: "Monthly", shortLabel: "mo" },
};
// An absent or unfamiliar enum does not establish a monthly duration.
const name = names[period?.type as keyof typeof names] ?? {
id: `period:${period?.type || "unknown"}`,
label: period?.type || "Current period",
shortLabel: "",
};
return windowFromUsedPct({
id: weekly ? "weekly" : "monthly",
label: weekly ? "Weekly" : "Monthly",
shortLabel: weekly ? "wk" : "mo",
...name,
utilizationPct: percent,
resetsAt: period?.end ?? null,
tone: toneFromUsedPct(percent),
@@ -129,7 +135,7 @@ export async function fetchUsage(
if (!res.ok) throw new Error(`Grok usage API returned ${res.status}`);
const resp = GrokUsageResponseSchema.parse(await res.json());
const balance = grokMonthlyCreditBalance(resp);
const balance = grokCreditBalance(resp);
const window = grokUsageWindow(resp);
return {
+39 -1
View File
@@ -279,7 +279,7 @@ describe("Kimi usage source usage windows", () => {
windows: [
{
id: "coding_usage",
label: "Weekly limit",
label: "Usage limit",
usedPct: 61,
remainingPct: 39,
resetsAt: "2026-08-05T00:01:45Z",
@@ -428,3 +428,41 @@ it.each([401, 403])("reports an existing login rejected with HTTP %i", async (st
else process.env["KIMI_TOKEN"] = previous;
}
});
it("does not invent a weekly duration for unnamed top-level usage", async () => {
const directory = mkdtempSync(join(tmpdir(), "kimi-period-"));
try {
const locator = join(directory, "auth.json");
writeFileSync(locator, JSON.stringify({ access_token: "fixture" }));
const report = await fetchUsage({ store: "file", locator }, async () =>
Response.json({ usage: { limit: "100", remaining: "74" } }),
);
expect(report).toMatchObject({
status: "available",
windows: [{ id: "coding_usage", label: "Usage limit" }],
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
it("does not infer a duration from an unfamiliar time-unit name containing HOUR", async () => {
const directory = mkdtempSync(join(tmpdir(), "kimi-unit-"));
try {
const locator = join(directory, "auth.json");
writeFileSync(locator, JSON.stringify({ access_token: "fixture" }));
const report = await fetchUsage({ store: "file", locator }, async () =>
Response.json({
limits: [
{
window: { duration: 2, timeUnit: "TIME_UNIT_NOT_HOUR" },
detail: { limit: "100", used: "11" },
},
],
}),
);
expect(report).toMatchObject({ status: "available", windows: [{ label: "Limit 1" }] });
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
+12 -8
View File
@@ -90,18 +90,18 @@ function durationFrom(value: unknown): number | null {
return Number.isFinite(duration) && duration > 0 ? duration : null;
}
function durationLabel(duration: number, timeUnit: string | undefined): string | null {
if (!timeUnit) return null;
function durationLabel(duration: number | null, timeUnit: string | undefined): string | null {
if (duration === null || !timeUnit) return null;
const normalizedUnit = timeUnit.replace(/^TIME_UNIT_/i, "").toUpperCase();
if (normalizedUnit.includes("MINUTE")) {
if (normalizedUnit === "MINUTE") {
if (duration % 60 === 0) return `${duration / 60}-hour limit`;
return `${duration}-minute limit`;
}
if (normalizedUnit.includes("HOUR")) return `${duration}-hour limit`;
if (normalizedUnit.includes("DAY")) return `${duration}-day limit`;
if (normalizedUnit.includes("WEEK")) return `${duration}-week limit`;
if (normalizedUnit.includes("SECOND")) return `${duration}-second limit`;
if (normalizedUnit === "HOUR") return `${duration}-hour limit`;
if (normalizedUnit === "DAY") return `${duration}-day limit`;
if (normalizedUnit === "WEEK") return `${duration}-week limit`;
if (normalizedUnit === "SECOND") return `${duration}-second limit`;
return null;
}
@@ -201,7 +201,11 @@ function kimiUsageWindowsFromPayload(payload: unknown): UsageWindow[] {
windows.push(
windowFromFields({
id: uniqueWindowId("coding_usage", seenWindowIds),
label: explicitUsageLabel(usage) ?? "Weekly limit",
// Top-level usage reports no period unless it supplies duration metadata.
label:
explicitUsageLabel(usage) ??
durationLabel(durationFrom(usage.duration), usage.timeUnit) ??
"Usage limit",
fields: usage,
}),
);
+1 -1
View File
@@ -1 +1 @@
{ "id": "muse-provider", "requirements": { "paseo": ">=0.10.0" } }
{ "id": "muse-provider", "requirements": { "paseo": ">=0.11.0" } }
+4 -4
View File
@@ -7,6 +7,7 @@ import {
hashAccountKey,
unavailable,
windowFromUsedPct,
windowFromReportedDuration,
toneFromUsedPct,
} from "@getpaseo/plugin/server/usage";
@@ -76,10 +77,9 @@ export class Usage {
status: "available",
planLabel: usage.tier,
windows: [
windowFromUsedPct({
id: "five_hour",
label: `${usage.window.windowDurationMins / 60} hours`,
shortLabel: `${usage.window.windowDurationMins / 60}h`,
windowFromReportedDuration({
durationSeconds: usage.window.windowDurationMins * 60,
unknown: { id: "window", label: "Current window", shortLabel: "" },
summary: true,
utilizationPct: usage.window.usedPercent,
resetsAt: new Date(usage.window.resetsAtMs).toISOString(),
+23 -1
View File
@@ -1171,7 +1171,7 @@ for (const usage of [
windows: [
{
id: "five_hour",
label: "5 hours",
label: "5-hour",
usedPct: 75,
remainingPct: 25,
tone: "warning",
@@ -1318,3 +1318,25 @@ test.each(["no sessions", "unrelated environment"])("Muse discovery is empty wit
const { Usage } = await import("../server/usage.js");
expect(await new Usage().registration().discover()).toEqual([]);
});
test("Muse window identity follows the reported duration instead of assuming five hours", async () => {
const h = await harness("catalog-controls", {
MUSE_TEST_USAGE: JSON.stringify({
usage: {
observedAtMs: 1700000000000,
tier: "Pro",
window: { usedPercent: 11, resetsAtMs: 1700018000000, windowDurationMins: 120 },
weekly: { usedPercent: 22, resetsAtMs: 1700604800000 },
},
}),
});
await h.open();
const [account] = await h.usageSource.discover();
expect(await h.usageSource.fetch(account!.input)).toMatchObject({
status: "available",
windows: [
{ id: "7200s", label: "2-hour", shortLabel: "2h" },
{ id: "weekly", label: "Weekly", shortLabel: "wk" },
],
});
});
+21
View File
@@ -363,6 +363,27 @@ inputs tried in order until a report is `available`. An unavailable report, erro
fetch falls through to the next input. If none succeeds, the card carries the last report.
Discovery failures are logged by the daemon and produce no card.
Window names must come from provider data: an explicit duration, a named API field such as
`five_hour` or `weekly`, or the provider's own period name. Response slots and reset countdowns
do not establish duration. When the provider omits it, use a neutral name such as “Rolling” or
“Primary limit” and explain the missing metadata next to the adapter.
For numeric durations, use `windowFromReportedDuration()` from
`@getpaseo/plugin/server/usage` (Paseo 0.11+). Pass the reported seconds or `null`, a neutral
`unknown` identity and name, and an optional stable quota `scope`. The helper derives the ID,
label, and short label together; it accepts no duration-label override. For named API periods,
use `windowFromUsedPct()` with names justified by that field.
A window ID identifies a quota scope and period, never its response position, utilization, or
reset instant. Scope model-specific quotas by the provider's stable feature ID, falling back to
the reported limit name when no ID exists. Preserve IDs across slot moves and display-name
changes. Do not alias an old ambiguous ID to a different period: saved pins match source and
window IDs across all accounts, so users must select the corrected window again.
`summary: true` selects source defaults until the user customizes pins. The app computes one
effective selection for cards, the sidebar, and toggles. The first edit snapshots those defaults;
an explicit empty selection stays empty.
Re-read the selected store in `fetch()` so the CLI's token rotations take effect. Never redeem
refresh tokens or write credential stores: refreshing elsewhere can invalidate the CLI's copy,
and rewriting parsed files can discard fields you do not model. If the store disappeared after