@henryqw/pi-multi-codex 0.3.9 → 0.3.11
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 -8
- package/extensions/multi-codex.ts +3 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-multi-codex`
|
|
2
2
|
|
|
3
|
-
Use multiple ChatGPT Codex OAuth accounts in Pi.
|
|
3
|
+
Use multiple ChatGPT Codex OAuth accounts in Pi.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,16 +8,23 @@ Use multiple ChatGPT Codex OAuth accounts in Pi. Native `/login` still owns cred
|
|
|
8
8
|
pi install npm:@henryqw/pi-multi-codex
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## With
|
|
12
12
|
|
|
13
|
-
|
|
|
13
|
+
| Package | Why |
|
|
14
14
|
| --- | --- |
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
15
|
+
| `@henryqw/pi-model-thinking` | Improves. Thinking level follows the model across slots. |
|
|
16
|
+
| `@henryqw/pi-task-models` | Improves. Numbered slots share one profile route. |
|
|
17
|
+
| `@henryqw/pi-subagent` | Improves. Isolated children keep Main's active Codex slot. |
|
|
18
|
+
|
|
19
|
+
## Use
|
|
20
|
+
|
|
21
|
+
| Surface | Type | Purpose |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `/codex-add` | command | Create the next numbered slot, then authenticate that slot. |
|
|
24
|
+
| `/codex-status` | command | Show shared seven-day quota snapshots. Never waits on network. |
|
|
25
|
+
| `/codex-switch` | command | Pick an authenticated slot. |
|
|
19
26
|
|
|
20
|
-
Extension reads
|
|
27
|
+
Extension reads `auth.json` and never writes or refreshes credentials. Before the first agent start, a fresh snapshot routes a managed Codex model to the slot with the most seven-day quota. Routing preserves model ID and never changes in-progress work. Footer shows the active slot and fresh seven-day quota. Scoped sessions can switch only to exact scoped aliases; restart or update model scope after adding a slot.
|
|
21
28
|
|
|
22
29
|
Quota snapshots live in `~/.pi/agent/config/pi-multi-codex/usage.json`. They are credential-free cache, not user-edited config.
|
|
23
30
|
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
6
|
import { lock } from "proper-lockfile";
|
|
6
7
|
import {
|
|
7
8
|
createAssistantMessageEventStream,
|
|
@@ -259,32 +260,6 @@ function errorCode(error: unknown): string | undefined {
|
|
|
259
260
|
return error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
function sleepUnref(ms: number, signal?: AbortSignal): Promise<void> {
|
|
263
|
-
return new Promise((resolve, reject) => {
|
|
264
|
-
if (signal?.aborted) return reject(signal.reason);
|
|
265
|
-
let abort: () => void;
|
|
266
|
-
const cleanup = () => signal?.removeEventListener("abort", abort);
|
|
267
|
-
const timer = setTimeout(() => {
|
|
268
|
-
cleanup();
|
|
269
|
-
resolve();
|
|
270
|
-
}, ms);
|
|
271
|
-
timer.unref?.();
|
|
272
|
-
abort = () => {
|
|
273
|
-
clearTimeout(timer);
|
|
274
|
-
cleanup();
|
|
275
|
-
reject(signal?.reason);
|
|
276
|
-
};
|
|
277
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
278
|
-
if (signal?.aborted) abort();
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function cleanupSignal(): AbortSignal {
|
|
283
|
-
const controller = new AbortController();
|
|
284
|
-
void sleepUnref(CLEANUP_TIMEOUT_MS).then(() => controller.abort());
|
|
285
|
-
return controller.signal;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
263
|
async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
289
264
|
signal?.throwIfAborted();
|
|
290
265
|
await mkdir(join(getAgentDir(), "config", "pi-multi-codex"), { recursive: true, mode: 0o700 });
|
|
@@ -310,7 +285,7 @@ async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>,
|
|
|
310
285
|
}
|
|
311
286
|
} catch (error) {
|
|
312
287
|
if (errorCode(error) !== "ELOCKED") throw error;
|
|
313
|
-
await
|
|
288
|
+
await delay(CACHE_MUTEX_RETRY_MS, undefined, { signal, ref: false });
|
|
314
289
|
}
|
|
315
290
|
}
|
|
316
291
|
throw new Error("Cache mutex acquisition ended unexpectedly.");
|
|
@@ -694,7 +669,7 @@ class CodexQuotaStatus {
|
|
|
694
669
|
} finally {
|
|
695
670
|
if (owner) {
|
|
696
671
|
this.stopHeartbeat(slot, owner);
|
|
697
|
-
await this.release(slot, owner, session.signal.aborted ?
|
|
672
|
+
await this.release(slot, owner, session.signal.aborted ? AbortSignal.timeout(CLEANUP_TIMEOUT_MS) : session.signal).catch(() => undefined);
|
|
698
673
|
}
|
|
699
674
|
}
|
|
700
675
|
}
|