355 lines · 13.1 KB · TypeScript
RawDownload
/** * 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 };}