/** * Selection and pace maths for the provider limits view, shared by web and * mobile so both agree on which providers show, what "ahead of pace" means, * and how a reset is phrased. * * @module usageLimits */import { type EnvironmentId, type UsageLimitsReport, type ProviderInstanceId, type ServerProviderSlashCommand, isProviderAvailable, type ServerProvider, type ServerProviderUsageLimits, type ServerProviderUsageWindow, type UsageLimitSourceSnapshot, type UsageLimitSourceSnapshots,} from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; const MINUTE = 60_000;const HOUR = 60 * MINUTE;const DAY = 24 * HOUR; /** * Providers that belong on the Limits view: enabled, installed, and one whose * driver reports subscription usage at all. A driver with no notion of usage * never sets `usageLimits`, so it has no row rather than an empty one. */export function providersWithLimits( providers: readonly ServerProvider[],): readonly ServerProvider[] { return providers.filter( (provider) => provider.enabled && provider.installed && isProviderAvailable(provider) && provider.usageLimits !== undefined, );} export interface LimitsGroup { readonly environmentId: EnvironmentId; /** Null while only one environment is connected; there is nothing to tell apart. */ readonly environmentLabel: string | null; readonly providers: readonly ServerProvider[];} /** * One group per connected environment with a provider reporting limits. * Provider snapshots come from the config stream every client already holds, * so opening the view costs no extra request. */export function collectLimitsGroups( presentations: ReadonlyMap< EnvironmentId, { readonly entry: { readonly target: { readonly label: string } }; readonly serverConfig: { readonly providers: readonly ServerProvider[] } | null; } >,): readonly LimitsGroup[] { const groups: LimitsGroup[] = []; for (const [environmentId, presentation] of presentations) { const providers = providersWithLimits(presentation.serverConfig?.providers ?? []); if (providers.length === 0) continue; groups.push({ environmentId, environmentLabel: presentation.entry.target.label, providers }); } return groups.length > 1 ? groups : groups.map((group) => ({ ...group, environmentLabel: null }));} /** * Every usage-limit source across connected environments, keyed so two * environments pointing at the same hub still get their own rows. The label * carries the environment only when more than one environment has sources. * A native provider with usable limits takes precedence over the same account * in a source, even when it belongs to another connected environment. */export function collectLimitSources( presentations: ReadonlyMap< EnvironmentId, { readonly entry: { readonly target: { readonly label: string } }; readonly serverConfig: { readonly providers?: readonly ServerProvider[] | undefined; readonly usageLimitSources?: UsageLimitSourceSnapshots | undefined; } | null; } >,): ReadonlyArray< UsageLimitSourceSnapshot & { readonly key: string; readonly environmentId: EnvironmentId; readonly hiddenAccountCount: number; }> { const nativeAccounts = new Set<string>(); for (const presentation of presentations.values()) { for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) { const key = accountKey(provider.driver, provider.auth.email); if ( key !== null && provider.usageLimits?.windows.length && !provider.usageLimits.unavailable ) { nativeAccounts.add(key); } } } const perEnvironment: Array<{ readonly environmentId: EnvironmentId; readonly environmentLabel: string; readonly sources: UsageLimitSourceSnapshots; }> = []; for (const [environmentId, presentation] of presentations) { const sources = presentation.serverConfig?.usageLimitSources ?? []; if (sources.length === 0) continue; perEnvironment.push({ environmentId, environmentLabel: presentation.entry.target.label, sources, }); } const labelEnvironment = perEnvironment.length > 1; return perEnvironment.flatMap(({ environmentId, environmentLabel, sources }) => sources.map((source) => { const accounts = source.accounts.filter((account) => { const key = accountKey(account.driver, account.email); return key === null || !nativeAccounts.has(key); }); return { ...source, accounts, hiddenAccountCount: source.accounts.length - accounts.length, environmentId, key: `${environmentId}:${source.id}`, label: labelEnvironment ? `${environmentLabel} · ${source.label}` : source.label, }; }), );} function accountKey(driver: ServerProvider["driver"], email: string | undefined): string | null { const normalizedEmail = email?.trim().toLowerCase(); return normalizedEmail ? `${driver}:${normalizedEmail}` : null;} /** The instance's configured name, else the driver's, else its raw kind. */export function providerLimitsLabel( provider: Pick<ServerProvider, "driver" | "displayName">, driverLabel: (driver: ServerProvider["driver"]) => string | undefined,): string { return provider.displayName?.trim() || driverLabel(provider.driver) || String(provider.driver);} /** The one-line status under a provider heading when there are no bars to draw. */export function limitsNotice(limits: ServerProviderUsageLimits): string | null { if (limits.unavailable?.reason === "unsupported") { return limits.unavailable.message ?? "This account has no subscription limits."; } if (limits.unavailable?.reason === "probeFailed") { return limits.unavailable.message ?? "Could not read limits."; } return limits.windows.length === 0 ? "No limits reported." : null;} function resetMillis(window: ServerProviderUsageWindow): number | null { if (window.resetsAt === undefined) return null; const at = Date.parse(window.resetsAt); return Number.isFinite(at) ? at : null;} /** Elapsed share of the window, 0..1, or null when its length or reset is unknown. */export function elapsedShare(window: ServerProviderUsageWindow, now: number): number | null { const resetsAt = resetMillis(window); if (resetsAt === null || window.windowDurationMins === undefined) return null; const length = window.windowDurationMins * MINUTE; if (length <= 0) return null; return Math.max(0, Math.min(1, (length - (resetsAt - now)) / length));} export type LimitPace = "ahead" | "on" | "under"; /** * Usage against the clock. The bar is the whole window, so the elapsed share * is also where even spending would have put the fill; within five points of * it counts as on pace. */export function paceOf(window: ServerProviderUsageWindow, now: number): LimitPace | null { const elapsed = elapsedShare(window, now); if (elapsed === null) return null; const gap = window.usedPercent - elapsed * 100; if (gap > 5) return "ahead"; if (gap < -5) return "under"; return "on";} /** `2h 13m`, `3d 4h`, `12m`. */export function formatDuration(ms: number): string { const remaining = Math.max(0, ms); const days = Math.floor(remaining / DAY); const hours = Math.floor((remaining % DAY) / HOUR); const minutes = Math.floor((remaining % HOUR) / MINUTE); if (days > 0) return `${days}d ${hours}h`; if (hours > 0) return `${hours}h ${minutes}m`; return `${minutes}m`;} /** `resets in 2h 13m`, or null when the window has no reset. */export function formatResetsIn(window: ServerProviderUsageWindow, now: number): string | null { const resetsAt = resetMillis(window); if (resetsAt === null) return null; return resetsAt <= now ? "resets now" : `resets in ${formatDuration(resetsAt - now)}`;} /** Limit commands are served by T3 from the same snapshots as Usage → Limits. */export const USAGE_LIMITS_COMMAND = { name: "usage-limits", description: "Show this provider's usage limits",} satisfies ServerProviderSlashCommand; /** Handled by the client without sending a turn; anything with arguments stays an ordinary prompt. */export function isUsageLimitsCommand(prompt: string): boolean { return prompt.trim().toLowerCase() === "/usage-limits";} /** * Whether Limits has anything to say about this driver. A source that failed to * read keeps no accounts, so its error counts for every driver rather than * disappearing until the next successful refresh. */export function hasProviderUsageLimits( driver: ServerProvider["driver"], providers: readonly ServerProvider[], sources: UsageLimitSourceSnapshots,): boolean { return ( providersWithLimits(providers).some((provider) => provider.driver === driver) || sources.some( (source) => source.accounts.some((account) => account.driver === driver) || (source.error !== undefined && source.accounts.length === 0), ) );} /** * The drivers a set of sources would offer the command to, where a source that * failed to read counts for every driver. Two snapshots with the same coverage * need no catalog republish, however much their quotas moved. */export function sameUsageLimitCommandCoverage( previous: UsageLimitSourceSnapshots, next: UsageLimitSourceSnapshots,): boolean { const coverage = (sources: UsageLimitSourceSnapshots) => new Set( sources.flatMap((source) => source.error !== undefined && source.accounts.length === 0 ? ["*"] : source.accounts.map((account) => String(account.driver)), ), ); const before = coverage(previous); const after = coverage(next); return before.size === after.size && [...before].every((driver) => after.has(driver));} /** Advertise on workspace catalogs too, which replace the global command list. */export function withUsageLimitsCommands( providers: readonly ServerProvider[], sources: UsageLimitSourceSnapshots,): ServerProvider[] { return providers.map((provider) => { if (!hasProviderUsageLimits(provider.driver, providers, sources)) return provider; const commands = (items: readonly ServerProviderSlashCommand[]) => [ ...items.filter((command) => command.name !== USAGE_LIMITS_COMMAND.name), USAGE_LIMITS_COMMAND, ]; return { ...provider, slashCommands: commands(provider.slashCommands), ...(provider.workspaceSnapshots ? { workspaceSnapshots: provider.workspaceSnapshots.map((snapshot) => ({ ...snapshot, slashCommands: commands(snapshot.slashCommands), })), } : {}), }; });} /** A point-in-time report; never refreshes or guesses which pooled account serves a turn. */export function collectProviderUsageLimits( instanceId: ProviderInstanceId, providers: readonly ServerProvider[], sources: UsageLimitSourceSnapshots, now: number,): UsageLimitsReport | null { const selected = providers.find((provider) => provider.instanceId === instanceId); if (!selected || !hasProviderUsageLimits(selected.driver, providers, sources)) return null; const native = providersWithLimits(providers).filter( (provider) => provider.driver === selected.driver, ); const nativeAccounts = new Set( native.flatMap((provider) => { const key = accountKey(provider.driver, provider.auth.email); return key && provider.usageLimits?.windows.length && !provider.usageLimits.unavailable ? [key] : []; }), ); const accounts: Array<UsageLimitsReport["accounts"][number]> = []; const notices: string[] = []; for (const provider of native) { if (!provider.usageLimits) continue; accounts.push({ id: provider.instanceId, driver: provider.driver, label: `${providerLimitsLabel(provider, () => undefined)} [${provider.instanceId}]`, ...(provider.auth.label ? { plan: provider.auth.label } : {}), instanceId: provider.instanceId, ...(provider.displayName ? { displayName: provider.displayName } : {}), ...(provider.accentColor ? { accentColor: provider.accentColor } : {}), ...(provider.auth.email ? { email: provider.auth.email } : {}), limits: provider.usageLimits, }); } for (const source of sources) { const matching = source.accounts.filter((account) => account.driver === selected.driver); for (const account of matching) { const key = accountKey(account.driver, account.email); if (key && nativeAccounts.has(key)) continue; accounts.push({ id: `${source.id}:${account.id}`, driver: account.driver, label: `${source.label} · ${account.id}`, sourceLabel: "CLI Proxy", ...(account.plan ? { plan: account.plan } : {}), ...(account.email ? { email: account.email } : {}), limits: account.usageLimits, }); } // A source that failed to read has no accounts left to match on, so its // error is reported to every provider rather than silently dropped. if (source.error && (matching.length > 0 || source.accounts.length === 0)) { notices.push(`${source.label}: ${source.error}`); } } return { createdAt: DateTime.formatIso(DateTime.makeUnsafe(now)), accounts, notices };}
/**
* Selection and pace maths for the provider limits view, shared by web and
* mobile so both agree on which providers show, what "ahead of pace" means,
* and how a reset is phrased.
*
* @module usageLimits
*/
import {
type EnvironmentId,
type UsageLimitsReport,
type ProviderInstanceId,
type ServerProviderSlashCommand,
isProviderAvailable,
type ServerProvider,
type ServerProviderUsageLimits,
type ServerProviderUsageWindow,
type UsageLimitSourceSnapshot,
type UsageLimitSourceSnapshots,
} from "@t3tools/contracts";
import * as DateTime from "effect/DateTime";
const MINUTE = 60_000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
/**
* Providers that belong on the Limits view: enabled, installed, and one whose
* driver reports subscription usage at all. A driver with no notion of usage
* never sets `usageLimits`, so it has no row rather than an empty one.
*/
export function providersWithLimits(
providers: readonly ServerProvider[],
): readonly ServerProvider[] {
return providers.filter(
(provider) =>
provider.enabled &&
provider.installed &&
isProviderAvailable(provider) &&
provider.usageLimits !== undefined,
);
}
export interface LimitsGroup {
readonly environmentId: EnvironmentId;
/** Null while only one environment is connected; there is nothing to tell apart. */
readonly environmentLabel: string | null;
readonly providers: readonly ServerProvider[];
}
/**
* One group per connected environment with a provider reporting limits.
* Provider snapshots come from the config stream every client already holds,
* so opening the view costs no extra request.
*/
export function collectLimitsGroups(
presentations: ReadonlyMap<
EnvironmentId,
{
readonly entry: { readonly target: { readonly label: string } };
readonly serverConfig: { readonly providers: readonly ServerProvider[] } | null;
}
>,
): readonly LimitsGroup[] {
const groups: LimitsGroup[] = [];
for (const [environmentId, presentation] of presentations) {
const providers = providersWithLimits(presentation.serverConfig?.providers ?? []);
if (providers.length === 0) continue;
groups.push({ environmentId, environmentLabel: presentation.entry.target.label, providers });
}
return groups.length > 1 ? groups : groups.map((group) => ({ ...group, environmentLabel: null }));
}
/**
* Every usage-limit source across connected environments, keyed so two
* environments pointing at the same hub still get their own rows. The label
* carries the environment only when more than one environment has sources.
* A native provider with usable limits takes precedence over the same account
* in a source, even when it belongs to another connected environment.
*/
export function collectLimitSources(
presentations: ReadonlyMap<
EnvironmentId,
{
readonly entry: { readonly target: { readonly label: string } };
readonly serverConfig: {
readonly providers?: readonly ServerProvider[] | undefined;
readonly usageLimitSources?: UsageLimitSourceSnapshots | undefined;
} | null;
}
>,
): ReadonlyArray<
UsageLimitSourceSnapshot & {
readonly key: string;
readonly environmentId: EnvironmentId;
readonly hiddenAccountCount: number;
}
> {
const nativeAccounts = new Set<string>();
for (const presentation of presentations.values()) {
for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) {
const key = accountKey(provider.driver, provider.auth.email);
if (
key !== null &&
provider.usageLimits?.windows.length &&
!provider.usageLimits.unavailable
) {
nativeAccounts.add(key);
}
}
}
const perEnvironment: Array<{
readonly environmentId: EnvironmentId;
readonly environmentLabel: string;
readonly sources: UsageLimitSourceSnapshots;
}> = [];
for (const [environmentId, presentation] of presentations) {
const sources = presentation.serverConfig?.usageLimitSources ?? [];
if (sources.length === 0) continue;
perEnvironment.push({
environmentId,
environmentLabel: presentation.entry.target.label,
sources,
});
}
const labelEnvironment = perEnvironment.length > 1;
return perEnvironment.flatMap(({ environmentId, environmentLabel, sources }) =>
sources.map((source) => {
const accounts = source.accounts.filter((account) => {
const key = accountKey(account.driver, account.email);
return key === null || !nativeAccounts.has(key);
});
return {
...source,
accounts,
hiddenAccountCount: source.accounts.length - accounts.length,
environmentId,
key: `${environmentId}:${source.id}`,
label: labelEnvironment ? `${environmentLabel} · ${source.label}` : source.label,
};
}),
);
}
function accountKey(driver: ServerProvider["driver"], email: string | undefined): string | null {
const normalizedEmail = email?.trim().toLowerCase();
return normalizedEmail ? `${driver}:${normalizedEmail}` : null;
}
/** The instance's configured name, else the driver's, else its raw kind. */
export function providerLimitsLabel(
provider: Pick<ServerProvider, "driver" | "displayName">,
driverLabel: (driver: ServerProvider["driver"]) => string | undefined,
): string {
return provider.displayName?.trim() || driverLabel(provider.driver) || String(provider.driver);
}
/** The one-line status under a provider heading when there are no bars to draw. */
export function limitsNotice(limits: ServerProviderUsageLimits): string | null {
if (limits.unavailable?.reason === "unsupported") {
return limits.unavailable.message ?? "This account has no subscription limits.";
}
if (limits.unavailable?.reason === "probeFailed") {
return limits.unavailable.message ?? "Could not read limits.";
}
return limits.windows.length === 0 ? "No limits reported." : null;
}
function resetMillis(window: ServerProviderUsageWindow): number | null {
if (window.resetsAt === undefined) return null;
const at = Date.parse(window.resetsAt);
return Number.isFinite(at) ? at : null;
}
/** Elapsed share of the window, 0..1, or null when its length or reset is unknown. */
export function elapsedShare(window: ServerProviderUsageWindow, now: number): number | null {
const resetsAt = resetMillis(window);
if (resetsAt === null || window.windowDurationMins === undefined) return null;
const length = window.windowDurationMins * MINUTE;
if (length <= 0) return null;
return Math.max(0, Math.min(1, (length - (resetsAt - now)) / length));
}
export type LimitPace = "ahead" | "on" | "under";
/**
* Usage against the clock. The bar is the whole window, so the elapsed share
* is also where even spending would have put the fill; within five points of
* it counts as on pace.
*/
export function paceOf(window: ServerProviderUsageWindow, now: number): LimitPace | null {
const elapsed = elapsedShare(window, now);
if (elapsed === null) return null;
const gap = window.usedPercent - elapsed * 100;
if (gap > 5) return "ahead";
if (gap < -5) return "under";
return "on";
}
/** `2h 13m`, `3d 4h`, `12m`. */
export function formatDuration(ms: number): string {
const remaining = Math.max(0, ms);
const days = Math.floor(remaining / DAY);
const hours = Math.floor((remaining % DAY) / HOUR);
const minutes = Math.floor((remaining % HOUR) / MINUTE);
if (days > 0) return `${days}d ${hours}h`;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
}
/** `resets in 2h 13m`, or null when the window has no reset. */
export function formatResetsIn(window: ServerProviderUsageWindow, now: number): string | null {
const resetsAt = resetMillis(window);
if (resetsAt === null) return null;
return resetsAt <= now ? "resets now" : `resets in ${formatDuration(resetsAt - now)}`;
}
/** Limit commands are served by T3 from the same snapshots as Usage → Limits. */
export const USAGE_LIMITS_COMMAND = {
name: "usage-limits",
description: "Show this provider's usage limits",
} satisfies ServerProviderSlashCommand;
/** Handled by the client without sending a turn; anything with arguments stays an ordinary prompt. */
export function isUsageLimitsCommand(prompt: string): boolean {
return prompt.trim().toLowerCase() === "/usage-limits";
}
/**
* Whether Limits has anything to say about this driver. A source that failed to
* read keeps no accounts, so its error counts for every driver rather than
* disappearing until the next successful refresh.
*/
export function hasProviderUsageLimits(
driver: ServerProvider["driver"],
providers: readonly ServerProvider[],
sources: UsageLimitSourceSnapshots,
): boolean {
return (
providersWithLimits(providers).some((provider) => provider.driver === driver) ||
sources.some(
(source) =>
source.accounts.some((account) => account.driver === driver) ||
(source.error !== undefined && source.accounts.length === 0),
)
);
}
/**
* The drivers a set of sources would offer the command to, where a source that
* failed to read counts for every driver. Two snapshots with the same coverage
* need no catalog republish, however much their quotas moved.
*/
export function sameUsageLimitCommandCoverage(
previous: UsageLimitSourceSnapshots,
next: UsageLimitSourceSnapshots,
): boolean {
const coverage = (sources: UsageLimitSourceSnapshots) =>
new Set(
sources.flatMap((source) =>
source.error !== undefined && source.accounts.length === 0
? ["*"]
: source.accounts.map((account) => String(account.driver)),
),
);
const before = coverage(previous);
const after = coverage(next);
return before.size === after.size && [...before].every((driver) => after.has(driver));
}
/** Advertise on workspace catalogs too, which replace the global command list. */
export function withUsageLimitsCommands(
providers: readonly ServerProvider[],
sources: UsageLimitSourceSnapshots,
): ServerProvider[] {
return providers.map((provider) => {
if (!hasProviderUsageLimits(provider.driver, providers, sources)) return provider;
const commands = (items: readonly ServerProviderSlashCommand[]) => [
...items.filter((command) => command.name !== USAGE_LIMITS_COMMAND.name),
USAGE_LIMITS_COMMAND,
];
return {
...provider,
slashCommands: commands(provider.slashCommands),
...(provider.workspaceSnapshots
? {
workspaceSnapshots: provider.workspaceSnapshots.map((snapshot) => ({
...snapshot,
slashCommands: commands(snapshot.slashCommands),
})),
}
: {}),
};
});
}
/** A point-in-time report; never refreshes or guesses which pooled account serves a turn. */
export function collectProviderUsageLimits(
instanceId: ProviderInstanceId,
providers: readonly ServerProvider[],
sources: UsageLimitSourceSnapshots,
now: number,
): UsageLimitsReport | null {
const selected = providers.find((provider) => provider.instanceId === instanceId);
if (!selected || !hasProviderUsageLimits(selected.driver, providers, sources)) return null;
const native = providersWithLimits(providers).filter(
(provider) => provider.driver === selected.driver,
);
const nativeAccounts = new Set(
native.flatMap((provider) => {
const key = accountKey(provider.driver, provider.auth.email);
return key && provider.usageLimits?.windows.length && !provider.usageLimits.unavailable
? [key]
: [];
}),
);
const accounts: Array<UsageLimitsReport["accounts"][number]> = [];
const notices: string[] = [];
for (const provider of native) {
if (!provider.usageLimits) continue;
accounts.push({
id: provider.instanceId,
driver: provider.driver,
label: `${providerLimitsLabel(provider, () => undefined)} [${provider.instanceId}]`,
...(provider.auth.label ? { plan: provider.auth.label } : {}),
instanceId: provider.instanceId,
...(provider.displayName ? { displayName: provider.displayName } : {}),
...(provider.accentColor ? { accentColor: provider.accentColor } : {}),
...(provider.auth.email ? { email: provider.auth.email } : {}),
limits: provider.usageLimits,
});
}
for (const source of sources) {
const matching = source.accounts.filter((account) => account.driver === selected.driver);
for (const account of matching) {
const key = accountKey(account.driver, account.email);
if (key && nativeAccounts.has(key)) continue;
accounts.push({
id: `${source.id}:${account.id}`,
driver: account.driver,
label: `${source.label} · ${account.id}`,
sourceLabel: "CLI Proxy",
...(account.plan ? { plan: account.plan } : {}),
...(account.email ? { email: account.email } : {}),
limits: account.usageLimits,
});
}
// A source that failed to read has no accounts left to match on, so its
// error is reported to every provider rather than silently dropped.
if (source.error && (matching.length > 0 || source.accounts.length === 0)) {
notices.push(`${source.label}: ${source.error}`);
}
}
return { createdAt: DateTime.formatIso(DateTime.makeUnsafe(now)), accounts, notices };
}