@henryqw/pi-multi-codex 0.4.0 → 0.4.1

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 CHANGED
@@ -1,13 +1,13 @@
1
1
  # `@henryqw/pi-multi-codex`
2
2
 
3
- Use multiple ChatGPT Codex OAuth accounts in Pi.
3
+ Add multiple ChatGPT Codex OAuth accounts and start new Pi work on the slot with the most weekly quota.
4
4
 
5
5
  ![Pi showing Codex account quotas and the active footer slot](./example.png)
6
6
 
7
7
  ## Why
8
8
 
9
- - **Created for**: Use multiple ChatGPT Codex OAuth accounts from one Pi installation.
10
- - **Advantage**: Quota-aware routing starts with the eligible slot that has the most remaining seven-day quota. If that slot returns HTTP 429, Pi retries with another eligible slot.
9
+ - **Created for**: Pi users who work across more than one Codex subscription.
10
+ - **Advantage**: Quota-aware routing starts with the eligible slot that has the most remaining seven-day quota. With automatic switching enabled, Pi can retry an HTTP 429 response on another eligible slot.
11
11
 
12
12
  ## Install
13
13
 
@@ -19,12 +19,16 @@ pi install npm:@henryqw/pi-multi-codex
19
19
 
20
20
  | Package | Why |
21
21
  | --- | --- |
22
- | `@henryqw/pi-footer` | Improves. Shows the active slot's quota or five-hour block in the footer. |
23
- | `@henryqw/pi-subagent` | Improves. Isolated children keep Main's active Codex slot. |
24
- | `@henryqw/pi-task-models` | Improves. Numbered slots share one profile route. |
22
+ | [`@henryqw/pi-footer`](https://pi.henry.wang/extensions/pi-footer) | Improves. Shows the active slot's quota or five-hour block in the footer. |
23
+ | [`@henryqw/pi-subagent`](https://pi.henry.wang/extensions/pi-subagent) | Improves. Isolated children keep Main's active Codex slot. |
24
+ | [`@henryqw/pi-task-models`](https://pi.henry.wang/extensions/pi-task-models) | Improves. Numbered slots share one profile route. |
25
25
 
26
26
  ## Use
27
27
 
28
+ Run `/login` and authenticate `OpenAI Codex` for slot 1 first. Run `/codex-add`, then run `/login` and select the new `OpenAI Codex #<n>` provider. Restart Pi or update model scope, then run `/codex-status`.
29
+
30
+ `/codex-status` lists the new slot. It shows cached quota when available, or `unavailable` until the first successful snapshot.
31
+
28
32
  | Surface | Type | Purpose |
29
33
  | --- | --- | --- |
30
34
  | `/codex-add` | command | Create the next numbered slot, then authenticate that slot. |
@@ -33,15 +37,17 @@ pi install npm:@henryqw/pi-multi-codex
33
37
 
34
38
  A numbered slot is one Codex account position in Pi.
35
39
 
40
+ ![Flowchart separating fresh-quota startup ranking from broader HTTP 429 failover](./docs/codex-routing-flow.svg)
41
+
36
42
  - The extension reads `auth.json`. It never writes or refreshes credentials.
37
- - Before the first agent start, a fresh snapshot routes a managed Codex model to the eligible slot with the most seven-day quota.
38
- - A slot whose reported five-hour window has reached 100% is excluded until that window resets.
43
+ - Before the first agent start, only fresh quota snapshots enter startup ranking. The slot with the most seven-day quota wins.
44
+ - After HTTP 429, failover can use authenticated, registered, scope-allowed, untried slots with stale or missing quota.
45
+ - Failover skips known active five-hour blocks. It ranks fresh known quota first, then unranked slots by slot number.
39
46
  - Routing preserves the model ID.
40
47
  - During one agent run, each eligible slot is tried at most once after HTTP 429 responses.
41
48
  - Automatic retry stops when no untried eligible slot remains.
42
49
  - The footer shows the active slot's fresh quota or five-hour block.
43
50
  - Scoped sessions can switch only to exact scoped aliases.
44
- - Restart or update model scope after adding a slot.
45
51
 
46
52
  ## Config
47
53
 
@@ -0,0 +1,119 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Codex startup ranking and HTTP 429 failover</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;display=swap" rel="stylesheet">
8
+ <style>
9
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
+ body { min-height: 100vh; display: grid; place-items: center; padding: 32px; background: #f0eee9; color: #101828; }
11
+ .frame { width: min(1280px, 100%); }
12
+ .eyebrow { margin-bottom: 8px; color: #4c5665; font: 500 10px/1 'Geist Mono', monospace; letter-spacing: .18em; text-transform: uppercase; }
13
+ h1 { margin-bottom: 24px; font: 400 28px/1.15 'Instrument Serif', serif; }
14
+ svg { display: block; width: 100%; min-width: 960px; height: auto; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <main class="frame">
19
+ <p class="eyebrow">Flowchart · @henryqw/pi-multi-codex</p>
20
+ <h1>Codex startup ranking and HTTP 429 failover</h1>
21
+ <svg width="1280" height="720" viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="codex-routing-flow-title codex-routing-flow-desc">
22
+ <title id="codex-routing-flow-title">Codex startup ranking and HTTP 429 failover</title>
23
+ <desc id="codex-routing-flow-desc">At startup, only authenticated, registered, scope-allowed slots with fresh quota snapshots and no known active five-hour block are ranked; the slot with the highest seven-day remaining quota is chosen, or the current slot stays active when none qualify. Success keeps the active slot. Non-429 failures and HTTP 429 with autoSwitchOn429 disabled do not retry. With switching enabled, HTTP 429 marks the failed slot tried, then failover allows authenticated, registered, scope-allowed, untried slots with stale or missing quota, excludes known active five-hour blocks, ranks fresh known quota first and then unranked slots by slot number, and retries without returning to startup ranking.</desc>
24
+ <defs>
25
+ <style>
26
+ @import url(&quot;https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;display=swap&quot;);
27
+ .node { fill: #fff; stroke: #101828; stroke-width: 1; }
28
+ .node-state { fill: rgba(16,24,40,.05); stroke: #4c5665; stroke-width: 1; }
29
+ .node-soft { fill: rgba(16,24,40,.03); stroke: #101828; stroke-width: 1; }
30
+ .node-focal { fill: rgba(29,78,216,.08); stroke: #1d4ed8; stroke-width: 1.2; }
31
+ .name { fill: #101828; font: 600 20px 'Geist', sans-serif; text-anchor: middle; }
32
+ .diamond-name { fill: #101828; font: 600 18px 'Geist', sans-serif; text-anchor: middle; }
33
+ .sub { fill: #4c5665; font: 400 16px 'Geist Mono', monospace; text-anchor: middle; }
34
+ .edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#arrow); }
35
+ .label-mask { fill: #f0eee9; }
36
+ .label { fill: #4c5665; font: 500 16px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
37
+ .note-label { fill: #4c5665; font: 500 16px 'Geist Mono', monospace; letter-spacing: .12em; }
38
+ .note { fill: #4c5665; font: 400 16px 'Geist', sans-serif; }
39
+ </style>
40
+ <marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#4c5665"/></marker>
41
+ </defs>
42
+
43
+ <rect width="1280" height="720" fill="#f0eee9"/>
44
+
45
+ <!-- Startup ranking uses only fresh, known quota. -->
46
+ <path class="edge" d="M508 104 H556"/>
47
+ <path class="edge" d="M864 104 H912"/>
48
+ <path class="edge" d="M1056 144 V192"/>
49
+
50
+ <!-- Outcomes split before the separate HTTP 429 failover pool. -->
51
+ <path class="edge" d="M936 240 H888"/>
52
+ <path class="edge" d="M1176 240 H1240 V348 H1024"/>
53
+ <path class="edge" d="M1056 288 V404 H300 V416"/>
54
+
55
+ <!-- Failover retries return to the request, never to startup ranking. -->
56
+ <path class="edge" d="M560 508 H584"/>
57
+ <path class="edge" d="M824 508 H856"/>
58
+ <path class="edge" d="M704 556 V604 H856"/>
59
+ <path class="edge" d="M1192 508 H1272 V104 H1200"/>
60
+
61
+ <rect class="label-mask" x="344" y="372" width="272" height="44" rx="2"/>
62
+ <text class="label" x="480" y="390">429 + SWITCH ON</text>
63
+ <text class="label" x="480" y="410">MARK FAILED SLOT TRIED</text>
64
+
65
+ <!-- Visually distinct candidate pools. -->
66
+ <rect class="node-focal" x="40" y="40" width="468" height="128" rx="6"/>
67
+ <text class="name" x="274" y="72">Startup ranking pool</text>
68
+ <text class="sub" x="274" y="100">AUTHENTICATED · REGISTERED · SCOPE-ALLOWED</text>
69
+ <text class="sub" x="274" y="124">FRESH USAGE SNAPSHOT REQUIRED</text>
70
+ <text class="sub" x="274" y="148">KNOWN 7-DAY REMAINING · NO ACTIVE 5H BLOCK</text>
71
+
72
+ <rect class="node" x="556" y="56" width="308" height="96" rx="6"/>
73
+ <text class="name" x="710" y="88">Choose startup route</text>
74
+ <text class="sub" x="710" y="116">HIGHEST 7-DAY REMAINING</text>
75
+ <text class="sub" x="710" y="140">NONE → KEEP CURRENT SLOT</text>
76
+
77
+ <rect class="node" x="912" y="64" width="288" height="80" rx="6"/>
78
+ <text class="name" x="1056" y="96">Send request</text>
79
+ <text class="sub" x="1056" y="124">ACTIVE SLOT · MODEL ID KEPT</text>
80
+
81
+ <polygon class="node" points="1056,192 1176,240 1056,288 936,240"/>
82
+ <text class="diamond-name" x="1056" y="247">Result</text>
83
+
84
+ <rect class="node-soft" x="648" y="200" width="240" height="80" rx="40"/>
85
+ <text class="name" x="768" y="232">Keep active slot</text>
86
+ <text class="sub" x="768" y="260">SUCCESS</text>
87
+
88
+ <rect class="node-soft" x="704" y="304" width="320" height="88" rx="44"/>
89
+ <text class="name" x="864" y="334">Stop without another slot</text>
90
+ <text class="sub" x="864" y="362">NON-429 FAILURE</text>
91
+ <text class="sub" x="864" y="382">429 + autoSwitchOn429=false</text>
92
+
93
+ <rect class="node-state" x="40" y="416" width="520" height="184" rx="6"/>
94
+ <text class="name" x="300" y="448">HTTP 429 failover pool</text>
95
+ <text class="sub" x="300" y="476">AUTHENTICATED · REGISTERED · SCOPE-ALLOWED</text>
96
+ <text class="sub" x="300" y="500">UNTRIED · STALE OR MISSING QUOTA ALLOWED</text>
97
+ <text class="sub" x="300" y="524">EXCLUDE: KNOWN ACTIVE 5H BLOCK</text>
98
+ <text class="sub" x="300" y="548">FRESH KNOWN: HIGHEST 7-DAY REMAINING FIRST</text>
99
+ <text class="sub" x="300" y="572">THEN UNRANKED: SLOT NUMBER ASCENDING</text>
100
+
101
+ <polygon class="node" points="704,460 824,508 704,556 584,508"/>
102
+ <text class="diamond-name" x="704" y="504">Failover</text>
103
+ <text class="diamond-name" x="704" y="528">candidate?</text>
104
+
105
+ <rect class="node-focal" x="856" y="468" width="336" height="80" rx="6"/>
106
+ <text class="name" x="1024" y="500">Switch slot and retry</text>
107
+ <text class="sub" x="1024" y="528">SAME REQUEST · MODEL ID KEPT</text>
108
+
109
+ <rect class="node-soft" x="856" y="568" width="336" height="72" rx="36"/>
110
+ <text class="name" x="1024" y="596">Stop failover</text>
111
+ <text class="sub" x="1024" y="620">NO ELIGIBLE UNTRIED SLOT</text>
112
+
113
+ <line x1="40" y1="660" x2="1240" y2="660" stroke="rgba(16,24,40,.141)" stroke-width="0.8"/>
114
+ <text class="note-label" x="40" y="692">RETRY</text>
115
+ <text class="note" x="120" y="692">Retries re-enter at Send request. Startup freshness is not checked again.</text>
116
+ </svg>
117
+ </main>
118
+ </body>
119
+ </html>
@@ -0,0 +1,97 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="1280" height="720" viewBox="0 0 1280 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="codex-routing-flow-title codex-routing-flow-desc">
3
+ <title id="codex-routing-flow-title">Codex startup ranking and HTTP 429 failover</title>
4
+ <desc id="codex-routing-flow-desc">At startup, only authenticated, registered, scope-allowed slots with fresh quota snapshots and no known active five-hour block are ranked; the slot with the highest seven-day remaining quota is chosen, or the current slot stays active when none qualify. Success keeps the active slot. Non-429 failures and HTTP 429 with autoSwitchOn429 disabled do not retry. With switching enabled, HTTP 429 marks the failed slot tried, then failover allows authenticated, registered, scope-allowed, untried slots with stale or missing quota, excludes known active five-hour blocks, ranks fresh known quota first and then unranked slots by slot number, and retries without returning to startup ranking.</desc>
5
+ <defs>
6
+ <style>
7
+ @import url(&quot;https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;display=swap&quot;);
8
+ .node { fill: #fff; stroke: #101828; stroke-width: 1; }
9
+ .node-state { fill: rgba(16,24,40,.05); stroke: #4c5665; stroke-width: 1; }
10
+ .node-soft { fill: rgba(16,24,40,.03); stroke: #101828; stroke-width: 1; }
11
+ .node-focal { fill: rgba(29,78,216,.08); stroke: #1d4ed8; stroke-width: 1.2; }
12
+ .name { fill: #101828; font: 600 20px 'Geist', sans-serif; text-anchor: middle; }
13
+ .diamond-name { fill: #101828; font: 600 18px 'Geist', sans-serif; text-anchor: middle; }
14
+ .sub { fill: #4c5665; font: 400 16px 'Geist Mono', monospace; text-anchor: middle; }
15
+ .edge { fill: none; stroke: #4c5665; stroke-width: 1.2; marker-end: url(#arrow); }
16
+ .label-mask { fill: #f0eee9; }
17
+ .label { fill: #4c5665; font: 500 16px 'Geist Mono', monospace; text-anchor: middle; letter-spacing: .04em; }
18
+ .note-label { fill: #4c5665; font: 500 16px 'Geist Mono', monospace; letter-spacing: .12em; }
19
+ .note { fill: #4c5665; font: 400 16px 'Geist', sans-serif; }
20
+ </style>
21
+ <marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#4c5665"/></marker>
22
+ </defs>
23
+
24
+ <rect width="1280" height="720" fill="#f0eee9"/>
25
+
26
+ <!-- Startup ranking uses only fresh, known quota. -->
27
+ <path class="edge" d="M508 104 H556"/>
28
+ <path class="edge" d="M864 104 H912"/>
29
+ <path class="edge" d="M1056 144 V192"/>
30
+
31
+ <!-- Outcomes split before the separate HTTP 429 failover pool. -->
32
+ <path class="edge" d="M936 240 H888"/>
33
+ <path class="edge" d="M1176 240 H1240 V348 H1024"/>
34
+ <path class="edge" d="M1056 288 V404 H300 V416"/>
35
+
36
+ <!-- Failover retries return to the request, never to startup ranking. -->
37
+ <path class="edge" d="M560 508 H584"/>
38
+ <path class="edge" d="M824 508 H856"/>
39
+ <path class="edge" d="M704 556 V604 H856"/>
40
+ <path class="edge" d="M1192 508 H1272 V104 H1200"/>
41
+
42
+ <rect class="label-mask" x="344" y="372" width="272" height="44" rx="2"/>
43
+ <text class="label" x="480" y="390">429 + SWITCH ON</text>
44
+ <text class="label" x="480" y="410">MARK FAILED SLOT TRIED</text>
45
+
46
+ <!-- Visually distinct candidate pools. -->
47
+ <rect class="node-focal" x="40" y="40" width="468" height="128" rx="6"/>
48
+ <text class="name" x="274" y="72">Startup ranking pool</text>
49
+ <text class="sub" x="274" y="100">AUTHENTICATED · REGISTERED · SCOPE-ALLOWED</text>
50
+ <text class="sub" x="274" y="124">FRESH USAGE SNAPSHOT REQUIRED</text>
51
+ <text class="sub" x="274" y="148">KNOWN 7-DAY REMAINING · NO ACTIVE 5H BLOCK</text>
52
+
53
+ <rect class="node" x="556" y="56" width="308" height="96" rx="6"/>
54
+ <text class="name" x="710" y="88">Choose startup route</text>
55
+ <text class="sub" x="710" y="116">HIGHEST 7-DAY REMAINING</text>
56
+ <text class="sub" x="710" y="140">NONE → KEEP CURRENT SLOT</text>
57
+
58
+ <rect class="node" x="912" y="64" width="288" height="80" rx="6"/>
59
+ <text class="name" x="1056" y="96">Send request</text>
60
+ <text class="sub" x="1056" y="124">ACTIVE SLOT · MODEL ID KEPT</text>
61
+
62
+ <polygon class="node" points="1056,192 1176,240 1056,288 936,240"/>
63
+ <text class="diamond-name" x="1056" y="247">Result</text>
64
+
65
+ <rect class="node-soft" x="648" y="200" width="240" height="80" rx="40"/>
66
+ <text class="name" x="768" y="232">Keep active slot</text>
67
+ <text class="sub" x="768" y="260">SUCCESS</text>
68
+
69
+ <rect class="node-soft" x="704" y="304" width="320" height="88" rx="44"/>
70
+ <text class="name" x="864" y="334">Stop without another slot</text>
71
+ <text class="sub" x="864" y="362">NON-429 FAILURE</text>
72
+ <text class="sub" x="864" y="382">429 + autoSwitchOn429=false</text>
73
+
74
+ <rect class="node-state" x="40" y="416" width="520" height="184" rx="6"/>
75
+ <text class="name" x="300" y="448">HTTP 429 failover pool</text>
76
+ <text class="sub" x="300" y="476">AUTHENTICATED · REGISTERED · SCOPE-ALLOWED</text>
77
+ <text class="sub" x="300" y="500">UNTRIED · STALE OR MISSING QUOTA ALLOWED</text>
78
+ <text class="sub" x="300" y="524">EXCLUDE: KNOWN ACTIVE 5H BLOCK</text>
79
+ <text class="sub" x="300" y="548">FRESH KNOWN: HIGHEST 7-DAY REMAINING FIRST</text>
80
+ <text class="sub" x="300" y="572">THEN UNRANKED: SLOT NUMBER ASCENDING</text>
81
+
82
+ <polygon class="node" points="704,460 824,508 704,556 584,508"/>
83
+ <text class="diamond-name" x="704" y="504">Failover</text>
84
+ <text class="diamond-name" x="704" y="528">candidate?</text>
85
+
86
+ <rect class="node-focal" x="856" y="468" width="336" height="80" rx="6"/>
87
+ <text class="name" x="1024" y="500">Switch slot and retry</text>
88
+ <text class="sub" x="1024" y="528">SAME REQUEST · MODEL ID KEPT</text>
89
+
90
+ <rect class="node-soft" x="856" y="568" width="336" height="72" rx="36"/>
91
+ <text class="name" x="1024" y="596">Stop failover</text>
92
+ <text class="sub" x="1024" y="620">NO ELIGIBLE UNTRIED SLOT</text>
93
+
94
+ <line x1="40" y1="660" x2="1240" y2="660" stroke="rgba(16,24,40,.141)" stroke-width="0.8"/>
95
+ <text class="note-label" x="40" y="692">RETRY</text>
96
+ <text class="note" x="120" y="692">Retries re-enter at Send request. Startup freshness is not checked again.</text>
97
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-multi-codex",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Use multiple ChatGPT Codex OAuth accounts in Pi.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -15,6 +15,7 @@
15
15
  "license": "MIT",
16
16
  "files": [
17
17
  "LICENSE",
18
+ "docs",
18
19
  "extensions",
19
20
  "README.md"
20
21
  ],