@henryqw/pi-multi-codex 3.1.1 → 3.1.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 +15 -15
- package/extensions/multi-codex.ts +9 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-multi-codex`
|
|
2
2
|
|
|
3
|
-
Add multiple ChatGPT Codex OAuth accounts and start Pi work on the eligible slot with the most
|
|
3
|
+
Add multiple ChatGPT Codex OAuth accounts and start Pi work on the eligible slot with the most remaining quota. The extension switches away from active five-hour quota blocks and can retry an HTTP 429 on another eligible slot.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
@@ -11,42 +11,42 @@ Add multiple ChatGPT Codex OAuth accounts and start Pi work on the eligible slot
|
|
|
11
11
|
pi install npm:@henryqw/pi-multi-codex
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This package requires Pi 1.1.0 or later in the 1.x series.
|
|
15
15
|
|
|
16
16
|
## Works with
|
|
17
17
|
|
|
18
18
|
| Package | Relationship | Purpose |
|
|
19
19
|
| --- | --- | --- |
|
|
20
|
-
| [`@henryqw/pi-footer`](https://pi.henry.wang/extensions/pi-footer) | Improves | Shows the
|
|
20
|
+
| [`@henryqw/pi-footer`](https://pi.henry.wang/extensions/pi-footer) | Improves | Shows the remaining quota or five-hour quota block of the active slot in the footer. |
|
|
21
21
|
| [`@henryqw/pi-subagent`](https://pi.henry.wang/extensions/pi-subagent) | Improves | Isolated children keep Main's active Codex slot. |
|
|
22
22
|
| [`@henryqw/pi-task-models`](https://pi.henry.wang/extensions/pi-task-models) | Improves | Numbered slots share one profile route. |
|
|
23
23
|
|
|
24
24
|
## Use
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
First, run `/login` and authenticate `OpenAI Codex (legacy)` for slot 1. Run `/codex-add`. Then run `/login` and select the new `OpenAI Codex #<n>` provider. Restart Pi or update model scope. Then run `/codex-status`. The status lists the new slot with cached quota when available, or `unavailable` until the first successful usage snapshot.
|
|
27
27
|
|
|
28
28
|
| Surface | Type | Purpose |
|
|
29
29
|
| --- | --- | --- |
|
|
30
30
|
| `/codex-add` | command | For people: create the next numbered slot, then authenticate that slot. |
|
|
31
|
-
| `/codex-status` | command | For people: show shared
|
|
31
|
+
| `/codex-status` | command | For people: show shared usage snapshots and five-hour quota blocks. It never waits on the network. |
|
|
32
32
|
| `/codex-switch` | command | For people: select an authenticated, scope-allowed slot for the current Codex model. |
|
|
33
33
|
| Pi model selector (`OpenAI Codex (legacy)`, `OpenAI Codex #<n>`) | ui | Choose an authenticated account slot. |
|
|
34
|
-
| Pi status `pi-multi-codex` | ui | Report the active slot's fresh quota, five-hour limit countdown, or stale/unavailable state
|
|
34
|
+
| Pi status `pi-multi-codex` | ui | Report the active slot's fresh quota, five-hour limit countdown, or stale/unavailable state. `@henryqw/pi-footer` displays it in the footer. |
|
|
35
35
|
|
|
36
36
|
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. A numbered slot is one Codex account position in Pi.
|
|
37
37
|
|
|
38
38
|
## Flow
|
|
39
39
|
|
|
40
|
-
- Before the first agent start, only fresh
|
|
40
|
+
- Before the first agent start, only fresh usage snapshots enter startup ranking. The slot with the most remaining quota wins.
|
|
41
41
|
- An explicit model selection always wins for the next agent start.
|
|
42
|
-
- Before later agent starts, a known active five-hour block switches to the eligible fresh slot with the most
|
|
42
|
+
- Before later agent starts, a known active five-hour quota block switches to the eligible fresh slot with the most remaining quota.
|
|
43
43
|
- After HTTP 429, failover can use authenticated, registered, scope-allowed, untried slots with stale or missing quota.
|
|
44
|
-
- Failover skips known active five-hour blocks. It ranks fresh known quota first, then unranked slots by slot number.
|
|
44
|
+
- Failover skips known active five-hour quota blocks. It ranks fresh known quota first, then unranked slots by slot number.
|
|
45
45
|
- Routing preserves the model ID.
|
|
46
46
|
- Only physical Codex models are slots. A virtual model listed under `openai-codex`, such as `openai-codex/auto`, is never routed to a slot and shows no quota status.
|
|
47
|
-
- During one agent run, each eligible slot is tried at most
|
|
47
|
+
- During one agent run, each eligible slot is tried at most one time after HTTP 429 responses.
|
|
48
48
|
- Automatic retry stops when no untried eligible slot remains.
|
|
49
|
-
- The footer shows the
|
|
49
|
+
- The footer shows the fresh quota or five-hour quota block of the active slot.
|
|
50
50
|
|
|
51
51
|
## Config
|
|
52
52
|
|
|
@@ -54,16 +54,16 @@ Package-owned: `~/.pi/agent/config/pi-multi-codex/config.json`
|
|
|
54
54
|
|
|
55
55
|
| Name | Description | Values | Default |
|
|
56
56
|
| --- | --- | --- | --- |
|
|
57
|
-
| `autoSwitchOn429` | Switches to another eligible slot after an HTTP 429. | Boolean. | `true`
|
|
57
|
+
| `autoSwitchOn429` | Switches to another eligible slot after an HTTP 429. | Boolean. | `true` |
|
|
58
58
|
|
|
59
|
-
Edit this file yourself
|
|
59
|
+
Edit this file yourself. The extension does not create or write it. A missing file uses the default `true`, and config changes take effect at the next session start. Set `autoSwitchOn429` to `false` to disable automatic switching. At session start, the extension shows a warning with the file path for an unknown key, malformed JSON, or a value that is not a boolean. It ignores an unknown key. When `autoSwitchOn429` is not a boolean or is misspelled (for example, `autoswitchOn429`), or when the file has malformed JSON, invalid UTF-8, more than 64 KiB, a read error, or a non-object root, automatic switching stays off for that session. The extension keeps the file unchanged. Fix the file and start a new session to recover.
|
|
60
60
|
|
|
61
61
|
## State and storage
|
|
62
62
|
|
|
63
|
-
The extension
|
|
63
|
+
The extension keeps a generated, credential-free quota cache at `~/.pi/agent/config/pi-multi-codex/usage.json`.
|
|
64
64
|
|
|
65
65
|
## Limits and recovery
|
|
66
66
|
|
|
67
|
-
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
|
|
67
|
+
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 can refresh expired OAuth credentials and update its auth store.
|
|
68
68
|
|
|
69
69
|
Scoped sessions can switch only to exact scoped aliases.
|
|
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
-
import { createConfigStore, extensionConfigDir, writePrivateTextFileAtomically } from "@henryqw/pi-config-store";
|
|
6
|
+
import { createConfigStore, extensionConfigDir, field, parsers, registerConfigReporting, writePrivateTextFileAtomically } from "@henryqw/pi-config-store";
|
|
7
7
|
import { lock } from "proper-lockfile";
|
|
8
8
|
import {
|
|
9
9
|
createAssistantMessageEventStream,
|
|
@@ -76,7 +76,6 @@ type UsageState = {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
type ParsedUsage = { remaining: number; reset: number; fiveHourReset?: number; limitedUntil?: number; tier?: string };
|
|
79
|
-
type Config = { autoSwitchOn429: boolean };
|
|
80
79
|
|
|
81
80
|
const NATIVE_PROVIDER_ID = "openai-codex";
|
|
82
81
|
const CODEX_ALIAS_PATTERN = /^openai-codex-([2-9]|[1-9]\d+)$/;
|
|
@@ -92,13 +91,6 @@ const CLEANUP_TIMEOUT_MS = 250;
|
|
|
92
91
|
const cachePath = () => join(extensionConfigDir("pi-multi-codex"), "usage.json");
|
|
93
92
|
const cacheMutexPath = () => `${cachePath()}.mutex`;
|
|
94
93
|
|
|
95
|
-
function parseConfig(value: unknown): Config {
|
|
96
|
-
if (!isRecord(value) || Object.keys(value).length !== 1 || typeof value.autoSwitchOn429 !== "boolean") {
|
|
97
|
-
throw new Error("Config must contain only autoSwitchOn429:boolean.");
|
|
98
|
-
}
|
|
99
|
-
return { autoSwitchOn429: value.autoSwitchOn429 };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
94
|
function isRecord(value: unknown): value is JsonRecord {
|
|
103
95
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
96
|
}
|
|
@@ -820,11 +812,11 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
820
812
|
const native = builtinProviders().find((provider) => provider.id === NATIVE_PROVIDER_ID) as CodexProvider | undefined;
|
|
821
813
|
if (!native) return;
|
|
822
814
|
const quota = new CodexQuotaStatus();
|
|
823
|
-
const configStore = createConfigStore
|
|
815
|
+
const configStore = createConfigStore({
|
|
824
816
|
extensionId: "pi-multi-codex",
|
|
825
|
-
|
|
826
|
-
parse: parseConfig,
|
|
817
|
+
fields: { autoSwitchOn429: field(parsers.boolean(), true) },
|
|
827
818
|
});
|
|
819
|
+
registerConfigReporting(pi, [configStore]);
|
|
828
820
|
const registered = new Map<number, CodexProvider>();
|
|
829
821
|
const triedSlots = new Set<number>();
|
|
830
822
|
let sessionContext: ExtensionContext | undefined;
|
|
@@ -833,7 +825,6 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
833
825
|
let preserveModelChoice = false;
|
|
834
826
|
let selectingAutomatically = false;
|
|
835
827
|
let autoSwitchOn429 = true;
|
|
836
|
-
let configWarned = false;
|
|
837
828
|
let providerRequest: { provider?: string; status?: number } | undefined;
|
|
838
829
|
|
|
839
830
|
const registerSlot = (slot: number): void => {
|
|
@@ -949,15 +940,11 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
949
940
|
triedSlots.clear();
|
|
950
941
|
providerRequest = undefined;
|
|
951
942
|
preserveModelChoice = false;
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
configWarned = true;
|
|
958
|
-
ctx.ui.notify("Pi Multi Codex config is invalid. Automatic 429 switching is disabled; the file was left unchanged.", "warning");
|
|
959
|
-
}
|
|
960
|
-
}
|
|
943
|
+
// registerConfigReporting shows any issues. A file-level problem or an invalid or misspelled
|
|
944
|
+
// `autoSwitchOn429` keeps switching off; a missing file uses the default.
|
|
945
|
+
const config = configStore.loadSync();
|
|
946
|
+
autoSwitchOn429 = config.status === "ok" && config.value.autoSwitchOn429
|
|
947
|
+
&& !config.issues.some(({ key }) => key === "" || key === "autoSwitchOn429");
|
|
961
948
|
automaticOpen = isManagedModel(ctx.model) && !sessionHasAgentWork(ctx);
|
|
962
949
|
automaticCandidate = ctx.model;
|
|
963
950
|
// Cache-only ranking happens before background refresh starts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-multi-codex",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Use multiple ChatGPT Codex OAuth accounts in Pi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-multi-codex/example.png"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@henryqw/pi-config-store": "^
|
|
50
|
+
"@henryqw/pi-config-store": "^4.0.0",
|
|
51
51
|
"proper-lockfile": "^4.1.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|