@henryqw/pi-multi-codex 3.1.0 → 3.1.1
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/extensions/multi-codex.ts +35 -50
- package/package.json +1 -1
|
@@ -260,7 +260,6 @@ async function loadState(signal?: AbortSignal): Promise<UsageState> {
|
|
|
260
260
|
|
|
261
261
|
async function saveState(state: UsageState, signal?: AbortSignal): Promise<void> {
|
|
262
262
|
signal?.throwIfAborted();
|
|
263
|
-
if (state.untrusted) return;
|
|
264
263
|
const file = cachePath();
|
|
265
264
|
const data = `${JSON.stringify({
|
|
266
265
|
slots: [...state.slots.values()].sort((a, b) => a.slot - b.slot),
|
|
@@ -272,11 +271,11 @@ async function saveState(state: UsageState, signal?: AbortSignal): Promise<void>
|
|
|
272
271
|
async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
273
272
|
signal?.throwIfAborted();
|
|
274
273
|
await mkdir(extensionConfigDir("pi-multi-codex"), { recursive: true, mode: 0o700 });
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
const compromised = new AbortController();
|
|
275
|
+
let release: () => Promise<void>;
|
|
276
|
+
for (;;) {
|
|
277
277
|
signal?.throwIfAborted();
|
|
278
278
|
try {
|
|
279
|
-
const compromised = new AbortController();
|
|
280
279
|
release = await lock(cachePath(), {
|
|
281
280
|
lockfilePath: cacheMutexPath(),
|
|
282
281
|
realpath: false,
|
|
@@ -285,19 +284,19 @@ async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>,
|
|
|
285
284
|
retries: 0,
|
|
286
285
|
onCompromised: (error) => compromised.abort(error),
|
|
287
286
|
});
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
operationSignal.throwIfAborted();
|
|
291
|
-
return await raceWithSignal(operation(operationSignal), operationSignal);
|
|
292
|
-
} finally {
|
|
293
|
-
await release().catch(() => undefined);
|
|
294
|
-
}
|
|
287
|
+
break;
|
|
295
288
|
} catch (error) {
|
|
296
289
|
if (!(error && typeof error === "object" && "code" in error && String(error.code) === "ELOCKED")) throw error;
|
|
297
290
|
await delay(CACHE_MUTEX_RETRY_MS, undefined, { signal, ref: false });
|
|
298
291
|
}
|
|
299
292
|
}
|
|
300
|
-
|
|
293
|
+
const operationSignal = signal ? AbortSignal.any([signal, compromised.signal]) : compromised.signal;
|
|
294
|
+
try {
|
|
295
|
+
operationSignal.throwIfAborted();
|
|
296
|
+
return await raceWithSignal(operation(operationSignal), operationSignal);
|
|
297
|
+
} finally {
|
|
298
|
+
await release().catch(() => undefined);
|
|
299
|
+
}
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
function isMeasured(snapshot: UsageSnapshot | undefined): snapshot is MeasuredSnapshot {
|
|
@@ -434,15 +433,10 @@ class CodexQuotaStatus {
|
|
|
434
433
|
|
|
435
434
|
private change<T>(change: (state: UsageState) => { value: T; write: boolean }, signal?: AbortSignal): Promise<T | undefined> {
|
|
436
435
|
const operation = this.writes.then(() => withCacheMutex(async (operationSignal) => {
|
|
437
|
-
operationSignal.throwIfAborted();
|
|
438
436
|
const state = await loadState(operationSignal);
|
|
439
|
-
operationSignal.throwIfAborted();
|
|
440
437
|
if (state.untrusted) return undefined;
|
|
441
438
|
const result = change(state);
|
|
442
|
-
if (result.write)
|
|
443
|
-
operationSignal.throwIfAborted();
|
|
444
|
-
await saveState(state, operationSignal);
|
|
445
|
-
}
|
|
439
|
+
if (result.write) await saveState(state, operationSignal);
|
|
446
440
|
return result.value;
|
|
447
441
|
}, signal));
|
|
448
442
|
this.writes = operation.then(() => undefined, () => undefined);
|
|
@@ -468,7 +462,6 @@ class CodexQuotaStatus {
|
|
|
468
462
|
const refresh = this.refreshContext;
|
|
469
463
|
if (!refresh) return;
|
|
470
464
|
void this.refreshDue(refresh)
|
|
471
|
-
.catch(() => undefined)
|
|
472
465
|
.then(() => {
|
|
473
466
|
if (this.refreshContext === refresh) this.scheduleRefresh();
|
|
474
467
|
});
|
|
@@ -507,20 +500,21 @@ class CodexQuotaStatus {
|
|
|
507
500
|
this.timer = undefined;
|
|
508
501
|
if (this.refreshContext === refresh) this.requestRefresh();
|
|
509
502
|
}, delay);
|
|
510
|
-
this.timer.unref
|
|
503
|
+
this.timer.unref();
|
|
511
504
|
}
|
|
512
505
|
|
|
513
506
|
private async refreshDue(refresh: QuotaRefresh): Promise<void> {
|
|
514
507
|
const credentials = readCodexCredentials();
|
|
515
508
|
const state = await this.state();
|
|
516
|
-
|
|
517
|
-
|
|
509
|
+
const now = Date.now();
|
|
510
|
+
for (const [slot, credential] of credentials) {
|
|
511
|
+
if (this.refreshContext !== refresh || this.active.has(slot)) continue;
|
|
518
512
|
const identity = identityFor(credential);
|
|
519
|
-
if (!identity)
|
|
513
|
+
if (!identity) continue;
|
|
520
514
|
const snapshot = state.slots.get(slot);
|
|
521
|
-
if (isFresh(snapshot, identity,
|
|
515
|
+
if (isFresh(snapshot, identity, now) || checkedRecently(snapshot, identity, now)) continue;
|
|
522
516
|
this.launch(slot, identity, refresh);
|
|
523
|
-
}
|
|
517
|
+
}
|
|
524
518
|
}
|
|
525
519
|
|
|
526
520
|
private launch(slot: number, identity: SlotIdentity, refresh: QuotaRefresh): void {
|
|
@@ -569,7 +563,7 @@ class CodexQuotaStatus {
|
|
|
569
563
|
if (!kept) this.stopHeartbeat(slot, owner);
|
|
570
564
|
}).catch(() => this.stopHeartbeat(slot, owner));
|
|
571
565
|
}, HEARTBEAT_MS);
|
|
572
|
-
timer.unref
|
|
566
|
+
timer.unref();
|
|
573
567
|
this.heartbeats.set(slot, { owner, timer });
|
|
574
568
|
}
|
|
575
569
|
|
|
@@ -662,7 +656,7 @@ class CodexQuotaStatus {
|
|
|
662
656
|
signal: fetchSignal,
|
|
663
657
|
});
|
|
664
658
|
if (!response.ok) throw new Error(`Codex usage request failed (${response.status}).`);
|
|
665
|
-
const usage = parseCodexUsage(await
|
|
659
|
+
const usage = parseCodexUsage(await response.json());
|
|
666
660
|
if (!usage) throw new Error("Codex usage response has no usable seven-day window.");
|
|
667
661
|
if (!this.identityStillCurrent(slot, identity.accountHash) || !(await this.stillOwn(slot, owner, identity.accountHash))) {
|
|
668
662
|
discard = true;
|
|
@@ -703,10 +697,6 @@ class CodexQuotaStatus {
|
|
|
703
697
|
}
|
|
704
698
|
}
|
|
705
699
|
|
|
706
|
-
function nativeModel(model: CodexModel): CodexModel {
|
|
707
|
-
return model.provider === NATIVE_PROVIDER_ID ? model : { ...model, provider: NATIVE_PROVIDER_ID };
|
|
708
|
-
}
|
|
709
|
-
|
|
710
700
|
function aliasModel(model: CodexModel, provider: string): CodexModel {
|
|
711
701
|
return model.provider === provider ? model : { ...model, provider };
|
|
712
702
|
}
|
|
@@ -717,10 +707,7 @@ function nativeContext(context: TranscriptContext, alias: string): TranscriptCon
|
|
|
717
707
|
messages: context.messages.map((message) => {
|
|
718
708
|
if (message.role !== "assistant") return message;
|
|
719
709
|
const provider = message.provider === alias ? NATIVE_PROVIDER_ID : message.provider === NATIVE_PROVIDER_ID ? alias : message.provider;
|
|
720
|
-
|
|
721
|
-
const rewritten = { ...message, provider };
|
|
722
|
-
if (message.deferred) rewritten.deferred = { ...message.deferred, provider };
|
|
723
|
-
return rewritten;
|
|
710
|
+
return provider === message.provider ? message : aliasMessage(message, provider);
|
|
724
711
|
}),
|
|
725
712
|
};
|
|
726
713
|
}
|
|
@@ -788,28 +775,28 @@ export function createCodexAliasProvider(native: CodexProvider, slot: number): C
|
|
|
788
775
|
auth: native.auth,
|
|
789
776
|
getModels: () => models,
|
|
790
777
|
stream: (model, context, options?: ApiStreamOptions<"openai-codex-responses">) =>
|
|
791
|
-
aliasStream(native.stream(
|
|
778
|
+
aliasStream(native.stream(aliasModel(model, NATIVE_PROVIDER_ID), nativeContext(context, providerId), options), model, providerId),
|
|
792
779
|
streamSimple: (model, context, options?: SimpleStreamOptions) =>
|
|
793
|
-
aliasStream(native.streamSimple(
|
|
780
|
+
aliasStream(native.streamSimple(aliasModel(model, NATIVE_PROVIDER_ID), nativeContext(context, providerId), options), model, providerId),
|
|
794
781
|
};
|
|
795
782
|
|
|
796
783
|
if (native.filterModels) {
|
|
797
784
|
provider.filterModels = (available, credential) =>
|
|
798
785
|
native
|
|
799
|
-
.filterModels!(available.map(
|
|
786
|
+
.filterModels!(available.map((model) => aliasModel(model, NATIVE_PROVIDER_ID)), credential)
|
|
800
787
|
.map((model) => aliasModel(model, providerId));
|
|
801
788
|
}
|
|
802
789
|
if (native.fetchDeferred) {
|
|
803
790
|
provider.fetchDeferred = (model, handle, options?: DeferredFetchOptions) =>
|
|
804
791
|
aliasStream(
|
|
805
|
-
native.fetchDeferred!(
|
|
792
|
+
native.fetchDeferred!(aliasModel(model, NATIVE_PROVIDER_ID), { ...handle, provider: NATIVE_PROVIDER_ID }, options),
|
|
806
793
|
model,
|
|
807
794
|
providerId,
|
|
808
795
|
);
|
|
809
796
|
}
|
|
810
797
|
if (native.cancelDeferred) {
|
|
811
798
|
provider.cancelDeferred = (model, handle, options?: DeferredCancelOptions) =>
|
|
812
|
-
native.cancelDeferred!(
|
|
799
|
+
native.cancelDeferred!(aliasModel(model, NATIVE_PROVIDER_ID), { ...handle, provider: NATIVE_PROVIDER_ID }, options);
|
|
813
800
|
}
|
|
814
801
|
return provider;
|
|
815
802
|
}
|
|
@@ -824,9 +811,9 @@ function isManagedModel(model: Model<any> | undefined): model is CodexModel {
|
|
|
824
811
|
}
|
|
825
812
|
|
|
826
813
|
function sessionHasAgentWork(ctx: ExtensionContext): boolean {
|
|
827
|
-
return ctx.sessionManager
|
|
814
|
+
return ctx.sessionManager.getBranch().some((entry) =>
|
|
828
815
|
entry.type === "message" || (entry.type === "custom" && entry.customType === AGENT_STARTED_ENTRY),
|
|
829
|
-
)
|
|
816
|
+
);
|
|
830
817
|
}
|
|
831
818
|
|
|
832
819
|
export default function multiCodex(pi: ExtensionAPI): void {
|
|
@@ -863,7 +850,7 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
863
850
|
};
|
|
864
851
|
|
|
865
852
|
const allowsModel = (ctx: ExtensionContext, model: Model<any>, slot: number): boolean => {
|
|
866
|
-
const scopedModels = ctx.scopedModels
|
|
853
|
+
const scopedModels = ctx.scopedModels;
|
|
867
854
|
return scopedModels.length === 0 || scopedModels.some(({ model: scoped }) => scoped.provider === providerForSlot(slot) && scoped.id === model.id);
|
|
868
855
|
};
|
|
869
856
|
|
|
@@ -919,17 +906,17 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
919
906
|
const now = Date.now();
|
|
920
907
|
if (isFiveHourLimited(snapshot, now)) {
|
|
921
908
|
const text = `${prefix} · 5h limit · ${formatDuration(snapshot.limitedUntil! - now)}`;
|
|
922
|
-
return ctx.ui.theme
|
|
909
|
+
return ctx.ui.theme.fg("error", text);
|
|
923
910
|
}
|
|
924
911
|
if (!isFresh(snapshot, identity, now)) return `${prefix} · stale`;
|
|
925
912
|
const display = displayedReset(snapshot, now);
|
|
926
913
|
const text = `${prefix} · ${formatPercent(snapshot.remaining)}% · ${display.label} ${formatDuration(display.time - now)}`;
|
|
927
914
|
const color = snapshot.remaining >= 50 ? "success" : snapshot.remaining >= 25 ? "warning" : "error";
|
|
928
|
-
return ctx.ui.theme
|
|
915
|
+
return ctx.ui.theme.fg(color, text);
|
|
929
916
|
};
|
|
930
917
|
|
|
931
918
|
const updateFooter = (ctx: ExtensionContext | undefined = sessionContext): void => {
|
|
932
|
-
if (ctx) ctx.ui.setStatus
|
|
919
|
+
if (ctx) ctx.ui.setStatus("pi-multi-codex", footerText(ctx));
|
|
933
920
|
};
|
|
934
921
|
|
|
935
922
|
const setModelAutomatically = async (model: Model<any>): Promise<boolean> => {
|
|
@@ -1099,10 +1086,8 @@ export default function multiCodex(pi: ExtensionAPI): void {
|
|
|
1099
1086
|
return;
|
|
1100
1087
|
}
|
|
1101
1088
|
|
|
1102
|
-
const authenticated = [...syncSlots()]
|
|
1103
|
-
|
|
1104
|
-
.sort((left, right) => left - right);
|
|
1105
|
-
const scopedModels = ctx.scopedModels ?? [];
|
|
1089
|
+
const authenticated = [...syncSlots()].sort((left, right) => left - right);
|
|
1090
|
+
const scopedModels = ctx.scopedModels;
|
|
1106
1091
|
const slots = authenticated.filter((slot) => allowsModel(ctx, model, slot));
|
|
1107
1092
|
const currentSlot = slotForProvider(model.provider);
|
|
1108
1093
|
if (slots.length === 0 || (scopedModels.length > 0 && authenticated.some((slot) => !allowsModel(ctx, model, slot)) && slots.every((slot) => slot === currentSlot))) {
|