/** * 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 ProviderConsumeResetCreditInput, 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[] | undefined; } | 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;} /** * One subscription account as the pooled views see it, whichever way it was * reported. The same email signed in natively on two environments, or reported * by a hub as well as natively, is one account: its quota is one bucket, so * counting it twice would misstate what is left. */export interface LimitAccount { readonly key: string; readonly driver: ServerProvider["driver"]; /** The instance's configured name, which is not sensitive; null for hub accounts. */ readonly displayName: string | null; readonly email: string | undefined; readonly plan: string | undefined; readonly accentColor: string | undefined; /** Environments the account is signed in on; empty when only a hub reports it. */ readonly environments: ReadonlyArray<{ readonly environmentId: EnvironmentId; readonly label: string; }>; /** The hub that reported it, when no environment has it natively. */ readonly sourceLabel: string | null; /** Where the displayed reset credit can be redeemed. */ readonly redeem: { readonly environmentId: EnvironmentId; readonly input: ProviderConsumeResetCreditInput; } | null; readonly limits: ServerProviderUsageLimits;} /** * Every account with usable windows across the connected environments, one * entry per distinct account. The freshest reads supply windows and credits; * native instances supply names and environment labels. */export function collectLimitAccounts( presentations: Parameters<typeof collectLimitSources>[0],): readonly LimitAccount[] { const accounts = new Map<string, LimitAccount>(); const creditSources = new Map<string, LimitAccount>(); const hubRedeems = new Map<string, LimitAccount>(); const merge = (key: string, next: LimitAccount) => { // Redeeming through a hub also clears the routing cooldown that hub holds // for the account. Redeeming natively against the same subscription resets // it upstream but leaves the hub refusing to route to the account until // its own cooldown expires, so a hub target wins the redemption outright // while the displayed balance still follows the freshest read. const previousHub = hubRedeems.get(key); if ( next.redeem && "sourceId" in next.redeem.input && (!previousHub || Date.parse(next.limits.checkedAt) > Date.parse(previousHub.limits.checkedAt)) ) { hubRedeems.set(key, next); } const previousCredit = creditSources.get(key); if ( next.limits.resetCredits && (!previousCredit || Date.parse(next.limits.checkedAt) > Date.parse(previousCredit.limits.checkedAt)) ) { creditSources.set(key, next); } const previous = accounts.get(key); if (!previous) { accounts.set(key, next); return; } const fresher = Date.parse(next.limits.checkedAt) > Date.parse(previous.limits.checkedAt); // Two instances on one machine sharing an account still name it once. const environments = [ ...previous.environments, ...next.environments.filter( (candidate) => !previous.environments.some((seen) => seen.environmentId === candidate.environmentId), ), ]; const winner = fresher ? next : previous; // Credits and their redemption target travel together. A failed credit // probe must not erase a successful read from another environment. const creditSource = creditSources.get(key); accounts.set(key, { ...previous, displayName: previous.displayName ?? next.displayName, plan: previous.plan ?? next.plan, accentColor: previous.accentColor ?? next.accentColor, environments, // A hub only names the account when no environment has it natively. sourceLabel: environments.length > 0 ? null : (previous.sourceLabel ?? next.sourceLabel), redeem: hubRedeems.get(key)?.redeem ?? (creditSource ? creditSource.redeem : (winner.redeem ?? previous.redeem ?? next.redeem)), limits: { ...winner.limits, ...(creditSource?.limits.resetCredits ? { resetCredits: creditSource.limits.resetCredits } : { resetCredits: undefined }), }, }); }; for (const [environmentId, presentation] of presentations) { const label = presentation.entry.target.label; for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) { if (!provider.usageLimits || limitsNotice(provider.usageLimits) !== null) continue; merge( accountKey(provider.driver, provider.auth.email) ?? `${environmentId}:${provider.instanceId}`, { key: `${environmentId}:${provider.instanceId}`, driver: provider.driver, displayName: provider.displayName?.trim() || null, email: provider.auth.email, plan: provider.auth.label, accentColor: provider.accentColor, environments: [{ environmentId, label }], sourceLabel: null, redeem: { environmentId, input: { instanceId: provider.instanceId } }, limits: provider.usageLimits, }, ); } } // Every hub account, including those a native instance also knows: the hub // may hold a fresher read of the same subscription, and the merge above // keeps the redeem target consistent with whichever snapshot wins. const labelEnvironment = presentations.size > 1; for (const [environmentId, presentation] of presentations) { for (const source of presentation.serverConfig?.usageLimitSources ?? []) { const sourceLabel = labelEnvironment ? `${presentation.entry.target.label} · ${source.label}` : source.label; for (const account of source.accounts) { if (limitsNotice(account.usageLimits) !== null) continue; merge(accountKey(account.driver, account.email) ?? `${source.id}:${account.id}`, { key: `${source.id}:${account.id}`, driver: account.driver, displayName: account.email ? null : account.id.replace(/\.json$/i, ""), email: account.email, plan: account.plan, accentColor: undefined, environments: [], sourceLabel, redeem: account.usageLimits.resetCredits?.nextCreditId ? { environmentId, input: { sourceId: source.id, accountId: account.id, creditId: account.usageLimits.resetCredits.nextCreditId, }, } : null, limits: account.usageLimits, }); } } } return [...accounts.values()];} /** * What the pooled views cannot draw as a bar: a hub that failed to read, a * provider whose probe failed. Accounts that can never report (API keys) * are left out; there is nothing for the user to act on. The environment * is named only when more than one is connected. */export function collectLimitNotices( presentations: Parameters<typeof collectLimitSources>[0],): readonly string[] { const label = (environmentLabel: string, subject: string) => presentations.size > 1 ? `${environmentLabel} · ${subject}` : subject; const notices: string[] = []; for (const presentation of presentations.values()) { const environmentLabel = presentation.entry.target.label; for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) { // An account that can never report (API key) is left out; one that // failed, or reported nothing at all, is worth a line. if (provider.usageLimits?.unavailable?.reason === "unsupported") continue; const notice = provider.usageLimits ? limitsNotice(provider.usageLimits) : null; const name = provider.displayName?.trim() || String(provider.driver); if (notice) notices.push(`${label(environmentLabel, name)}: ${notice}`); } for (const source of presentation.serverConfig?.usageLimitSources ?? []) { if (source.error) { notices.push(`${label(environmentLabel, source.label)}: ${source.error}`); } else if (source.accounts.length === 0) { notices.push(`${label(environmentLabel, source.label)}: No accounts reported.`); } } } return notices;} export interface LimitPoolMember { readonly account: LimitAccount; readonly window: ServerProviderUsageWindow;} /** * One window id across every account that reports it: the pooled share left, * pace against the clock, and the resets in the order they will land, each * with the share of the pool it hands back. */export interface LimitPoolWindow { readonly id: string; readonly kind: ServerProviderUsageWindow["kind"]; readonly label: string; readonly members: readonly LimitPoolMember[]; /** Fixed account positions across rows; a null window leaves a gap. */ readonly columns: ReadonlyArray<{ readonly account: LimitAccount; readonly window: ServerProviderUsageWindow | null; }>; readonly remainingPercent: number; readonly usedPercent: number; readonly pace: LimitPace | null; readonly resets: ReadonlyArray<{ readonly member: LimitPoolMember; readonly at: number; /** Points of the pool the reset restores: the member's used share over the member count. */ readonly restoresPercent: number; }>;} export interface LimitPool { readonly driver: ServerProvider["driver"]; readonly accounts: readonly LimitAccount[]; readonly windows: readonly LimitPoolWindow[];} const WINDOW_KIND_ORDER: Record<ServerProviderUsageWindow["kind"], number> = { session: 0, weekly: 1, monthly: 2, other: 3,}; /** * Accounts grouped by driver, each with its windows pooled by kind and id. * Window ids are stable per provider, so a hub row and a native row for the * same window land in the same pool; the kind is part of the key because * Codex's `primary` is a position, not a duration (five hours on paid plans, * a month on Free/Go), and a monthly allowance must not average into a * five-hour pool. Pools order by kind, then first appearance. * * Accounts and columns share the session reset order, soonest first. When * no account reports a session window, use the first window by kind instead. * Missing reset times sort last, with account names and keys breaking ties. * Each window's reset list still follows its own clock. */export function collectLimitPools( accounts: readonly LimitAccount[], now: number,): readonly LimitPool[] { const byDriver = new Map<ServerProvider["driver"], LimitAccount[]>(); for (const account of accounts) { const list = byDriver.get(account.driver); if (list) list.push(account); else byDriver.set(account.driver, [account]); } return [...byDriver].map(([driver, members]) => { const orderWindow = members .flatMap((account) => account.limits.windows) .sort((left, right) => WINDOW_KIND_ORDER[left.kind] - WINDOW_KIND_ORDER[right.kind])[0]; const orderReset = (account: LimitAccount) => { const window = account.limits.windows.find( (window) => window.kind === orderWindow?.kind && window.id === orderWindow.id, ); return (window ? resetMillis(window) : null) ?? Number.POSITIVE_INFINITY; }; const sorted = [...members].sort( (left, right) => orderReset(left) - orderReset(right) || accountSortName(left).localeCompare(accountSortName(right)) || left.key.localeCompare(right.key), ); return { driver, accounts: sorted, windows: poolWindows(sorted, now) }; });} function accountSortName(account: LimitAccount): string { return (account.displayName ?? account.email ?? account.key).toLowerCase();} function poolWindows(accounts: readonly LimitAccount[], now: number): readonly LimitPoolWindow[] { const byKey = new Map<string, LimitPoolMember[]>(); for (const account of accounts) { for (const window of account.limits.windows) { const key = `${window.kind}:${window.id}`; const list = byKey.get(key); if (list) list.push({ account, window }); else byKey.set(key, [{ account, window }]); } } const pools = [...byKey.values()].map((members): LimitPoolWindow => { const memberByAccount = new Map(members.map((member) => [member.account.key, member])); const first = members[0]!.window; const usedPercent = members.reduce((sum, m) => sum + m.window.usedPercent, 0) / members.length; // Pace compares spend against the clock, so it is judged only over the // members that have a clock; a window with no reset would otherwise // count as spend with no time elapsed and skew the verdict. const timed = members.flatMap((m) => { const share = elapsedShare(m.window, now); return share === null ? [] : [{ used: m.window.usedPercent, elapsed: share }]; }); const timedUsed = timed.reduce((sum, t) => sum + t.used, 0) / timed.length; const meanElapsed = timed.length > 0 ? timed.reduce((sum, t) => sum + t.elapsed, 0) / timed.length : null; const resets = members .flatMap((member) => { const at = resetMillis(member.window); return at === null ? [] : [ { member, at, restoresPercent: Math.round(member.window.usedPercent / members.length), }, ]; }) .sort((left, right) => left.at - right.at); return { id: first.id, kind: first.kind, label: first.label, members, columns: accounts.map( (account) => memberByAccount.get(account.key) ?? { account, window: null }, ), usedPercent: Math.round(usedPercent), remainingPercent: Math.round(100 - usedPercent), pace: meanElapsed === null ? null : paceOfShares(timedUsed, meanElapsed), resets, }; }); return pools.sort((left, right) => WINDOW_KIND_ORDER[left.kind] - WINDOW_KIND_ORDER[right.kind]);} /** 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;}Lines 1–500 of 743. Load more
/** * 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 ProviderConsumeResetCreditInput, 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[] | undefined; } | 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; } /** * One subscription account as the pooled views see it, whichever way it was * reported. The same email signed in natively on two environments, or reported * by a hub as well as natively, is one account: its quota is one bucket, so * counting it twice would misstate what is left. */ export interface LimitAccount { readonly key: string; readonly driver: ServerProvider["driver"]; /** The instance's configured name, which is not sensitive; null for hub accounts. */ readonly displayName: string | null; readonly email: string | undefined; readonly plan: string | undefined; readonly accentColor: string | undefined; /** Environments the account is signed in on; empty when only a hub reports it. */ readonly environments: ReadonlyArray<{ readonly environmentId: EnvironmentId; readonly label: string; }>; /** The hub that reported it, when no environment has it natively. */ readonly sourceLabel: string | null; /** Where the displayed reset credit can be redeemed. */ readonly redeem: { readonly environmentId: EnvironmentId; readonly input: ProviderConsumeResetCreditInput; } | null; readonly limits: ServerProviderUsageLimits; } /** * Every account with usable windows across the connected environments, one * entry per distinct account. The freshest reads supply windows and credits; * native instances supply names and environment labels. */ export function collectLimitAccounts( presentations: Parameters<typeof collectLimitSources>[0], ): readonly LimitAccount[] { const accounts = new Map<string, LimitAccount>(); const creditSources = new Map<string, LimitAccount>(); const hubRedeems = new Map<string, LimitAccount>(); const merge = (key: string, next: LimitAccount) => { // Redeeming through a hub also clears the routing cooldown that hub holds // for the account. Redeeming natively against the same subscription resets // it upstream but leaves the hub refusing to route to the account until // its own cooldown expires, so a hub target wins the redemption outright // while the displayed balance still follows the freshest read. const previousHub = hubRedeems.get(key); if ( next.redeem && "sourceId" in next.redeem.input && (!previousHub || Date.parse(next.limits.checkedAt) > Date.parse(previousHub.limits.checkedAt)) ) { hubRedeems.set(key, next); } const previousCredit = creditSources.get(key); if ( next.limits.resetCredits && (!previousCredit || Date.parse(next.limits.checkedAt) > Date.parse(previousCredit.limits.checkedAt)) ) { creditSources.set(key, next); } const previous = accounts.get(key); if (!previous) { accounts.set(key, next); return; } const fresher = Date.parse(next.limits.checkedAt) > Date.parse(previous.limits.checkedAt); // Two instances on one machine sharing an account still name it once. const environments = [ ...previous.environments, ...next.environments.filter( (candidate) => !previous.environments.some((seen) => seen.environmentId === candidate.environmentId), ), ]; const winner = fresher ? next : previous; // Credits and their redemption target travel together. A failed credit // probe must not erase a successful read from another environment. const creditSource = creditSources.get(key); accounts.set(key, { ...previous, displayName: previous.displayName ?? next.displayName, plan: previous.plan ?? next.plan, accentColor: previous.accentColor ?? next.accentColor, environments, // A hub only names the account when no environment has it natively. sourceLabel: environments.length > 0 ? null : (previous.sourceLabel ?? next.sourceLabel), redeem: hubRedeems.get(key)?.redeem ?? (creditSource ? creditSource.redeem : (winner.redeem ?? previous.redeem ?? next.redeem)), limits: { ...winner.limits, ...(creditSource?.limits.resetCredits ? { resetCredits: creditSource.limits.resetCredits } : { resetCredits: undefined }), }, }); }; for (const [environmentId, presentation] of presentations) { const label = presentation.entry.target.label; for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) { if (!provider.usageLimits || limitsNotice(provider.usageLimits) !== null) continue; merge( accountKey(provider.driver, provider.auth.email) ?? `${environmentId}:${provider.instanceId}`, { key: `${environmentId}:${provider.instanceId}`, driver: provider.driver, displayName: provider.displayName?.trim() || null, email: provider.auth.email, plan: provider.auth.label, accentColor: provider.accentColor, environments: [{ environmentId, label }], sourceLabel: null, redeem: { environmentId, input: { instanceId: provider.instanceId } }, limits: provider.usageLimits, }, ); } } // Every hub account, including those a native instance also knows: the hub // may hold a fresher read of the same subscription, and the merge above // keeps the redeem target consistent with whichever snapshot wins. const labelEnvironment = presentations.size > 1; for (const [environmentId, presentation] of presentations) { for (const source of presentation.serverConfig?.usageLimitSources ?? []) { const sourceLabel = labelEnvironment ? `${presentation.entry.target.label} · ${source.label}` : source.label; for (const account of source.accounts) { if (limitsNotice(account.usageLimits) !== null) continue; merge(accountKey(account.driver, account.email) ?? `${source.id}:${account.id}`, { key: `${source.id}:${account.id}`, driver: account.driver, displayName: account.email ? null : account.id.replace(/\.json$/i, ""), email: account.email, plan: account.plan, accentColor: undefined, environments: [], sourceLabel, redeem: account.usageLimits.resetCredits?.nextCreditId ? { environmentId, input: { sourceId: source.id, accountId: account.id, creditId: account.usageLimits.resetCredits.nextCreditId, }, } : null, limits: account.usageLimits, }); } } } return [...accounts.values()]; } /** * What the pooled views cannot draw as a bar: a hub that failed to read, a * provider whose probe failed. Accounts that can never report (API keys) * are left out; there is nothing for the user to act on. The environment * is named only when more than one is connected. */ export function collectLimitNotices( presentations: Parameters<typeof collectLimitSources>[0], ): readonly string[] { const label = (environmentLabel: string, subject: string) => presentations.size > 1 ? `${environmentLabel} · ${subject}` : subject; const notices: string[] = []; for (const presentation of presentations.values()) { const environmentLabel = presentation.entry.target.label; for (const provider of providersWithLimits(presentation.serverConfig?.providers ?? [])) { // An account that can never report (API key) is left out; one that // failed, or reported nothing at all, is worth a line. if (provider.usageLimits?.unavailable?.reason === "unsupported") continue; const notice = provider.usageLimits ? limitsNotice(provider.usageLimits) : null; const name = provider.displayName?.trim() || String(provider.driver); if (notice) notices.push(`${label(environmentLabel, name)}: ${notice}`); } for (const source of presentation.serverConfig?.usageLimitSources ?? []) { if (source.error) { notices.push(`${label(environmentLabel, source.label)}: ${source.error}`); } else if (source.accounts.length === 0) { notices.push(`${label(environmentLabel, source.label)}: No accounts reported.`); } } } return notices; } export interface LimitPoolMember { readonly account: LimitAccount; readonly window: ServerProviderUsageWindow; } /** * One window id across every account that reports it: the pooled share left, * pace against the clock, and the resets in the order they will land, each * with the share of the pool it hands back. */ export interface LimitPoolWindow { readonly id: string; readonly kind: ServerProviderUsageWindow["kind"]; readonly label: string; readonly members: readonly LimitPoolMember[]; /** Fixed account positions across rows; a null window leaves a gap. */ readonly columns: ReadonlyArray<{ readonly account: LimitAccount; readonly window: ServerProviderUsageWindow | null; }>; readonly remainingPercent: number; readonly usedPercent: number; readonly pace: LimitPace | null; readonly resets: ReadonlyArray<{ readonly member: LimitPoolMember; readonly at: number; /** Points of the pool the reset restores: the member's used share over the member count. */ readonly restoresPercent: number; }>; } export interface LimitPool { readonly driver: ServerProvider["driver"]; readonly accounts: readonly LimitAccount[]; readonly windows: readonly LimitPoolWindow[]; } const WINDOW_KIND_ORDER: Record<ServerProviderUsageWindow["kind"], number> = { session: 0, weekly: 1, monthly: 2, other: 3, }; /** * Accounts grouped by driver, each with its windows pooled by kind and id. * Window ids are stable per provider, so a hub row and a native row for the * same window land in the same pool; the kind is part of the key because * Codex's `primary` is a position, not a duration (five hours on paid plans, * a month on Free/Go), and a monthly allowance must not average into a * five-hour pool. Pools order by kind, then first appearance. * * Accounts and columns share the session reset order, soonest first. When * no account reports a session window, use the first window by kind instead. * Missing reset times sort last, with account names and keys breaking ties. * Each window's reset list still follows its own clock. */ export function collectLimitPools( accounts: readonly LimitAccount[], now: number, ): readonly LimitPool[] { const byDriver = new Map<ServerProvider["driver"], LimitAccount[]>(); for (const account of accounts) { const list = byDriver.get(account.driver); if (list) list.push(account); else byDriver.set(account.driver, [account]); } return [...byDriver].map(([driver, members]) => { const orderWindow = members .flatMap((account) => account.limits.windows) .sort((left, right) => WINDOW_KIND_ORDER[left.kind] - WINDOW_KIND_ORDER[right.kind])[0]; const orderReset = (account: LimitAccount) => { const window = account.limits.windows.find( (window) => window.kind === orderWindow?.kind && window.id === orderWindow.id, ); return (window ? resetMillis(window) : null) ?? Number.POSITIVE_INFINITY; }; const sorted = [...members].sort( (left, right) => orderReset(left) - orderReset(right) || accountSortName(left).localeCompare(accountSortName(right)) || left.key.localeCompare(right.key), ); return { driver, accounts: sorted, windows: poolWindows(sorted, now) }; }); } function accountSortName(account: LimitAccount): string { return (account.displayName ?? account.email ?? account.key).toLowerCase(); } function poolWindows(accounts: readonly LimitAccount[], now: number): readonly LimitPoolWindow[] { const byKey = new Map<string, LimitPoolMember[]>(); for (const account of accounts) { for (const window of account.limits.windows) { const key = `${window.kind}:${window.id}`; const list = byKey.get(key); if (list) list.push({ account, window }); else byKey.set(key, [{ account, window }]); } } const pools = [...byKey.values()].map((members): LimitPoolWindow => { const memberByAccount = new Map(members.map((member) => [member.account.key, member])); const first = members[0]!.window; const usedPercent = members.reduce((sum, m) => sum + m.window.usedPercent, 0) / members.length; // Pace compares spend against the clock, so it is judged only over the // members that have a clock; a window with no reset would otherwise // count as spend with no time elapsed and skew the verdict. const timed = members.flatMap((m) => { const share = elapsedShare(m.window, now); return share === null ? [] : [{ used: m.window.usedPercent, elapsed: share }]; }); const timedUsed = timed.reduce((sum, t) => sum + t.used, 0) / timed.length; const meanElapsed = timed.length > 0 ? timed.reduce((sum, t) => sum + t.elapsed, 0) / timed.length : null; const resets = members .flatMap((member) => { const at = resetMillis(member.window); return at === null ? [] : [ { member, at, restoresPercent: Math.round(member.window.usedPercent / members.length), }, ]; }) .sort((left, right) => left.at - right.at); return { id: first.id, kind: first.kind, label: first.label, members, columns: accounts.map( (account) => memberByAccount.get(account.key) ?? { account, window: null }, ), usedPercent: Math.round(usedPercent), remainingPercent: Math.round(100 - usedPercent), pace: meanElapsed === null ? null : paceOfShares(timedUsed, meanElapsed), resets, }; }); return pools.sort((left, right) => WINDOW_KIND_ORDER[left.kind] - WINDOW_KIND_ORDER[right.kind]); } /** 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; }