@henryqw/pi-multi-codex 1.0.4 → 2.0.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.
- package/README.md +2 -0
- package/extensions/multi-codex.ts +24 -8
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Run `/login` and authenticate `OpenAI Codex` for slot 1 first. Run `/codex-add`,
|
|
|
25
25
|
|
|
26
26
|
`/codex-status` lists the new slot. It shows cached quota when available, or `unavailable` until the first successful snapshot.
|
|
27
27
|
|
|
28
|
+
The footer and `/codex-status` show the five-hour reset countdown for Free, Go, and Plus tiers. They show the seven-day reset countdown for Pro Lite and other tiers.
|
|
29
|
+
|
|
28
30
|
| Surface | Type | Purpose |
|
|
29
31
|
| --- | --- | --- |
|
|
30
32
|
| `/codex-add` | command | Create the next numbered slot, then authenticate that slot. |
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
type ApiStreamOptions,
|
|
12
12
|
type AssistantMessage,
|
|
13
13
|
type AssistantMessageEvent,
|
|
14
|
-
type Context,
|
|
15
14
|
type DeferredCancelOptions,
|
|
16
15
|
type DeferredFetchOptions,
|
|
17
16
|
type Model,
|
|
18
17
|
type Provider,
|
|
19
18
|
type SimpleStreamOptions,
|
|
19
|
+
type TranscriptContext,
|
|
20
20
|
} from "@earendil-works/pi-ai";
|
|
21
21
|
import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
22
22
|
import {
|
|
@@ -54,6 +54,7 @@ type UsageSnapshot = {
|
|
|
54
54
|
fetchedAt?: number;
|
|
55
55
|
remaining?: number;
|
|
56
56
|
reset?: number;
|
|
57
|
+
fiveHourReset?: number;
|
|
57
58
|
limitedUntil?: number | null;
|
|
58
59
|
};
|
|
59
60
|
|
|
@@ -70,7 +71,7 @@ type UsageState = {
|
|
|
70
71
|
untrusted?: true;
|
|
71
72
|
};
|
|
72
73
|
|
|
73
|
-
type ParsedUsage = { remaining: number; reset: number; limitedUntil?: number; tier?: string };
|
|
74
|
+
type ParsedUsage = { remaining: number; reset: number; fiveHourReset?: number; limitedUntil?: number; tier?: string };
|
|
74
75
|
type Config = { autoSwitchOn429: boolean };
|
|
75
76
|
|
|
76
77
|
const NATIVE_PROVIDER_ID = "openai-codex";
|
|
@@ -187,9 +188,10 @@ function readSnapshot(value: unknown): UsageSnapshot | undefined {
|
|
|
187
188
|
typeof value.remaining !== "number" ||
|
|
188
189
|
!Number.isFinite(value.remaining) ||
|
|
189
190
|
!validTime(value.reset) ||
|
|
191
|
+
(value.fiveHourReset !== undefined && !validTime(value.fiveHourReset)) ||
|
|
190
192
|
(value.limitedUntil !== null && !validTime(value.limitedUntil))
|
|
191
193
|
)) return undefined;
|
|
192
|
-
if (!successful && value.limitedUntil !== undefined) return undefined;
|
|
194
|
+
if (!successful && (value.fiveHourReset !== undefined || value.limitedUntil !== undefined)) return undefined;
|
|
193
195
|
return {
|
|
194
196
|
slot: value.slot,
|
|
195
197
|
accountHash: value.accountHash,
|
|
@@ -200,6 +202,7 @@ function readSnapshot(value: unknown): UsageSnapshot | undefined {
|
|
|
200
202
|
fetchedAt: value.fetchedAt as number,
|
|
201
203
|
remaining: Math.max(0, Math.min(100, value.remaining as number)),
|
|
202
204
|
reset: value.reset as number,
|
|
205
|
+
...(validTime(value.fiveHourReset) ? { fiveHourReset: value.fiveHourReset } : {}),
|
|
203
206
|
limitedUntil: value.limitedUntil as number | null,
|
|
204
207
|
}
|
|
205
208
|
: {}),
|
|
@@ -312,6 +315,13 @@ function isFiveHourLimited(snapshot: UsageSnapshot, now: number): boolean {
|
|
|
312
315
|
return validTime(snapshot.limitedUntil) && snapshot.limitedUntil > now;
|
|
313
316
|
}
|
|
314
317
|
|
|
318
|
+
function displayedReset(snapshot: UsageSnapshot, now: number): { label: "5h" | "7d"; time: number } {
|
|
319
|
+
if (["free", "go", "plus"].includes(snapshot.tier ?? "") && validTime(snapshot.fiveHourReset) && snapshot.fiveHourReset > now) {
|
|
320
|
+
return { label: "5h", time: snapshot.fiveHourReset };
|
|
321
|
+
}
|
|
322
|
+
return { label: "7d", time: snapshot.reset! };
|
|
323
|
+
}
|
|
324
|
+
|
|
315
325
|
function checkedRecently(snapshot: UsageSnapshot | undefined, identity: SlotIdentity, now: number): boolean {
|
|
316
326
|
return Boolean(
|
|
317
327
|
snapshot &&
|
|
@@ -341,7 +351,7 @@ function resetTime(window: JsonRecord, now: number): number | undefined {
|
|
|
341
351
|
return resetAfter === undefined || resetAfter < 0 ? undefined : now + resetAfter * 1000;
|
|
342
352
|
}
|
|
343
353
|
|
|
344
|
-
/** Extract seven-day quota and
|
|
354
|
+
/** Extract seven-day quota and the five-hour window. */
|
|
345
355
|
export function parseCodexUsage(value: unknown, now = Date.now()): ParsedUsage | undefined {
|
|
346
356
|
if (!isRecord(value)) return undefined;
|
|
347
357
|
const rateLimit = isRecord(value.rate_limit) ? value.rate_limit : value;
|
|
@@ -355,11 +365,13 @@ export function parseCodexUsage(value: unknown, now = Date.now()): ParsedUsage |
|
|
|
355
365
|
?? (windows.length === 1 && numberValue(windows[0].value.limit_window_seconds) === undefined ? windows[0] : undefined);
|
|
356
366
|
if (!sevenDay || sevenDay.reset <= now) return undefined;
|
|
357
367
|
const fiveHour = windows.find((window) => numberValue(window.value.limit_window_seconds) === 5 * 60 * 60);
|
|
358
|
-
const
|
|
368
|
+
const fiveHourReset = fiveHour && fiveHour.reset > now ? fiveHour.reset : undefined;
|
|
369
|
+
const limitedUntil = fiveHour && fiveHour.used >= 100 ? fiveHourReset : undefined;
|
|
359
370
|
const tier = validTier(value.plan_type) ? value.plan_type : validTier(value.tier) ? value.tier : undefined;
|
|
360
371
|
return {
|
|
361
372
|
remaining: Math.max(0, Math.min(100, 100 - sevenDay.used)),
|
|
362
373
|
reset: sevenDay.reset,
|
|
374
|
+
...(fiveHourReset ? { fiveHourReset } : {}),
|
|
363
375
|
...(limitedUntil ? { limitedUntil } : {}),
|
|
364
376
|
...(tier ? { tier } : {}),
|
|
365
377
|
};
|
|
@@ -598,6 +610,7 @@ class CodexQuotaStatus {
|
|
|
598
610
|
fetchedAt: checkedAt,
|
|
599
611
|
remaining: outcome.remaining,
|
|
600
612
|
reset: outcome.reset,
|
|
613
|
+
...(outcome.fiveHourReset && outcome.fiveHourReset > checkedAt ? { fiveHourReset: outcome.fiveHourReset } : {}),
|
|
601
614
|
limitedUntil: outcome.limitedUntil && outcome.limitedUntil > checkedAt ? outcome.limitedUntil : null,
|
|
602
615
|
});
|
|
603
616
|
} else {
|
|
@@ -680,7 +693,9 @@ class CodexQuotaStatus {
|
|
|
680
693
|
if (isFiveHourLimited(snapshot, now)) {
|
|
681
694
|
return `Codex slot ${slot}${tier}: five-hour limit reached, resets in ${formatDuration(snapshot.limitedUntil! - now)} (${status})`;
|
|
682
695
|
}
|
|
683
|
-
|
|
696
|
+
const display = displayedReset(snapshot, now);
|
|
697
|
+
const resetWindow = display.label === "5h" ? "five-hour" : "seven-day";
|
|
698
|
+
return `Codex slot ${slot}${tier}: ${formatPercent(snapshot.remaining)}% of the seven-day quota remaining; ${resetWindow} reset in ${formatDuration(display.time - now)} (${status})`;
|
|
684
699
|
});
|
|
685
700
|
}
|
|
686
701
|
}
|
|
@@ -693,7 +708,7 @@ function aliasModel(model: CodexModel, provider: string): CodexModel {
|
|
|
693
708
|
return model.provider === provider ? model : { ...model, provider };
|
|
694
709
|
}
|
|
695
710
|
|
|
696
|
-
function nativeContext(context:
|
|
711
|
+
function nativeContext(context: TranscriptContext, alias: string): TranscriptContext {
|
|
697
712
|
return {
|
|
698
713
|
...context,
|
|
699
714
|
messages: context.messages.map((message) => {
|
|
@@ -903,7 +918,8 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
903
918
|
return ctx.ui.theme?.fg ? ctx.ui.theme.fg("error", text) : text;
|
|
904
919
|
}
|
|
905
920
|
if (!isFresh(snapshot, identity, now)) return `${prefix} · stale`;
|
|
906
|
-
const
|
|
921
|
+
const display = displayedReset(snapshot, now);
|
|
922
|
+
const text = `${prefix} · ${formatPercent(snapshot.remaining)}% · ${display.label} ${formatDuration(display.time - now)}`;
|
|
907
923
|
const color = snapshot.remaining >= 50 ? "success" : snapshot.remaining >= 25 ? "warning" : "error";
|
|
908
924
|
return ctx.ui.theme?.fg ? ctx.ui.theme.fg(color, text) : text;
|
|
909
925
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-multi-codex",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Use multiple ChatGPT Codex OAuth accounts in Pi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"pack:check": "npm pack --dry-run"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@earendil-works/pi-ai": "^0.
|
|
29
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
28
|
+
"@earendil-works/pi-ai": "^0.86.0",
|
|
29
|
+
"@earendil-works/pi-coding-agent": "^0.86.0"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
@@ -46,10 +46,12 @@
|
|
|
46
46
|
"image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-multi-codex/example.png"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@henryqw/pi-config-store": "^
|
|
49
|
+
"@henryqw/pi-config-store": "^2.0.0",
|
|
50
50
|
"proper-lockfile": "^4.1.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
+
"@earendil-works/pi-ai": "0.86.0",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "0.86.0",
|
|
53
55
|
"@types/proper-lockfile": "^4.1.4"
|
|
54
56
|
}
|
|
55
57
|
}
|