@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 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 remaining quota. The extension switches away from active five-hour quota blocks and can retry an HTTP 429 on another eligible slot.
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
  ![Pi showing Codex account quotas and the active footer slot](./example.png)
6
6
  ![Flowchart separating fresh-quota startup ranking from broader HTTP 429 failover](./docs/codex-routing-flow.svg)
@@ -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
- 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.
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 enter startup ranking. The slot with the most remaining quota wins.
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 fresh slot with the most remaining quota.
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 quota blocks. It ranks fresh known quota first, then unranked slots by slot number.
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. 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 one time after HTTP 429 responses.
48
- - Automatic retry stops when no untried eligible slot remains.
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` when the config file is missing |
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
- 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. A present config must contain only this field as a boolean. If the config is invalid, the extension keeps the file, shows a warning, and disables automatic switching for the session. Fix the file and start a new session to recover.
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 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.
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<Config>({
815
+ const configStore = createConfigStore({
824
816
  extensionId: "pi-multi-codex",
825
- defaults: () => ({ autoSwitchOn429: true }),
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
- try {
953
- autoSwitchOn429 = configStore.loadSync().value.autoSwitchOn429;
954
- } catch {
955
- autoSwitchOn429 = false;
956
- if (!configWarned) {
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.2",
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": "^3.0.0",
50
+ "@henryqw/pi-config-store": "^4.0.0",
51
51
  "proper-lockfile": "^4.1.2"
52
52
  },
53
53
  "devDependencies": {