Files
paseo/plugins/muse-provider/server/usage.ts
T
Mohamed Boudra 05e94ab52e Keep usage logins visible and explain unavailable quota (#5876)
* fix(usage): keep existing logins visible and explain unavailable quota

Credential discovery must preserve accounts whose login exists even when it has expired. Keep vendor failures on the discovered account and isolate discovery failures without inventing provider cards.

* test(usage): align credential route tests with account discovery
2026-10-02 00:30:11 +01:00

116 lines
4.1 KiB
TypeScript

import { homedir } from "node:os";
import path from "node:path";
import { z } from "zod";
import type { UsageSourceRegistration } from "@getpaseo/plugin/server";
import type { ProviderLaunch } from "@getpaseo/plugin/server/provider";
import {
hashAccountKey,
unavailable,
windowFromUsedPct,
toneFromUsedPct,
} from "@getpaseo/plugin/server/usage";
import { MspConnection } from "./connection.js";
import { usageSchema } from "./wire.js";
const inputSchema = z.object({ account: z.string() }).strict();
type Observation = z.infer<typeof usageSchema>;
export class Usage {
private readonly launches = new Map<string, ProviderLaunch>();
private readonly sessions = new Map<
string,
{ account: string; read: () => Promise<Observation> }
>();
remember(launch: ProviderLaunch): { account: string } {
// MSP 1.4.1 has no stable account ID; swapping accounts in one config directory shares a report ID.
const configHome =
launch.env.XDG_CONFIG_HOME ?? path.join(launch.env.HOME ?? homedir(), ".config");
const key = hashAccountKey(path.resolve(configHome, "muse"));
this.launches.set(key, launch);
return { account: key };
}
attach(id: string, launch: ProviderLaunch, read: () => Promise<Observation>): void {
this.sessions.set(id, { ...this.remember(launch), read });
}
detach(id: string): void {
this.sessions.delete(id);
}
private async read(account: string, launch: ProviderLaunch): Promise<Observation> {
const readers = [...this.sessions.values()].filter((session) => session.account === account);
if (readers.length > 0) {
const observations = await Promise.all(readers.map((session) => session.read()));
const usages = observations.flatMap((observation) =>
observation.usage ? [observation.usage] : [],
);
usages.sort((a, b) => b.observedAtMs - a.observedAtMs);
return usages.length > 0 ? { usage: usages[0] } : {};
}
const host = new MspConnection({ launch });
try {
await host.initialize();
return await host.request("usage/read", {}, usageSchema);
} finally {
await host.close();
}
}
registration(): UsageSourceRegistration {
return {
id: "muse",
label: "Muse Code",
icon: "icon.svg",
input: inputSchema,
discover: async () =>
[...this.launches.keys()].map((account) => ({
key: account,
label: "Muse Code",
input: { account },
})),
fetch: async (input) => {
const { account } = inputSchema.parse(input);
const launch = this.launches.get(account);
if (!launch) throw new Error("Muse login no longer exists");
try {
const { usage } = await this.read(account, launch);
if (!usage) return unavailable({ kind: "no_quota", detail: "No usage quota reported" });
return {
status: "available",
planLabel: usage.tier,
windows: [
windowFromUsedPct({
id: "five_hour",
label: `${usage.window.windowDurationMins / 60} hours`,
shortLabel: `${usage.window.windowDurationMins / 60}h`,
summary: true,
utilizationPct: usage.window.usedPercent,
resetsAt: new Date(usage.window.resetsAtMs).toISOString(),
tone: toneFromUsedPct(usage.window.usedPercent),
}),
windowFromUsedPct({
id: "weekly",
label: "Weekly",
shortLabel: "wk",
summary: true,
utilizationPct: usage.weekly.usedPercent,
resetsAt: new Date(usage.weekly.resetsAtMs).toISOString(),
tone: toneFromUsedPct(usage.weekly.usedPercent),
}),
],
details: [
{
id: "observed",
label: "Observed",
value: new Date(usage.observedAtMs).toISOString(),
},
],
};
} catch (error) {
return {
status: "error",
error: error instanceof Error ? error.message : String(error),
};
}
},
};
}
}