mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 05:32:58 +00:00
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:
@@ -1 +1 @@
|
||||
{ "id": "codex-usage-source", "requirements": { "paseo": ">=0.9.2" } }
|
||||
{ "id": "codex-usage-source", "requirements": { "paseo": ">=0.11.0" } }
|
||||
|
||||
@@ -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" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 @@
|
||||
{ "id": "muse-provider", "requirements": { "paseo": ">=0.10.0" } }
|
||||
{ "id": "muse-provider", "requirements": { "paseo": ">=0.11.0" } }
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user