@henryqw/pi-multi-codex 2.0.3 → 2.0.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
@@ -21,14 +21,14 @@ pi install npm:@henryqw/pi-multi-codex
21
21
 
22
22
  ## Use
23
23
 
24
- 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`. The status lists the new slot with cached quota when available, or `unavailable` until the first successful snapshot.
24
+ Run `/login` and authenticate `OpenAI Codex (legacy)` 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`. The status lists the new slot with cached quota when available, or `unavailable` until the first successful snapshot.
25
25
 
26
26
  | Surface | Type | Purpose |
27
27
  | --- | --- | --- |
28
28
  | `/codex-add` | command | For people: create the next numbered slot, then authenticate that slot. |
29
29
  | `/codex-status` | command | For people: show shared quota snapshots and five-hour blocks; never waits on network. |
30
30
  | `/codex-switch` | command | For people: select an authenticated, scope-allowed slot for the current Codex model. |
31
- | Pi model selector (`OpenAI Codex`, `OpenAI Codex #<n>`) | ui | Choose an authenticated account slot. |
31
+ | Pi model selector (`OpenAI Codex (legacy)`, `OpenAI Codex #<n>`) | ui | Choose an authenticated account slot. |
32
32
  | 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. |
33
33
 
34
34
  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.
@@ -41,6 +41,7 @@ The footer and `/codex-status` show the five-hour reset countdown for Free, Go,
41
41
  - After HTTP 429, failover can use authenticated, registered, scope-allowed, untried slots with stale or missing quota.
42
42
  - Failover skips known active five-hour blocks. It ranks fresh known quota first, then unranked slots by slot number.
43
43
  - Routing preserves the model ID.
44
+ - 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.
44
45
  - During one agent run, each eligible slot is tried at most once after HTTP 429 responses.
45
46
  - Automatic retry stops when no untried eligible slot remains.
46
47
  - The footer shows the active slot's fresh quota or five-hour block.
@@ -80,7 +80,7 @@ type Config = { autoSwitchOn429: boolean };
80
80
 
81
81
  const NATIVE_PROVIDER_ID = "openai-codex";
82
82
  const CODEX_ALIAS_PATTERN = /^openai-codex-([2-9]|[1-9]\d+)$/;
83
- const NO_ACCOUNTS_MESSAGE = "No Codex OAuth accounts found. Run /login and select OpenAI Codex.";
83
+ const NO_ACCOUNTS_MESSAGE = "No Codex OAuth accounts found. Run /login and select OpenAI Codex (legacy).";
84
84
  const AGENT_STARTED_ENTRY = "pi-multi-codex:agent-started";
85
85
  const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
86
86
  const REFRESH_MS = 5 * 60_000;
@@ -818,8 +818,9 @@ function providerForSlot(slot: number): string {
818
818
  return slot === 1 ? NATIVE_PROVIDER_ID : `${NATIVE_PROVIDER_ID}-${slot}`;
819
819
  }
820
820
 
821
- function isManagedProvider(provider: string | undefined): boolean {
822
- return provider !== undefined && slotForProvider(provider) !== undefined;
821
+ /** A physical Codex model on a slot provider. Virtual models listed under `openai-codex` route elsewhere and are not slots. */
822
+ function isManagedModel(model: Model<any> | undefined): model is CodexModel {
823
+ return model?.api === "openai-codex-responses" && slotForProvider(model.provider) !== undefined;
823
824
  }
824
825
 
825
826
  function sessionHasAgentWork(ctx: ExtensionContext): boolean {
@@ -907,8 +908,8 @@ export default function multiCodex(pi: ExtensionAPI): void {
907
908
 
908
909
  const footerText = (ctx: ExtensionContext): string | undefined => {
909
910
  const model = ctx.model;
910
- const slot = slotForProvider(model?.provider ?? "");
911
- if (!model || !slot) return undefined;
911
+ const slot = isManagedModel(model) ? slotForProvider(model.provider) : undefined;
912
+ if (!slot) return undefined;
912
913
  const identity = currentIdentity(slot);
913
914
  const snapshot = quota.snapshot(slot);
914
915
  const prefix = `Codex #${slot}`;
@@ -941,7 +942,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
941
942
  };
942
943
 
943
944
  const updatePendingCandidate = (ctx: ExtensionContext): void => {
944
- if (!ctx.model || !isManagedProvider(ctx.model.provider)) return;
945
+ if (!isManagedModel(ctx.model)) return;
945
946
  const slot = selectFreshSlot(ctx, ctx.model);
946
947
  automaticCandidate = slot ? { ...ctx.model, provider: providerForSlot(slot) } : ctx.model;
947
948
  };
@@ -970,7 +971,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
970
971
  ctx.ui.notify("Pi Multi Codex config is invalid. Automatic 429 switching is disabled; the file was left unchanged.", "warning");
971
972
  }
972
973
  }
973
- automaticOpen = isManagedProvider(ctx.model?.provider) && !sessionHasAgentWork(ctx);
974
+ automaticOpen = isManagedModel(ctx.model) && !sessionHasAgentWork(ctx);
974
975
  automaticCandidate = ctx.model;
975
976
  // Cache-only ranking happens before background refresh starts.
976
977
  updatePendingCandidate(ctx);
@@ -983,7 +984,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
983
984
  triedSlots.clear();
984
985
  providerRequest = undefined;
985
986
  const model = ctx.model;
986
- const slot = slotForProvider(model?.provider ?? "");
987
+ const slot = isManagedModel(model) ? slotForProvider(model.provider) : undefined;
987
988
  const identity = slot ? currentIdentity(slot) : undefined;
988
989
  const snapshot = slot ? quota.snapshot(slot) : undefined;
989
990
  if (!automaticOpen && (preserveModelChoice || !identity || snapshot?.accountHash !== identity.accountHash || !isFiveHourLimited(snapshot, Date.now()))) return;
@@ -991,7 +992,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
991
992
  // Close before await: no timer, refresh, queued turn, or provider event can route later.
992
993
  automaticOpen = false;
993
994
  const candidate = automaticCandidate;
994
- if (!model || !candidate || !isManagedProvider(model.provider) || candidate.id !== model.id || candidate.provider === model.provider) return;
995
+ if (!isManagedModel(model) || !candidate || candidate.id !== model.id || candidate.provider === model.provider) return;
995
996
  await setModelAutomatically(candidate);
996
997
  updateFooter(ctx);
997
998
  });
@@ -1014,7 +1015,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
1014
1015
  const model = ctx.model;
1015
1016
  const failedSlot = slotForProvider(event.message.provider);
1016
1017
  if (
1017
- !model
1018
+ !isManagedModel(model)
1018
1019
  || !failedSlot
1019
1020
  || request.provider !== event.message.provider
1020
1021
  || model.provider !== event.message.provider
@@ -1069,7 +1070,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
1069
1070
  handler: async (_args, ctx) => {
1070
1071
  const slots = syncSlots();
1071
1072
  if (!slots.has(1)) {
1072
- ctx.ui.notify("Run /login and select OpenAI Codex for slot 1 first.", "warning");
1073
+ ctx.ui.notify("Run /login and select OpenAI Codex (legacy) for slot 1 first.", "warning");
1073
1074
  return;
1074
1075
  }
1075
1076
 
@@ -1093,7 +1094,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
1093
1094
  description: "switch current Codex model to another authenticated slot",
1094
1095
  handler: async (_args, ctx) => {
1095
1096
  const model = ctx.model;
1096
- if (!model || !isManagedProvider(model.provider)) {
1097
+ if (!isManagedModel(model)) {
1097
1098
  ctx.ui.notify("Select an OpenAI Codex model before switching slots.", "warning");
1098
1099
  return;
1099
1100
  }
@@ -1108,7 +1109,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
1108
1109
  ctx.ui.notify(
1109
1110
  scopedModels.length > 0
1110
1111
  ? "No authenticated Codex slot matches this session's model scope. Restart Pi or update scoped models."
1111
- : "No authenticated Codex slots found. Run /login and select OpenAI Codex.",
1112
+ : "No authenticated Codex slots found. Run /login and select OpenAI Codex (legacy).",
1112
1113
  "warning",
1113
1114
  );
1114
1115
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-multi-codex",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
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.86.0 <0.88.0",
29
- "@earendil-works/pi-coding-agent": ">=0.86.0 <0.88.0"
28
+ "@earendil-works/pi-ai": ">=0.86.0 <0.100.0",
29
+ "@earendil-works/pi-coding-agent": ">=0.86.0 <0.100.0"
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
@@ -50,8 +50,8 @@
50
50
  "proper-lockfile": "^4.1.2"
51
51
  },
52
52
  "devDependencies": {
53
- "@earendil-works/pi-ai": "0.87.0",
54
- "@earendil-works/pi-coding-agent": "0.87.0",
53
+ "@earendil-works/pi-ai": "0.99.1",
54
+ "@earendil-works/pi-coding-agent": "0.99.1",
55
55
  "@types/proper-lockfile": "^4.1.4"
56
56
  }
57
57
  }