@monotykamary/pi-better-openai 0.1.42 → 0.1.43
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/index.ts +21 -25
- package/package.json +1 -1
- package/src/reset-controller.ts +97 -0
package/index.ts
CHANGED
|
@@ -62,8 +62,6 @@ import {
|
|
|
62
62
|
formatUsageSnapshot,
|
|
63
63
|
parseUsageSnapshot,
|
|
64
64
|
readCodexAuth,
|
|
65
|
-
requestCodexUsage,
|
|
66
|
-
type UsageSnapshot,
|
|
67
65
|
} from "./src/usage.ts";
|
|
68
66
|
import {
|
|
69
67
|
buildBankedResetConfirmation,
|
|
@@ -71,10 +69,9 @@ import {
|
|
|
71
69
|
formatBankedResetChoice,
|
|
72
70
|
formatConsumeOutcome,
|
|
73
71
|
newRedeemRequestId,
|
|
74
|
-
requestBankedResetCredits,
|
|
75
72
|
selectBankedResetCredit,
|
|
76
|
-
type BankedResetCredits,
|
|
77
73
|
} from "./src/resets.ts";
|
|
74
|
+
import { ResetController } from "./src/reset-controller.ts";
|
|
78
75
|
import { registerOpenAIImage, _imageTest } from "./src/image.ts";
|
|
79
76
|
import { registerOpenAIWebSearch, _websearchTest } from "./src/websearch.ts";
|
|
80
77
|
import { registerOpenAILive } from "./src/live/index.ts";
|
|
@@ -90,7 +87,7 @@ import {
|
|
|
90
87
|
_petsTest,
|
|
91
88
|
} from "./src/pets.ts";
|
|
92
89
|
import { FastController, modelList, supportsFast } from "./src/fast-controller.ts";
|
|
93
|
-
import { UsageController } from "./src/usage-controller.ts";
|
|
90
|
+
import { isOpenAISubscriptionModel, UsageController } from "./src/usage-controller.ts";
|
|
94
91
|
import { PetFooterController } from "./src/pet-footer-controller.ts";
|
|
95
92
|
import {
|
|
96
93
|
abbreviateHomePath,
|
|
@@ -247,6 +244,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
247
244
|
let cachedSessionNameLeafId: string | null | undefined;
|
|
248
245
|
let cachedSessionName: string | undefined;
|
|
249
246
|
const usageController = new UsageController(config, updateFooter);
|
|
247
|
+
const resetController = new ResetController();
|
|
250
248
|
const petController = new PetFooterController(config, updateFooter, () => footerInstalled);
|
|
251
249
|
let multiproviderService: MultiproviderService | undefined;
|
|
252
250
|
let unsubscribeMultiprovider: (() => void) | undefined;
|
|
@@ -265,6 +263,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
265
263
|
setActiveMultiproviderService(value);
|
|
266
264
|
unsubscribeMultiprovider = value.onActiveAccountChanged(CODEX_PROVIDER_ID, (event) => {
|
|
267
265
|
void usageController.refresh(event.ctx, undefined, { force: true });
|
|
266
|
+
void resetController.refresh(event.ctx, { force: true }).catch(() => {});
|
|
268
267
|
updateFooter(event.ctx);
|
|
269
268
|
});
|
|
270
269
|
const ctx = multiproviderRefreshCtx;
|
|
@@ -402,27 +401,18 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
402
401
|
ctx.ui.notify("/openai-resets requires an interactive TUI session.", "warning");
|
|
403
402
|
return;
|
|
404
403
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
]);
|
|
414
|
-
credits = creditResult;
|
|
415
|
-
snapshot = usageResult ? parseUsageSnapshot(usageResult, ctx.model?.id) : undefined;
|
|
416
|
-
} catch (error) {
|
|
417
|
-
ctx.ui.notify(
|
|
418
|
-
`Banked reset lookup failed: ${sanitizeDiagnosticError(
|
|
419
|
-
error instanceof Error ? error.message : String(error),
|
|
420
|
-
)}`,
|
|
421
|
-
"error",
|
|
422
|
-
);
|
|
404
|
+
let credits = resetController.snapshot?.credits;
|
|
405
|
+
if (!credits) {
|
|
406
|
+
await resetController.refresh(ctx, { force: true }).catch(() => {});
|
|
407
|
+
credits = resetController.snapshot?.credits;
|
|
408
|
+
}
|
|
409
|
+
if (!credits) {
|
|
410
|
+
const reason = resetController.lastError;
|
|
411
|
+
ctx.ui.notify(`Banked reset lookup failed${reason ? `: ${reason}` : "."}`, "error");
|
|
423
412
|
return;
|
|
424
413
|
}
|
|
425
|
-
|
|
414
|
+
void resetController.refresh(ctx).catch(() => {});
|
|
415
|
+
if (credits.availableCount <= 0) {
|
|
426
416
|
ctx.ui.notify("No banked Codex resets are available for this account.", "info");
|
|
427
417
|
return;
|
|
428
418
|
}
|
|
@@ -440,17 +430,20 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
440
430
|
const confirmation = buildBankedResetConfirmation({
|
|
441
431
|
credit: selected,
|
|
442
432
|
availableCount: credits.availableCount,
|
|
443
|
-
snapshot,
|
|
433
|
+
snapshot: usageController.snapshot,
|
|
444
434
|
});
|
|
445
435
|
if (!(await ctx.ui.confirm(confirmation.title, confirmation.message))) {
|
|
446
436
|
ctx.ui.notify("Banked reset redemption cancelled.", "info");
|
|
447
437
|
return;
|
|
448
438
|
}
|
|
449
439
|
try {
|
|
440
|
+
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
441
|
+
const signal = ctx.signal ? AbortSignal.any([ctx.signal, timeoutSignal]) : timeoutSignal;
|
|
450
442
|
const result = await consumeBankedReset(ctx, selected?.id, newRedeemRequestId(), signal);
|
|
451
443
|
const outcome = formatConsumeOutcome(result);
|
|
452
444
|
ctx.ui.notify(outcome.message, outcome.level);
|
|
453
445
|
void usageController.refresh(ctx, ctx.model?.id, { force: true });
|
|
446
|
+
void resetController.refresh(ctx, { force: true }).catch(() => {});
|
|
454
447
|
updateFooter(ctx);
|
|
455
448
|
} catch (error) {
|
|
456
449
|
ctx.ui.notify(
|
|
@@ -1393,6 +1386,8 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1393
1386
|
updateFooter(ctx);
|
|
1394
1387
|
if (hasTerminalUI(ctx) && nextConfig.pets.enabled) void petController.refresh(ctx, nextConfig);
|
|
1395
1388
|
usageController.start(ctx);
|
|
1389
|
+
if (nextConfig.usage.enabled && isOpenAISubscriptionModel(ctx, nextConfig))
|
|
1390
|
+
resetController.start(ctx);
|
|
1396
1391
|
if (fastController.active) ctx.ui.notify(fastController.stateText(ctx, nextConfig), "info");
|
|
1397
1392
|
});
|
|
1398
1393
|
|
|
@@ -1469,6 +1464,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
|
1469
1464
|
invalidateSessionName();
|
|
1470
1465
|
multiproviderRefreshCtx = undefined;
|
|
1471
1466
|
usageController.shutdown();
|
|
1467
|
+
resetController.stop();
|
|
1472
1468
|
petController.shutdown();
|
|
1473
1469
|
});
|
|
1474
1470
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { sanitizeDiagnosticError } from "./format.ts";
|
|
3
|
+
import { type BankedResetCredits, requestBankedResetCredits } from "./resets.ts";
|
|
4
|
+
|
|
5
|
+
export const BANKED_RESET_CACHE_TTL_MS = 5 * 60_000;
|
|
6
|
+
const BANKED_RESET_REQUEST_TIMEOUT_MS = 10_000;
|
|
7
|
+
|
|
8
|
+
export type BankedResetCache = {
|
|
9
|
+
credits: BankedResetCredits;
|
|
10
|
+
updatedAt: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Keeps the banked-reset credit list warm so /openai-resets opens without
|
|
14
|
+
// paying the credential-resolution + wham round trip. The cache is prefetched
|
|
15
|
+
// at session start, refreshed on a TTL, and served even when stale (a stale
|
|
16
|
+
// open still kicks off a background refresh; the backend re-validates every
|
|
17
|
+
// redemption server-side, so a stale offer can never be consumed twice).
|
|
18
|
+
export class ResetController {
|
|
19
|
+
private cache: BankedResetCache | undefined;
|
|
20
|
+
private error: string | undefined;
|
|
21
|
+
private lastFetchAt: number | undefined;
|
|
22
|
+
private inFlight: Promise<void> | undefined;
|
|
23
|
+
private timer: ReturnType<typeof setInterval> | undefined;
|
|
24
|
+
private sessionSignal: AbortSignal | undefined;
|
|
25
|
+
private sessionAbortHandler: (() => void) | undefined;
|
|
26
|
+
|
|
27
|
+
get snapshot(): BankedResetCache | undefined {
|
|
28
|
+
return this.cache;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get lastError(): string | undefined {
|
|
32
|
+
return this.error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
isFresh(now = Date.now()): boolean {
|
|
36
|
+
return this.cache !== undefined && now - this.cache.updatedAt < BANKED_RESET_CACHE_TTL_MS;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
refresh(ctx: ExtensionContext, options?: { force?: boolean }): Promise<void> {
|
|
40
|
+
if (this.inFlight) return this.inFlight;
|
|
41
|
+
if (
|
|
42
|
+
!options?.force &&
|
|
43
|
+
this.lastFetchAt !== undefined &&
|
|
44
|
+
Date.now() - this.lastFetchAt < BANKED_RESET_CACHE_TTL_MS
|
|
45
|
+
)
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
this.lastFetchAt = Date.now();
|
|
48
|
+
const timeoutSignal = AbortSignal.timeout(BANKED_RESET_REQUEST_TIMEOUT_MS);
|
|
49
|
+
const signal = ctx.signal ? AbortSignal.any([ctx.signal, timeoutSignal]) : timeoutSignal;
|
|
50
|
+
const task = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
const credits = await requestBankedResetCredits(ctx, signal);
|
|
53
|
+
if (credits) {
|
|
54
|
+
this.cache = { credits, updatedAt: Date.now() };
|
|
55
|
+
this.error = undefined;
|
|
56
|
+
} else {
|
|
57
|
+
this.error = "OpenAI Codex credentials unavailable.";
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.error = sanitizeDiagnosticError(
|
|
61
|
+
error instanceof Error ? error.message : String(error),
|
|
62
|
+
);
|
|
63
|
+
} finally {
|
|
64
|
+
this.inFlight = undefined;
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
this.inFlight = task;
|
|
68
|
+
return task;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
start(ctx: ExtensionContext): void {
|
|
72
|
+
this.stop();
|
|
73
|
+
if (ctx.signal?.aborted) return;
|
|
74
|
+
this.sessionSignal = ctx.signal;
|
|
75
|
+
this.sessionAbortHandler = () => this.stop();
|
|
76
|
+
ctx.signal?.addEventListener("abort", this.sessionAbortHandler, { once: true });
|
|
77
|
+
void this.refresh(ctx).catch(() => {});
|
|
78
|
+
this.timer = setInterval(() => {
|
|
79
|
+
if (ctx.signal?.aborted) {
|
|
80
|
+
this.stop();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
void this.refresh(ctx).catch(() => {});
|
|
84
|
+
}, BANKED_RESET_CACHE_TTL_MS);
|
|
85
|
+
this.timer.unref?.();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
stop(): void {
|
|
89
|
+
if (this.timer) clearInterval(this.timer);
|
|
90
|
+
this.timer = undefined;
|
|
91
|
+
if (this.sessionSignal && this.sessionAbortHandler) {
|
|
92
|
+
this.sessionSignal.removeEventListener("abort", this.sessionAbortHandler);
|
|
93
|
+
}
|
|
94
|
+
this.sessionSignal = undefined;
|
|
95
|
+
this.sessionAbortHandler = undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|