@adeildo/pi-providers 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ // Reading a plan's windows: the names a person sees, the reset countdown, and the map the session
2
+ // keeps. Nothing here knows a provider.
3
+ import type { AccountUsage, NamedWindow, UsageMap, UsageWindow, Window } from "./types.ts";
4
+
5
+ export function noteUsage(
6
+ usage: UsageMap,
7
+ providerId: string,
8
+ accountId: string,
9
+ reading: AccountUsage | undefined,
10
+ ): void {
11
+ if (reading === undefined) return;
12
+ const accounts = usage.get(providerId) ?? new Map<string, AccountUsage>();
13
+ accounts.set(accountId, reading);
14
+ usage.set(providerId, accounts);
15
+ }
16
+
17
+ export function usageOf(
18
+ usage: UsageMap,
19
+ providerId: string,
20
+ accountId: string,
21
+ ): AccountUsage | undefined {
22
+ return usage.get(providerId)?.get(accountId);
23
+ }
24
+
25
+ /** The windows of a reading, ready to show. */
26
+ export function windowsOf(usage: AccountUsage | undefined, now = Date.now()): UsageWindow[] {
27
+ return namedWindows(usage, now).map((named) => {
28
+ const left = leftOf(named.window, now);
29
+ if (left === undefined) return { name: named.name, used: named.window.used };
30
+ return { name: named.name, used: named.window.used, resetsIn: left };
31
+ });
32
+ }
33
+
34
+ /** The window closest to its limit. */
35
+ export function worstWindow(
36
+ usage: AccountUsage | undefined,
37
+ now = Date.now(),
38
+ ): NamedWindow | undefined {
39
+ return namedWindows(usage, now).reduce<NamedWindow | undefined>(
40
+ (worst, candidate) =>
41
+ worst === undefined || candidate.window.used > worst.window.used ? candidate : worst,
42
+ undefined,
43
+ );
44
+ }
45
+
46
+ /** How long the worst window has left, like `11m`. */
47
+ export function resetIn(usage: AccountUsage | undefined, now = Date.now()): string | undefined {
48
+ const shown = worstWindow(usage, now);
49
+ return shown === undefined ? undefined : leftOf(shown.window, now);
50
+ }
51
+
52
+ export function resetNote(usage: AccountUsage | undefined, now = Date.now()): string | undefined {
53
+ const left = resetIn(usage, now);
54
+ return left === undefined ? undefined : `resets in ${left}`;
55
+ }
56
+
57
+ /** The windows the reading knows, a window that already rolled counted as empty. */
58
+ function namedWindows(usage: AccountUsage | undefined, now: number): NamedWindow[] {
59
+ if (usage === undefined) return [];
60
+ const named: NamedWindow[] = [];
61
+ if (usage.session !== undefined)
62
+ named.push({ name: "5h", window: effective(usage.session, now) });
63
+ if (usage.week !== undefined) named.push({ name: "week", window: effective(usage.week, now) });
64
+ return named;
65
+ }
66
+
67
+ function effective(window: Window, now: number): Window {
68
+ if (window.resetsAt !== undefined && window.resetsAt <= Math.floor(now / 1000))
69
+ return { used: 0 };
70
+ return window;
71
+ }
72
+
73
+ /** How long until a unix second, like `2h`. */
74
+ export function timeLeft(until: number, now = Date.now()): string | undefined {
75
+ const seconds = until - Math.floor(now / 1000);
76
+ return seconds <= 0 ? undefined : duration(seconds);
77
+ }
78
+
79
+ function leftOf(window: Window, now: number): string | undefined {
80
+ return window.resetsAt === undefined ? undefined : timeLeft(window.resetsAt, now);
81
+ }
82
+
83
+ function duration(seconds: number): string {
84
+ if (seconds >= 86_400) return `${Math.round(seconds / 86_400)}d`;
85
+ if (seconds >= 3_600) return `${Math.round(seconds / 3_600)}h`;
86
+ return `${Math.max(1, Math.round(seconds / 60))}m`;
87
+ }
@@ -0,0 +1,47 @@
1
+ // What a plan reports, and the shape every provider reader returns. Nothing here knows Anthropic.
2
+
3
+ /** Percent used, and the unix second the window resets. */
4
+ export interface Window {
5
+ used: number;
6
+ resetsAt?: number;
7
+ }
8
+
9
+ export interface AccountUsage {
10
+ session?: Window;
11
+ week?: Window;
12
+ }
13
+
14
+ /** One window, with the name a person reads. */
15
+ export interface NamedWindow {
16
+ name: string;
17
+ window: Window;
18
+ }
19
+
20
+ /** One window of a reading, ready to show. */
21
+ export interface UsageWindow {
22
+ name: string;
23
+ used: number;
24
+ resetsIn?: string;
25
+ }
26
+
27
+ /** The readings of a session, by provider and account. */
28
+ export type UsageMap = Map<string, Map<string, AccountUsage>>;
29
+
30
+ /** What a reader needs to ask a provider. */
31
+ export interface UsageRequest {
32
+ token: string;
33
+ signal?: AbortSignal;
34
+ }
35
+
36
+ /** The windows of one plan; undefined when it could not be read. Never throws. */
37
+ export type UsageReader = (request: UsageRequest) => Promise<AccountUsage | undefined>;
38
+
39
+ /** How to read one provider's plan. */
40
+ export interface QuotaReader {
41
+ /** The source that reports the windows. */
42
+ read: UsageReader;
43
+ /** The fallback when `read` refuses, like a one-token call. */
44
+ probe?: UsageReader;
45
+ /** The windows a normal response carries, when the provider does. */
46
+ headers?: (headers: Record<string, string>) => AccountUsage | undefined;
47
+ }