@henryqw/pi-multi-codex 3.1.2 → 3.1.4
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 +19 -13
- 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
|
|
3
|
+
Add multiple ChatGPT Codex OAuth accounts. Start Pi work on the eligible account 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 account slot.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
@@ -23,7 +23,9 @@ This package requires Pi 1.1.0 or later in the 1.x series.
|
|
|
23
23
|
|
|
24
24
|
## Use
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Run `/login`. Authenticate `OpenAI Codex (legacy)` for account slot 1. Run `/codex-add`.
|
|
27
|
+
|
|
28
|
+
Run `/login` again. Select the new `OpenAI Codex #<n>` provider. Restart Pi or update model scope. Then run `/codex-status`. The status lists the new account slot with cached quota when available. It shows `unavailable` until the first successful usage snapshot.
|
|
27
29
|
|
|
28
30
|
| Surface | Type | Purpose |
|
|
29
31
|
| --- | --- | --- |
|
|
@@ -37,16 +39,16 @@ The footer and `/codex-status` show the five-hour reset countdown for Free, Go,
|
|
|
37
39
|
|
|
38
40
|
## Flow
|
|
39
41
|
|
|
40
|
-
- Before the first agent start, only fresh usage snapshots
|
|
42
|
+
- Before the first agent start, ranking uses only fresh usage snapshots. The account slot with the most remaining quota wins.
|
|
41
43
|
- An explicit model selection always wins for the next agent start.
|
|
42
|
-
- Before later agent starts, a known active five-hour quota block switches to the eligible
|
|
43
|
-
- After HTTP 429, failover can use authenticated, registered, scope-allowed
|
|
44
|
-
- Failover skips known active five-hour quota blocks. It ranks fresh known quota first
|
|
44
|
+
- Before later agent starts, the extension checks for a known active five-hour quota block. If it finds one, it switches to the eligible account slot with a fresh usage snapshot and the most remaining quota.
|
|
45
|
+
- After HTTP 429, failover can use authenticated, registered, scope-allowed account slots that it has not tried. These slots can have stale or missing quota.
|
|
46
|
+
- Failover skips known active five-hour quota blocks. It ranks fresh known quota first. Then it ranks the remaining slots by slot number.
|
|
45
47
|
- Routing preserves the model ID.
|
|
46
|
-
- Only physical Codex models are slots.
|
|
47
|
-
- During one agent run, each eligible slot
|
|
48
|
-
- Automatic retry stops when
|
|
49
|
-
- The footer shows the fresh quota or five-hour quota block of the active slot.
|
|
48
|
+
- Only physical Codex models are account slots. The extension never routes a virtual model under `openai-codex`, such as `openai-codex/auto`, to an account slot. It shows no quota status for a virtual model.
|
|
49
|
+
- During one agent run, the extension tries each eligible account slot at most one time after HTTP 429 responses.
|
|
50
|
+
- Automatic retry stops when the extension has tried all eligible account slots.
|
|
51
|
+
- The footer shows the fresh quota or five-hour quota block of the active account slot.
|
|
50
52
|
|
|
51
53
|
## Config
|
|
52
54
|
|
|
@@ -54,9 +56,13 @@ Package-owned: `~/.pi/agent/config/pi-multi-codex/config.json`
|
|
|
54
56
|
|
|
55
57
|
| Name | Description | Values | Default |
|
|
56
58
|
| --- | --- | --- | --- |
|
|
57
|
-
| `autoSwitchOn429` | Switches to another eligible slot after an HTTP 429. | Boolean. | `true`
|
|
59
|
+
| `autoSwitchOn429` | Switches to another eligible slot after an HTTP 429. | Boolean. | `true` |
|
|
60
|
+
|
|
61
|
+
Edit this file yourself. The extension does not create or write it. If the file is missing, the extension uses the default `true`. Config changes take effect at the next session start. Set `autoSwitchOn429` to `false` to disable automatic switching.
|
|
62
|
+
|
|
63
|
+
At session start, the extension warns about unknown keys, malformed JSON, or values that are not booleans. The warning includes the file path. The extension ignores unknown keys.
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
Automatic switching stays off for the session if `autoSwitchOn429` is not a boolean or has incorrect spelling, such as `autoswitchOn429`. Switching also stays off for malformed JSON, invalid UTF-8, files larger than 64 KiB, read errors, or a non-object root. The extension keeps the file unchanged. Fix the file. Then start a new session to recover.
|
|
60
66
|
|
|
61
67
|
## State and storage
|
|
62
68
|
|
|
@@ -64,6 +70,6 @@ The extension keeps a generated, credential-free quota cache at `~/.pi/agent/con
|
|
|
64
70
|
|
|
65
71
|
## Limits and recovery
|
|
66
72
|
|
|
67
|
-
The extension reads `auth.json` to identify account slots
|
|
73
|
+
The extension reads `auth.json` to identify account slots. It does not directly edit credentials. It asks Pi's auth resolver for a token to check quota. Pi can then refresh expired OAuth credentials and update its auth store.
|
|
68
74
|
|
|
69
75
|
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.4",
|
|
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": {
|