@cortexkit/antigravity-auth-core 3.0.6 → 3.0.8
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.
- package/dist/account-migration.d.ts +1 -1
- package/dist/account-migration.d.ts.map +1 -1
- package/dist/account-migration.js +228 -53
- package/dist/account-migration.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +2 -0
- package/dist/agy-transport.js.map +1 -1
- package/dist/common-auth-embedded/claustrum/availability.d.ts +89 -0
- package/dist/common-auth-embedded/claustrum/availability.js +106 -0
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +41 -1
- package/dist/common-auth-embedded/claustrum/consumer.js +152 -6
- package/dist/common-auth-embedded/claustrum/custody.d.ts +135 -14
- package/dist/common-auth-embedded/claustrum/custody.js +311 -16
- package/dist/common-auth-embedded/claustrum/errors.d.ts +1 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +5 -3
- package/dist/common-auth-embedded/claustrum/index.js +4 -1
- package/dist/common-auth-embedded/claustrum/insula.d.ts +48 -0
- package/dist/common-auth-embedded/claustrum/insula.js +286 -21
- package/dist/common-auth-embedded/claustrum/watch.d.ts +141 -0
- package/dist/common-auth-embedded/claustrum/watch.js +254 -0
- package/dist/common-auth-embedded/fs/private-files.js +10 -2
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +8 -2
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +6 -1
- package/dist/common-auth-embedded/pi-slot/lock.js +7 -3
- package/dist/common-auth-embedded/quota/format.d.ts +17 -3
- package/dist/common-auth-embedded/quota/format.js +203 -18
- package/dist/common-auth-embedded/quota/index.d.ts +2 -2
- package/dist/common-auth-embedded/quota/index.js +1 -1
- package/dist/common-auth-embedded/quota/projection.d.ts +16 -0
- package/dist/common-auth-embedded/source-output.json +105 -73
- package/dist/common-auth-embedded/store/pool.d.ts +1 -1
- package/dist/common-auth-embedded/store/pool.js +0 -1
- package/dist/common-auth-embedded/store/refresh-lock.d.ts +0 -1
- package/dist/common-auth-embedded/store/refresh-lock.js +0 -1
- package/dist/credential-redaction.d.ts +8 -4
- package/dist/credential-redaction.d.ts.map +1 -1
- package/dist/credential-redaction.js +55 -28
- package/dist/credential-redaction.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/pre-response-deadline.d.ts +41 -0
- package/dist/pre-response-deadline.d.ts.map +1 -0
- package/dist/pre-response-deadline.js +150 -0
- package/dist/pre-response-deadline.js.map +1 -0
- package/dist/quota-presentation.d.ts.map +1 -1
- package/dist/quota-presentation.js +9 -1
- package/dist/quota-presentation.js.map +1 -1
- package/dist/vault-account-source.d.ts +27 -2
- package/dist/vault-account-source.d.ts.map +1 -1
- package/dist/vault-account-source.js +12 -1
- package/dist/vault-account-source.js.map +1 -1
- package/dist/vault-folder-refusal.js +3 -3
- package/dist/vault-grant-reach.d.ts +23 -0
- package/dist/vault-grant-reach.d.ts.map +1 -0
- package/dist/vault-grant-reach.js +15 -0
- package/dist/vault-grant-reach.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* While a vault watch is healthy, the roster is still read this often: the
|
|
3
|
+
* watch carries no history, so a lost notice or a silently dead stream is
|
|
4
|
+
* caught within this period.
|
|
5
|
+
*/
|
|
6
|
+
export const SAFETY_POLL_MS = 5 * 60_000;
|
|
7
|
+
/**
|
|
8
|
+
* After a watch fails without being accepted, the host polls and tries one
|
|
9
|
+
* new watch this much later, so a refusing or draining module is never asked
|
|
10
|
+
* in a tight loop.
|
|
11
|
+
*/
|
|
12
|
+
export const WATCH_RETRY_MS = 5 * 60_000;
|
|
13
|
+
/**
|
|
14
|
+
* First wait before re-subscribing a watch that closed after it had been
|
|
15
|
+
* accepted. It doubles for each further close in a row, up to WATCH_RETRY_MS.
|
|
16
|
+
*/
|
|
17
|
+
export const RESUBSCRIBE_BACKOFF_MS = 1_000;
|
|
18
|
+
/**
|
|
19
|
+
* Tells whether a notice follows the previous one with nothing lost between
|
|
20
|
+
* them. `seq` restarts at 1 for every watch and `epoch` names the sending
|
|
21
|
+
* module's process lifetime. A sequence gap (notices are sheddable), a
|
|
22
|
+
* different epoch (the module restarted) or a notice of an unknown kind all
|
|
23
|
+
* mean changes may have been missed, so the caller must re-read everything.
|
|
24
|
+
*/
|
|
25
|
+
export class ChangeNoticeReader {
|
|
26
|
+
#epoch;
|
|
27
|
+
#seq = 0;
|
|
28
|
+
/**
|
|
29
|
+
* Begin a new watch, whose first notice is seq 1 again. The epoch is kept,
|
|
30
|
+
* so a module restart between two watches still reads as a break.
|
|
31
|
+
*/
|
|
32
|
+
restart() {
|
|
33
|
+
this.#seq = 0;
|
|
34
|
+
}
|
|
35
|
+
continuous(notice) {
|
|
36
|
+
const continuous = notice.kind === 'changed' &&
|
|
37
|
+
notice.seq === this.#seq + 1 &&
|
|
38
|
+
(this.#epoch === undefined || notice.epoch === this.#epoch);
|
|
39
|
+
this.#epoch = notice.epoch;
|
|
40
|
+
this.#seq = notice.seq;
|
|
41
|
+
return continuous;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Runs `read` with at most one call in flight and at most one more queued
|
|
46
|
+
* behind it, however many requests arrive meanwhile. A request made while a
|
|
47
|
+
* read is running may have arrived after that read took its snapshot, so it
|
|
48
|
+
* earns exactly one more read; any further requests share that one.
|
|
49
|
+
*/
|
|
50
|
+
export class CoalescedRead {
|
|
51
|
+
#read;
|
|
52
|
+
#onError;
|
|
53
|
+
#running;
|
|
54
|
+
#queued = false;
|
|
55
|
+
constructor(read, onError) {
|
|
56
|
+
this.#read = read;
|
|
57
|
+
this.#onError = onError;
|
|
58
|
+
}
|
|
59
|
+
/** The read in flight (including its queued follow-up), if any. */
|
|
60
|
+
get running() {
|
|
61
|
+
return this.#running;
|
|
62
|
+
}
|
|
63
|
+
request() {
|
|
64
|
+
if (this.#running) {
|
|
65
|
+
this.#queued = true;
|
|
66
|
+
return this.#running;
|
|
67
|
+
}
|
|
68
|
+
this.#running = (async () => {
|
|
69
|
+
do {
|
|
70
|
+
this.#queued = false;
|
|
71
|
+
try {
|
|
72
|
+
await this.#read();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
this.#onError(error);
|
|
76
|
+
}
|
|
77
|
+
} while (this.#queued);
|
|
78
|
+
})().finally(() => {
|
|
79
|
+
this.#running = undefined;
|
|
80
|
+
});
|
|
81
|
+
return this.#running;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Keeps one vault `watchScoped` open for a host and runs the host's discovery
|
|
86
|
+
* when a notice shows the visible account list changed. Discovery runs only
|
|
87
|
+
* when the notice's view differs from the last discovered roster's view, when
|
|
88
|
+
* a notice shows a gap or a new epoch, or on the first notice of a watch that
|
|
89
|
+
* was re-subscribed after a close (the time between two watches was not
|
|
90
|
+
* observed). It never opens a second watch while one is open.
|
|
91
|
+
*/
|
|
92
|
+
export class ScopedRosterWatch {
|
|
93
|
+
#options;
|
|
94
|
+
#reader = new ChangeNoticeReader();
|
|
95
|
+
#reads;
|
|
96
|
+
#mode = 'polling';
|
|
97
|
+
#watch;
|
|
98
|
+
#timer;
|
|
99
|
+
#stopped = false;
|
|
100
|
+
#latestView;
|
|
101
|
+
#force = false;
|
|
102
|
+
/**
|
|
103
|
+
* How many accepted watches in a row have closed; each one doubles the wait
|
|
104
|
+
* before the next re-subscribe. Reset once a watch stays up five minutes.
|
|
105
|
+
*/
|
|
106
|
+
#closes = 0;
|
|
107
|
+
constructor(options) {
|
|
108
|
+
this.#options = options;
|
|
109
|
+
this.#reads = new CoalescedRead(async () => {
|
|
110
|
+
const force = this.#force;
|
|
111
|
+
this.#force = false;
|
|
112
|
+
// A read is only ever requested by a notice, which set #latestView.
|
|
113
|
+
if (force || this.#latestView !== this.#options.view())
|
|
114
|
+
await this.#options.discover(this.#latestView ?? '');
|
|
115
|
+
}, (error) => {
|
|
116
|
+
if (!this.#stopped)
|
|
117
|
+
this.#options.onError?.(error);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
get mode() {
|
|
121
|
+
return this.#mode;
|
|
122
|
+
}
|
|
123
|
+
start() {
|
|
124
|
+
if (this.#stopped || this.#watch || this.#timer)
|
|
125
|
+
return;
|
|
126
|
+
if (typeof this.#options.client.watchScoped !== 'function') {
|
|
127
|
+
// A claustrum-client older than 0.12.0: poll exactly as before.
|
|
128
|
+
this.#setMode('fallback', 'client-without-watch');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
void this.#open(false);
|
|
132
|
+
}
|
|
133
|
+
stop() {
|
|
134
|
+
if (this.#stopped)
|
|
135
|
+
return;
|
|
136
|
+
this.#stopped = true;
|
|
137
|
+
if (this.#timer)
|
|
138
|
+
(this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
|
|
139
|
+
this.#timer = undefined;
|
|
140
|
+
const watch = this.#watch;
|
|
141
|
+
this.#watch = undefined;
|
|
142
|
+
watch?.handle.stop();
|
|
143
|
+
}
|
|
144
|
+
#setMode(mode, reason) {
|
|
145
|
+
if (this.#mode === mode)
|
|
146
|
+
return;
|
|
147
|
+
this.#mode = mode;
|
|
148
|
+
this.#options.logger?.debug('claustrum vault watch', { mode, reason });
|
|
149
|
+
this.#options.onMode?.(mode);
|
|
150
|
+
}
|
|
151
|
+
#later(delay, run) {
|
|
152
|
+
this.#timer = (this.#options.setTimeoutImpl ?? setTimeout)(() => {
|
|
153
|
+
this.#timer = undefined;
|
|
154
|
+
if (!this.#stopped)
|
|
155
|
+
run();
|
|
156
|
+
}, delay);
|
|
157
|
+
this.#timer.unref?.();
|
|
158
|
+
}
|
|
159
|
+
async #open(resubscribed) {
|
|
160
|
+
let token;
|
|
161
|
+
try {
|
|
162
|
+
token = await this.#options.enrollmentToken();
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (this.#stopped)
|
|
166
|
+
return;
|
|
167
|
+
this.#options.onError?.(error);
|
|
168
|
+
this.#fallback('token-unreadable');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const watchScoped = this.#options.client.watchScoped;
|
|
172
|
+
if (this.#stopped || this.#watch || !watchScoped)
|
|
173
|
+
return;
|
|
174
|
+
this.#reader.restart();
|
|
175
|
+
// A re-subscribed watch reads on its first notice: changes made while no
|
|
176
|
+
// watch was open were never announced.
|
|
177
|
+
this.#force = resubscribed;
|
|
178
|
+
let watch;
|
|
179
|
+
let handle;
|
|
180
|
+
try {
|
|
181
|
+
handle = watchScoped.call(this.#options.client, (notice) => {
|
|
182
|
+
if (this.#stopped || !watch || this.#watch !== watch)
|
|
183
|
+
return;
|
|
184
|
+
watch.notices++;
|
|
185
|
+
if (watch.notices === 1) {
|
|
186
|
+
this.#options.onReachable?.();
|
|
187
|
+
watch.acceptedAt = (this.#options.now ?? Date.now)();
|
|
188
|
+
this.#setMode('watching', 'accepted');
|
|
189
|
+
}
|
|
190
|
+
if (!this.#reader.continuous(notice))
|
|
191
|
+
this.#force = true;
|
|
192
|
+
this.#latestView = notice.view;
|
|
193
|
+
// The read itself decides whether to list, against the roster as
|
|
194
|
+
// it stands when the read runs.
|
|
195
|
+
void this.#reads.request();
|
|
196
|
+
}, { enrollmentToken: token });
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
this.#options.onError?.(error);
|
|
200
|
+
this.#fallback('watch-failed');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
watch = { handle, notices: 0 };
|
|
204
|
+
this.#watch = watch;
|
|
205
|
+
this.#setMode('polling', resubscribed ? 'resubscribing' : 'opening');
|
|
206
|
+
const opened = watch;
|
|
207
|
+
handle.closed.then((end) => this.#closed(opened, end), (error) => this.#closed(opened, { kind: 'transport_lost', error }));
|
|
208
|
+
}
|
|
209
|
+
#closed(watch, end) {
|
|
210
|
+
if (this.#stopped || this.#watch !== watch)
|
|
211
|
+
return;
|
|
212
|
+
this.#watch = undefined;
|
|
213
|
+
if (end.kind === 'stopped')
|
|
214
|
+
return;
|
|
215
|
+
if (end.kind === 'transport_lost')
|
|
216
|
+
this.#options.onTransportLost?.();
|
|
217
|
+
if (end.kind === 'unsupported') {
|
|
218
|
+
// An older vault answers unknown_method: poll exactly as before.
|
|
219
|
+
this.#setMode('fallback', 'vault-without-watch');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (end.kind === 'refused') {
|
|
223
|
+
this.#fallback('refused');
|
|
224
|
+
try {
|
|
225
|
+
this.#options.onRefused(end.error);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
this.#options.onError?.(error);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (watch.notices > 0) {
|
|
233
|
+
// An accepted watch that closed (the vault restarted, or a revocation
|
|
234
|
+
// ended it) gets one new watch. A revocation then shows up as that
|
|
235
|
+
// watch's refusal.
|
|
236
|
+
const now = (this.#options.now ?? Date.now)();
|
|
237
|
+
if (watch.acceptedAt !== undefined &&
|
|
238
|
+
now - watch.acceptedAt >= SAFETY_POLL_MS)
|
|
239
|
+
this.#closes = 0;
|
|
240
|
+
const delay = Math.min(RESUBSCRIBE_BACKOFF_MS * 2 ** Math.min(this.#closes, 16), WATCH_RETRY_MS);
|
|
241
|
+
this.#closes++;
|
|
242
|
+
this.#setMode('polling', 'resubscribing');
|
|
243
|
+
this.#later(delay, () => void this.#open(true));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Zero notices: the watch was never accepted (the vault is draining, or
|
|
247
|
+
// the connection failed). The client already made one read to learn why.
|
|
248
|
+
this.#fallback(end.kind);
|
|
249
|
+
}
|
|
250
|
+
#fallback(reason) {
|
|
251
|
+
this.#setMode('fallback', reason);
|
|
252
|
+
this.#later(WATCH_RETRY_MS, () => void this.#open(false));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -184,19 +184,25 @@ export async function replaceWindowsFile(from, to, options = {}) {
|
|
|
184
184
|
to = resolve(to);
|
|
185
185
|
const now = options.now ?? (() => performance.now());
|
|
186
186
|
const start = now();
|
|
187
|
-
const
|
|
187
|
+
const leaseDeadline = options.deadline ?? Infinity;
|
|
188
|
+
let retryDeadline;
|
|
188
189
|
const delays = [0, 10, 20, 40, 80, 100];
|
|
189
190
|
let original;
|
|
190
191
|
let attempts = 0;
|
|
191
192
|
for (const delay of delays) {
|
|
193
|
+
const deadline = Math.min(retryDeadline ?? Infinity, leaseDeadline);
|
|
192
194
|
if (now() >= deadline)
|
|
193
195
|
break;
|
|
194
196
|
if (delay)
|
|
195
197
|
await (options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))))(Math.min(delay, Math.max(0, deadline - now())));
|
|
196
198
|
if (now() >= deadline)
|
|
197
199
|
break;
|
|
200
|
+
const fenceStart = now();
|
|
198
201
|
await options.beforeRename?.();
|
|
199
|
-
|
|
202
|
+
// Ownership checks fence every attempt but do not spend the sharing-retry budget.
|
|
203
|
+
if (retryDeadline !== undefined)
|
|
204
|
+
retryDeadline += now() - fenceStart;
|
|
205
|
+
if (now() >= leaseDeadline)
|
|
200
206
|
break;
|
|
201
207
|
attempts++;
|
|
202
208
|
try {
|
|
@@ -212,6 +218,8 @@ export async function replaceWindowsFile(from, to, options = {}) {
|
|
|
212
218
|
!detail.reason;
|
|
213
219
|
if (!candidate)
|
|
214
220
|
break;
|
|
221
|
+
// Start the retry window only once a rename has actually failed.
|
|
222
|
+
retryDeadline ??= now() + 500;
|
|
215
223
|
}
|
|
216
224
|
}
|
|
217
225
|
const error = new PrivateFileError(to, 'rename', original ??
|
|
@@ -13,7 +13,7 @@ export interface RefreshFileLock {
|
|
|
13
13
|
whenLost(): Promise<LockLoss>;
|
|
14
14
|
hasLost(): boolean;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
type RefreshFileLockOptions = {
|
|
17
17
|
name: string;
|
|
18
18
|
ttlMs: number;
|
|
19
19
|
/**
|
|
@@ -22,7 +22,6 @@ export declare function acquireRefreshFileLock(options: {
|
|
|
22
22
|
* be one host's path.
|
|
23
23
|
*/
|
|
24
24
|
path: string;
|
|
25
|
-
now?: () => number;
|
|
26
25
|
renew?: boolean;
|
|
27
26
|
renewIntervalMs?: number;
|
|
28
27
|
/** Notifies a live-owner refusal without awaiting; observer failures are ignored. */
|
|
@@ -30,4 +29,11 @@ export declare function acquireRefreshFileLock(options: {
|
|
|
30
29
|
/** Synchronous, never awaited; throws and returned promise/thenable rejections are isolated. */
|
|
31
30
|
onRenewalTimer?: (event: 'scheduled' | 'cancelled') => void;
|
|
32
31
|
onStep?: (step: 'stale-marker-stat' | 'stale-marker-claimed' | 'stale-lock-observed' | 'lock-read-ready' | 'lock-missing' | 'stale-lock-confirmed' | 'stale-lock-removed' | 'stale-lock-recreated' | 'eviction-marker-acquired' | 'renewal-owner-confirmed' | 'renewal-marker-unavailable' | 'renewal-write-fenced' | 'renewal-write-ready' | 'renewal-write-finished' | 'relinquish-read' | 'renewal-finished' | 'release-owner-confirmed') => void | Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
/** Filesystem leases share wall time across all callers and processes. */
|
|
34
|
+
export declare function acquireRefreshFileLock(options: RefreshFileLockOptions): Promise<RefreshFileLock | null>;
|
|
35
|
+
/** @internal Clock seam for lease tests; not exported by the package's fs entry point. */
|
|
36
|
+
export declare function acquireRefreshFileLockWithClock(options: RefreshFileLockOptions & {
|
|
37
|
+
now?: () => number;
|
|
33
38
|
}): Promise<RefreshFileLock | null>;
|
|
39
|
+
export {};
|
|
@@ -31,7 +31,12 @@ export function isLostMarkerRaceError(error) {
|
|
|
31
31
|
return (code === 'ENOENT' ||
|
|
32
32
|
(!windowsPrivateFiles && (code === 'EINVAL' || code === 'ENOTDIR')));
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
/** Filesystem leases share wall time across all callers and processes. */
|
|
35
|
+
export function acquireRefreshFileLock(options) {
|
|
36
|
+
return acquireRefreshFileLockWithClock({ ...options, now: Date.now });
|
|
37
|
+
}
|
|
38
|
+
/** @internal Clock seam for lease tests; not exported by the package's fs entry point. */
|
|
39
|
+
export async function acquireRefreshFileLockWithClock(options) {
|
|
35
40
|
const lockPath = lockPathFor(options.path, options.name);
|
|
36
41
|
const legacyOwnerPath = join(lockPath, 'owner.json');
|
|
37
42
|
const ownerId = randomUUID();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rmdirSync, statSync } from 'node:fs';
|
|
1
|
+
import { rmdirSync, statSync, utimesSync } from 'node:fs';
|
|
2
2
|
import { mkdir, rmdir, stat, utimes } from 'node:fs/promises';
|
|
3
3
|
// Protocol source: proper-lockfile 4.1.2 lib/lockfile.js and lib/mtime-precision.js;
|
|
4
4
|
// line-level citations and the Pi lock options are recorded in docs/sources.md.
|
|
@@ -135,7 +135,9 @@ export async function acquirePiLock(authPath, onCompromised, onRenew) {
|
|
|
135
135
|
return;
|
|
136
136
|
let observed;
|
|
137
137
|
try {
|
|
138
|
-
|
|
138
|
+
// Durable writes can occupy every async filesystem worker in fsync.
|
|
139
|
+
// Renew on the calling thread so their queue cannot consume the lease.
|
|
140
|
+
observed = statSync(path);
|
|
139
141
|
}
|
|
140
142
|
catch (error) {
|
|
141
143
|
retry(error);
|
|
@@ -151,12 +153,14 @@ export async function acquirePiLock(authPath, onCompromised, onRenew) {
|
|
|
151
153
|
}
|
|
152
154
|
const next = new Date(precision === 's' ? Math.ceil(Date.now() / 1_000) * 1_000 : Date.now());
|
|
153
155
|
try {
|
|
154
|
-
|
|
156
|
+
utimesSync(path, next, next);
|
|
155
157
|
}
|
|
156
158
|
catch (error) {
|
|
157
159
|
retry(error);
|
|
158
160
|
return;
|
|
159
161
|
}
|
|
162
|
+
// A metadata syscall can itself stall; never confirm an expired lease.
|
|
163
|
+
assertFresh();
|
|
160
164
|
if (released)
|
|
161
165
|
return;
|
|
162
166
|
mtime = next.getTime();
|
|
@@ -11,8 +11,14 @@ export declare const NO_LIMITS_REPORTED = "no limits reported";
|
|
|
11
11
|
/**
|
|
12
12
|
* `compact` fits a footer or a sidebar row: each window's figure, the credit
|
|
13
13
|
* budget and the stale marker, without reset times. `full` adds each reset.
|
|
14
|
+
* `narrow` fits one line per account in a narrow sidebar: `5h 92% · 7d 66%
|
|
15
|
+
* · Fa 98% left`, the account-wide windows plus at most one model-scoped
|
|
16
|
+
* window or the budget (as `cr`), and no stale marker (see `isQuotaStale`).
|
|
17
|
+
* A reading whose windows belong to named groups and that has no
|
|
18
|
+
* account-wide window reads up to two groups instead:
|
|
19
|
+
* `Ge 7d 62% · Ot 7d 48% left`.
|
|
14
20
|
*/
|
|
15
|
-
export type QuotaTextForm = 'compact' | 'full';
|
|
21
|
+
export type QuotaTextForm = 'compact' | 'full' | 'narrow';
|
|
16
22
|
export interface QuotaTextOptions {
|
|
17
23
|
/** The current time in ms, for reset countdowns and the stale marker. */
|
|
18
24
|
now: number;
|
|
@@ -55,17 +61,25 @@ export declare function quotaWindowName(limit: Pick<ProjectedLimit, 'scope' | 'l
|
|
|
55
61
|
* and the optional age keep the shared formatter's wording.
|
|
56
62
|
*/
|
|
57
63
|
export declare function quotaWindowParts(quota: ProjectedQuota | undefined, options: QuotaTextOptions): QuotaParts;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the reading is stale: true exactly when the compact and full forms
|
|
66
|
+
* would end with `checked … ago`. The narrow form leaves that age out to stay
|
|
67
|
+
* short, so a caller drawing a narrow line uses this to dim it instead.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isQuotaStale(quota: ProjectedQuota | undefined, options: QuotaTextOptions): boolean;
|
|
58
70
|
/**
|
|
59
71
|
* The pieces of a quota text, in order: one per reported window
|
|
60
72
|
* (`5h 58% left, resets 2h`), then the credit budget when there is one, then
|
|
61
73
|
* `checked 3h ago` when the oldest piece shown is stale. Empty when nothing
|
|
62
74
|
* was reported; `formatQuota` turns that into a sentence. A caller printing
|
|
63
|
-
* one line per window (the terminal auth menu) uses these directly.
|
|
75
|
+
* one line per window (the terminal auth menu) uses these directly. In the
|
|
76
|
+
* narrow form the pieces are the narrow line's (`5h 92%`, `7d 66% left`).
|
|
64
77
|
*/
|
|
65
78
|
export declare function quotaTextParts(quota: ProjectedQuota | undefined, options: QuotaTextOptions): string[];
|
|
66
79
|
/**
|
|
67
80
|
* A row's quota as one line: `5h 58% left · 7d 90% left` in the compact
|
|
68
|
-
* form, with `, resets 2h` after each window in the full form
|
|
81
|
+
* form, with `, resets 2h` after each window in the full form, and
|
|
82
|
+
* `5h 58% · 7d 90% left` in the narrow form. With nothing
|
|
69
83
|
* reported it is `NO_QUOTA_READING`, or `NO_LIMITS_REPORTED` when the
|
|
70
84
|
* provider answered without a limit for any window.
|
|
71
85
|
*/
|