@henryqw/pi-multi-codex 2.0.2 → 2.0.3
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/README.md +1 -1
- package/extensions/multi-codex.ts +47 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,6 @@ The extension maintains a generated, credential-free quota cache at `~/.pi/agent
|
|
|
61
61
|
|
|
62
62
|
## Limits and recovery
|
|
63
63
|
|
|
64
|
-
The extension reads `auth.json
|
|
64
|
+
The extension reads `auth.json` to identify account slots but does not directly edit credentials. When it asks Pi's auth resolver for a token to check quota, Pi may refresh expired OAuth credentials and update its auth store.
|
|
65
65
|
|
|
66
66
|
Scoped sessions can switch only to exact scoped aliases.
|
|
@@ -46,17 +46,21 @@ type SlotIdentity = {
|
|
|
46
46
|
type SlotAuthResolver = (slot: number) => Promise<string | undefined>;
|
|
47
47
|
type QuotaRefresh = { session: AbortController; resolveSlotAuth: SlotAuthResolver };
|
|
48
48
|
|
|
49
|
-
type
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fetchedAt?: number;
|
|
55
|
-
remaining?: number;
|
|
56
|
-
reset?: number;
|
|
49
|
+
type SnapshotBase = { slot: number; accountHash: string; tier?: string; checkedAt: number };
|
|
50
|
+
type MeasuredSnapshot = SnapshotBase & {
|
|
51
|
+
fetchedAt: number;
|
|
52
|
+
remaining: number;
|
|
53
|
+
reset: number;
|
|
57
54
|
fiveHourReset?: number;
|
|
58
|
-
limitedUntil
|
|
55
|
+
limitedUntil: number | null;
|
|
59
56
|
};
|
|
57
|
+
type UsageSnapshot = MeasuredSnapshot | (SnapshotBase & {
|
|
58
|
+
fetchedAt?: never;
|
|
59
|
+
remaining?: never;
|
|
60
|
+
reset?: never;
|
|
61
|
+
fiveHourReset?: never;
|
|
62
|
+
limitedUntil?: never;
|
|
63
|
+
});
|
|
60
64
|
|
|
61
65
|
type UsageLock = {
|
|
62
66
|
slot: number;
|
|
@@ -182,30 +186,27 @@ function validTier(value: unknown): value is string {
|
|
|
182
186
|
|
|
183
187
|
function readSnapshot(value: unknown): UsageSnapshot | undefined {
|
|
184
188
|
if (!isRecord(value) || !validSlot(value.slot) || !validHash(value.accountHash) || !validTime(value.checkedAt)) return undefined;
|
|
185
|
-
const
|
|
186
|
-
if (successful && (
|
|
187
|
-
!validTime(value.fetchedAt) ||
|
|
188
|
-
typeof value.remaining !== "number" ||
|
|
189
|
-
!Number.isFinite(value.remaining) ||
|
|
190
|
-
!validTime(value.reset) ||
|
|
191
|
-
(value.fiveHourReset !== undefined && !validTime(value.fiveHourReset)) ||
|
|
192
|
-
(value.limitedUntil !== null && !validTime(value.limitedUntil))
|
|
193
|
-
)) return undefined;
|
|
194
|
-
if (!successful && (value.fiveHourReset !== undefined || value.limitedUntil !== undefined)) return undefined;
|
|
195
|
-
return {
|
|
189
|
+
const base = {
|
|
196
190
|
slot: value.slot,
|
|
197
191
|
accountHash: value.accountHash,
|
|
198
192
|
...(validTier(value.tier) ? { tier: value.tier } : {}),
|
|
199
193
|
checkedAt: value.checkedAt,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
194
|
+
};
|
|
195
|
+
const successful = value.fetchedAt !== undefined || value.remaining !== undefined || value.reset !== undefined;
|
|
196
|
+
if (!successful) {
|
|
197
|
+
if (value.fiveHourReset !== undefined || value.limitedUntil !== undefined) return undefined;
|
|
198
|
+
return base;
|
|
199
|
+
}
|
|
200
|
+
if (!validTime(value.fetchedAt) || typeof value.remaining !== "number" || !Number.isFinite(value.remaining) ||
|
|
201
|
+
!validTime(value.reset) || (value.fiveHourReset !== undefined && !validTime(value.fiveHourReset)) ||
|
|
202
|
+
(value.limitedUntil !== null && !validTime(value.limitedUntil))) return undefined;
|
|
203
|
+
return {
|
|
204
|
+
...base,
|
|
205
|
+
fetchedAt: value.fetchedAt,
|
|
206
|
+
remaining: Math.max(0, Math.min(100, value.remaining)),
|
|
207
|
+
reset: value.reset,
|
|
208
|
+
...(validTime(value.fiveHourReset) ? { fiveHourReset: value.fiveHourReset } : {}),
|
|
209
|
+
limitedUntil: value.limitedUntil,
|
|
209
210
|
};
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -299,12 +300,14 @@ async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>,
|
|
|
299
300
|
throw new Error("Cache mutex acquisition ended unexpectedly.");
|
|
300
301
|
}
|
|
301
302
|
|
|
302
|
-
function
|
|
303
|
+
function isMeasured(snapshot: UsageSnapshot | undefined): snapshot is MeasuredSnapshot {
|
|
304
|
+
return snapshot?.fetchedAt !== undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isFresh(snapshot: UsageSnapshot | undefined, identity: SlotIdentity, now: number): snapshot is MeasuredSnapshot {
|
|
303
308
|
return Boolean(
|
|
304
|
-
snapshot &&
|
|
309
|
+
isMeasured(snapshot) &&
|
|
305
310
|
snapshot.accountHash === identity.accountHash &&
|
|
306
|
-
validTime(snapshot.fetchedAt) &&
|
|
307
|
-
validTime(snapshot.reset) &&
|
|
308
311
|
snapshot.fetchedAt <= now &&
|
|
309
312
|
snapshot.reset > now &&
|
|
310
313
|
now - snapshot.fetchedAt < REFRESH_MS,
|
|
@@ -312,14 +315,14 @@ function isFresh(snapshot: UsageSnapshot | undefined, identity: SlotIdentity, no
|
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
function isFiveHourLimited(snapshot: UsageSnapshot, now: number): boolean {
|
|
315
|
-
return
|
|
318
|
+
return isMeasured(snapshot) && snapshot.limitedUntil !== null && snapshot.limitedUntil > now;
|
|
316
319
|
}
|
|
317
320
|
|
|
318
|
-
function displayedReset(snapshot:
|
|
319
|
-
if (["free", "go", "plus"].includes(snapshot.tier ?? "") &&
|
|
321
|
+
function displayedReset(snapshot: MeasuredSnapshot, now: number): { label: "5h" | "7d"; time: number } {
|
|
322
|
+
if (["free", "go", "plus"].includes(snapshot.tier ?? "") && snapshot.fiveHourReset !== undefined && snapshot.fiveHourReset > now) {
|
|
320
323
|
return { label: "5h", time: snapshot.fiveHourReset };
|
|
321
324
|
}
|
|
322
|
-
return { label: "7d", time: snapshot.reset
|
|
325
|
+
return { label: "7d", time: snapshot.reset };
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
function checkedRecently(snapshot: UsageSnapshot | undefined, identity: SlotIdentity, now: number): boolean {
|
|
@@ -328,7 +331,7 @@ function checkedRecently(snapshot: UsageSnapshot | undefined, identity: SlotIden
|
|
|
328
331
|
snapshot.accountHash === identity.accountHash &&
|
|
329
332
|
snapshot.checkedAt <= now &&
|
|
330
333
|
now - snapshot.checkedAt < REFRESH_MS &&
|
|
331
|
-
(!
|
|
334
|
+
(!isMeasured(snapshot) || snapshot.reset > now),
|
|
332
335
|
);
|
|
333
336
|
}
|
|
334
337
|
|
|
@@ -491,7 +494,7 @@ class CodexQuotaStatus {
|
|
|
491
494
|
? lock.heartbeatAt + LOCK_STALE_MS + 1
|
|
492
495
|
: undefined;
|
|
493
496
|
const snapshotDue = isFresh(snapshot, identity, now)
|
|
494
|
-
? Math.min(snapshot
|
|
497
|
+
? Math.min(snapshot.fetchedAt + REFRESH_MS, snapshot.reset) + 1
|
|
495
498
|
: checkedRecently(snapshot, identity, now)
|
|
496
499
|
? snapshot!.checkedAt + REFRESH_MS + 1
|
|
497
500
|
: now;
|
|
@@ -614,7 +617,7 @@ class CodexQuotaStatus {
|
|
|
614
617
|
limitedUntil: outcome.limitedUntil && outcome.limitedUntil > checkedAt ? outcome.limitedUntil : null,
|
|
615
618
|
});
|
|
616
619
|
} else {
|
|
617
|
-
state.slots.set(slot, previous?.accountHash === identity.accountHash && (!
|
|
620
|
+
state.slots.set(slot, previous?.accountHash === identity.accountHash && (!isMeasured(previous) || previous.reset > checkedAt)
|
|
618
621
|
? { ...previous, checkedAt }
|
|
619
622
|
: { slot, accountHash: identity.accountHash, checkedAt });
|
|
620
623
|
}
|
|
@@ -685,7 +688,7 @@ class CodexQuotaStatus {
|
|
|
685
688
|
.map(([slot, credential]) => {
|
|
686
689
|
const identity = identityFor(credential);
|
|
687
690
|
const snapshot = state.slots.get(slot);
|
|
688
|
-
if (!identity || !snapshot || snapshot.accountHash !== identity.accountHash
|
|
691
|
+
if (!identity || !isMeasured(snapshot) || snapshot.accountHash !== identity.accountHash) {
|
|
689
692
|
return `Codex slot ${slot}: unavailable`;
|
|
690
693
|
}
|
|
691
694
|
const tier = snapshot.tier ? ` (${snapshot.tier})` : "";
|
|
@@ -871,7 +874,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
871
874
|
if ((slot !== 1 && !registered.has(slot)) || !allowsModel(ctx, model, slot)) return [];
|
|
872
875
|
const identity = identityFor(credential);
|
|
873
876
|
const snapshot = state.slots.get(slot);
|
|
874
|
-
if (!identity || !isFresh(snapshot, identity, now) ||
|
|
877
|
+
if (!identity || !isFresh(snapshot, identity, now) || isFiveHourLimited(snapshot, now)) return [];
|
|
875
878
|
return [{ slot, remaining: snapshot.remaining }];
|
|
876
879
|
});
|
|
877
880
|
};
|
|
@@ -895,7 +898,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
895
898
|
const identity = identityFor(credential);
|
|
896
899
|
const snapshot = state.slots.get(slot);
|
|
897
900
|
if (identity && snapshot?.accountHash === identity.accountHash && isFiveHourLimited(snapshot, now)) return [];
|
|
898
|
-
const remaining = identity && isFresh(snapshot, identity, now) ? snapshot
|
|
901
|
+
const remaining = identity && isFresh(snapshot, identity, now) ? snapshot.remaining : undefined;
|
|
899
902
|
return [{ slot, remaining }];
|
|
900
903
|
})
|
|
901
904
|
.sort((left, right) => (right.remaining ?? -1) - (left.remaining ?? -1) || left.slot - right.slot)
|
|
@@ -909,7 +912,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
909
912
|
const identity = currentIdentity(slot);
|
|
910
913
|
const snapshot = quota.snapshot(slot);
|
|
911
914
|
const prefix = `Codex #${slot}`;
|
|
912
|
-
if (!identity || !snapshot || snapshot.accountHash !== identity.accountHash
|
|
915
|
+
if (!identity || !isMeasured(snapshot) || snapshot.accountHash !== identity.accountHash) {
|
|
913
916
|
return `${prefix} · unavailable`;
|
|
914
917
|
}
|
|
915
918
|
const now = Date.now();
|