oc-codex-multi-auth 6.14.3 → 6.15.0
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 +46 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +30 -5
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +100 -28
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/codex-usage.d.ts +13 -1
- package/dist/lib/codex-usage.d.ts.map +1 -1
- package/dist/lib/codex-usage.js +27 -8
- package/dist/lib/codex-usage.js.map +1 -1
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +28 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/desktop-notifications.d.ts +14 -0
- package/dist/lib/desktop-notifications.d.ts.map +1 -0
- package/dist/lib/desktop-notifications.js +59 -0
- package/dist/lib/desktop-notifications.js.map +1 -0
- package/dist/lib/quota-notification-state.d.ts +25 -0
- package/dist/lib/quota-notification-state.d.ts.map +1 -0
- package/dist/lib/quota-notification-state.js +94 -0
- package/dist/lib/quota-notification-state.js.map +1 -0
- package/dist/lib/quota-notifications.d.ts +64 -0
- package/dist/lib/quota-notifications.d.ts.map +1 -0
- package/dist/lib/quota-notifications.js +371 -0
- package/dist/lib/quota-notifications.js.map +1 -0
- package/dist/lib/schemas.d.ts +6 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +6 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/types.d.ts +0 -4
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { logDebug, logWarn } from "./logger.js";
|
|
3
|
+
const DELIVERY_TIMEOUT_MS = 10_000;
|
|
4
|
+
const MACOS_NOTIFICATION_SCRIPT = `
|
|
5
|
+
function run(argv) {
|
|
6
|
+
const app = Application.currentApplication();
|
|
7
|
+
app.includeStandardAdditions = true;
|
|
8
|
+
app.displayNotification(argv[1], { withTitle: argv[0] });
|
|
9
|
+
}
|
|
10
|
+
`.trim();
|
|
11
|
+
const executeFile = (file, args, options, callback) => {
|
|
12
|
+
return execFile(file, args, options, (error) => callback(error));
|
|
13
|
+
};
|
|
14
|
+
function getErrorCode(error) {
|
|
15
|
+
if (typeof error !== "object" || error === null)
|
|
16
|
+
return "unknown";
|
|
17
|
+
const code = error.code;
|
|
18
|
+
return typeof code === "string" || typeof code === "number" ? String(code) : "unknown";
|
|
19
|
+
}
|
|
20
|
+
export function isDesktopNotificationSupported(platform = process.platform) {
|
|
21
|
+
return platform === "darwin";
|
|
22
|
+
}
|
|
23
|
+
export function createDesktopNotifier(options) {
|
|
24
|
+
const platform = options?.platform ?? process.platform;
|
|
25
|
+
const run = options?.executeFile ?? executeFile;
|
|
26
|
+
let warningLogged = false;
|
|
27
|
+
const warnOnce = (message) => {
|
|
28
|
+
if (warningLogged)
|
|
29
|
+
return;
|
|
30
|
+
warningLogged = true;
|
|
31
|
+
logWarn(message);
|
|
32
|
+
};
|
|
33
|
+
return async (title, message) => {
|
|
34
|
+
if (!isDesktopNotificationSupported(platform)) {
|
|
35
|
+
warnOnce("Desktop quota notifications are supported only on macOS.");
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return await new Promise((resolve) => {
|
|
39
|
+
try {
|
|
40
|
+
run("/usr/bin/osascript", ["-l", "JavaScript", "-e", MACOS_NOTIFICATION_SCRIPT, title, message], { timeout: DELIVERY_TIMEOUT_MS, windowsHide: true }, (error) => {
|
|
41
|
+
if (!error) {
|
|
42
|
+
resolve(true);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
logDebug(`Desktop quota notification failed with code ${getErrorCode(error)}`);
|
|
46
|
+
warnOnce("Desktop quota notification delivery failed on macOS.");
|
|
47
|
+
resolve(false);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logDebug(`Desktop quota notification threw with code ${getErrorCode(error)}`);
|
|
52
|
+
warnOnce("Desktop quota notification delivery failed on macOS.");
|
|
53
|
+
resolve(false);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export const sendDesktopNotification = createDesktopNotifier();
|
|
59
|
+
//# sourceMappingURL=desktop-notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop-notifications.js","sourceRoot":"","sources":["../../lib/desktop-notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,yBAAyB,GAAG;;;;;;CAMjC,CAAC,IAAI,EAAE,CAAC;AAYT,MAAM,WAAW,GAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAClE,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC;AAEF,SAAS,YAAY,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClE,MAAM,IAAI,GAAI,KAAiC,CAAC,IAAI,CAAC;IACrD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC7C,WAA4B,OAAO,CAAC,QAAQ;IAE5C,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAGrC;IACA,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACvD,MAAM,GAAG,GAAG,OAAO,EAAE,WAAW,IAAI,WAAW,CAAC;IAChD,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAQ,EAAE;QAC1C,IAAI,aAAa;YAAE,OAAO;QAC1B,aAAa,GAAG,IAAI,CAAC;QACrB,OAAO,CAAC,OAAO,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC/B,IAAI,CAAC,8BAA8B,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,QAAQ,CAAC,0DAA0D,CAAC,CAAC;YACrE,OAAO,KAAK,CAAC;QACd,CAAC;QAED,OAAO,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;YAC7C,IAAI,CAAC;gBACJ,GAAG,CACF,oBAAoB,EACpB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,CAAC,EACrE,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAE,EACnD,CAAC,KAAK,EAAE,EAAE;oBACT,IAAI,CAAC,KAAK,EAAE,CAAC;wBACZ,OAAO,CAAC,IAAI,CAAC,CAAC;wBACd,OAAO;oBACR,CAAC;oBACD,QAAQ,CAAC,+CAA+C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBAC/E,QAAQ,CAAC,sDAAsD,CAAC,CAAC;oBACjE,OAAO,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC,CACD,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,8CAA8C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAC9E,QAAQ,CAAC,sDAAsD,CAAC,CAAC;gBACjE,OAAO,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface QuotaWindowNotificationState {
|
|
2
|
+
/**
|
|
3
|
+
* The remaining-percent reading this window was last seen at. It is the
|
|
4
|
+
* only per-window state: which threshold is "armed" is derived from it, so
|
|
5
|
+
* storing that separately would let a corrupt extra field invalidate an
|
|
6
|
+
* otherwise usable file.
|
|
7
|
+
*/
|
|
8
|
+
lastPercent?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface QuotaNotificationState {
|
|
11
|
+
fiveHour: QuotaWindowNotificationState;
|
|
12
|
+
weekly: QuotaWindowNotificationState;
|
|
13
|
+
lastDeliveredAt?: number;
|
|
14
|
+
updatedAt: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function getQuotaNotificationStatePath(activeProjectStoragePath: string): string;
|
|
17
|
+
export declare function readQuotaNotificationState(statePath: string): Promise<QuotaNotificationState | undefined>;
|
|
18
|
+
export declare function updateQuotaNotificationState<T>(statePath: string, update: (state: QuotaNotificationState | undefined) => Promise<{
|
|
19
|
+
state: QuotaNotificationState;
|
|
20
|
+
result: T;
|
|
21
|
+
}> | {
|
|
22
|
+
state: QuotaNotificationState;
|
|
23
|
+
result: T;
|
|
24
|
+
}): Promise<T>;
|
|
25
|
+
//# sourceMappingURL=quota-notification-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-notification-state.d.ts","sourceRoot":"","sources":["../../lib/quota-notification-state.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,MAAM,EAAE,4BAA4B,CAAC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,6BAA6B,CAAC,wBAAwB,EAAE,MAAM,GAAG,MAAM,CAEtF;AAsCD,wBAAsB,0BAA0B,CAC/C,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAS7C;AAYD,wBAAsB,4BAA4B,CAAC,CAAC,EACnD,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,CAAC,KAAK,EAAE,sBAAsB,GAAG,SAAS,KAAK,OAAO,CAAC;IAC9D,KAAK,EAAE,sBAAsB,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC;CACV,CAAC,GAAG;IACJ,KAAK,EAAE,sBAAsB,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC;CACV,GACC,OAAO,CAAC,CAAC,CAAC,CA8BZ"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { lock } from "proper-lockfile";
|
|
4
|
+
import { renameWithWindowsRetry } from "./storage/atomic-write.js";
|
|
5
|
+
import { logDebug, logWarn } from "./logger.js";
|
|
6
|
+
export function getQuotaNotificationStatePath(activeProjectStoragePath) {
|
|
7
|
+
return join(dirname(activeProjectStoragePath), "oc-codex-multi-auth-quota-notifications.json");
|
|
8
|
+
}
|
|
9
|
+
function parseWindowState(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null)
|
|
11
|
+
return undefined;
|
|
12
|
+
const record = value;
|
|
13
|
+
const lastPercent = record.lastPercent;
|
|
14
|
+
if (lastPercent !== undefined &&
|
|
15
|
+
(typeof lastPercent !== "number" ||
|
|
16
|
+
!Number.isFinite(lastPercent) ||
|
|
17
|
+
lastPercent < 0 ||
|
|
18
|
+
lastPercent > 100)) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// Older files may carry an `activeThreshold` key. It is ignored rather than
|
|
22
|
+
// rejected so an upgrade does not discard a usable state file.
|
|
23
|
+
return { lastPercent };
|
|
24
|
+
}
|
|
25
|
+
function parseState(value) {
|
|
26
|
+
if (typeof value !== "object" || value === null)
|
|
27
|
+
return undefined;
|
|
28
|
+
const record = value;
|
|
29
|
+
const fiveHour = parseWindowState(record.fiveHour);
|
|
30
|
+
const weekly = parseWindowState(record.weekly);
|
|
31
|
+
const lastDeliveredAt = record.lastDeliveredAt;
|
|
32
|
+
if (!fiveHour || !weekly || typeof record.updatedAt !== "number" || !Number.isFinite(record.updatedAt)) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (lastDeliveredAt !== undefined &&
|
|
36
|
+
(typeof lastDeliveredAt !== "number" || !Number.isFinite(lastDeliveredAt) || lastDeliveredAt < 0)) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return { fiveHour, weekly, lastDeliveredAt, updatedAt: record.updatedAt };
|
|
40
|
+
}
|
|
41
|
+
export async function readQuotaNotificationState(statePath) {
|
|
42
|
+
try {
|
|
43
|
+
return parseState(JSON.parse(await fs.readFile(statePath, "utf-8")));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error.code !== "ENOENT") {
|
|
47
|
+
logWarn(`Failed to read quota notification state from ${statePath}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function writeState(statePath, state) {
|
|
53
|
+
const tempPath = `${statePath}.${process.pid}.${Date.now()}.tmp`;
|
|
54
|
+
try {
|
|
55
|
+
await fs.writeFile(tempPath, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
56
|
+
await renameWithWindowsRetry(tempPath, statePath);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function updateQuotaNotificationState(statePath, update) {
|
|
63
|
+
await fs.mkdir(dirname(statePath), { recursive: true, mode: 0o700 });
|
|
64
|
+
let compromised;
|
|
65
|
+
const release = await lock(statePath, {
|
|
66
|
+
realpath: false,
|
|
67
|
+
stale: 10_000,
|
|
68
|
+
update: 2_000,
|
|
69
|
+
retries: {
|
|
70
|
+
retries: 5,
|
|
71
|
+
factor: 1.5,
|
|
72
|
+
minTimeout: 50,
|
|
73
|
+
maxTimeout: 500,
|
|
74
|
+
randomize: true,
|
|
75
|
+
},
|
|
76
|
+
onCompromised: (error) => {
|
|
77
|
+
compromised = error;
|
|
78
|
+
logDebug(`Quota notification state lock compromised: ${error.message}`);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
const next = await update(await readQuotaNotificationState(statePath));
|
|
83
|
+
if (compromised)
|
|
84
|
+
throw compromised;
|
|
85
|
+
await writeState(statePath, next.state);
|
|
86
|
+
return next.result;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await release().catch((error) => {
|
|
90
|
+
logDebug(`Failed to release quota notification state lock: ${error.message}`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=quota-notification-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-notification-state.js","sourceRoot":"","sources":["../../lib/quota-notification-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type QuotaNotificationsConfig } from "./config.js";
|
|
2
|
+
import { type CodexUsageSummary } from "./codex-usage.js";
|
|
3
|
+
import { type DesktopNotifier } from "./desktop-notifications.js";
|
|
4
|
+
import { type QuotaNotificationState, type QuotaWindowNotificationState } from "./quota-notification-state.js";
|
|
5
|
+
import { type AccountMetadataV3, type AccountStorageV3 } from "./storage.js";
|
|
6
|
+
export type QuotaWindowName = "fiveHour" | "weekly";
|
|
7
|
+
export interface AggregatedQuotaWindow {
|
|
8
|
+
remainingPercent?: number;
|
|
9
|
+
resetAtMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AggregatedQuotaUsage {
|
|
12
|
+
fiveHour: AggregatedQuotaWindow;
|
|
13
|
+
weekly: AggregatedQuotaWindow;
|
|
14
|
+
}
|
|
15
|
+
export interface QuotaThresholdCrossing {
|
|
16
|
+
window: QuotaWindowName;
|
|
17
|
+
threshold: number;
|
|
18
|
+
remainingPercent: number;
|
|
19
|
+
resetAtMs?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface AccountQuotaSummary {
|
|
22
|
+
usage: CodexUsageSummary;
|
|
23
|
+
}
|
|
24
|
+
export interface QuotaMonitor {
|
|
25
|
+
start(): void;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
runNow(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
type MonitorDependencies = {
|
|
30
|
+
loadConfig: () => QuotaNotificationsConfig;
|
|
31
|
+
loadStorage: () => Promise<AccountStorageV3 | null>;
|
|
32
|
+
fetchSummary: (storage: AccountStorageV3, account: AccountMetadataV3 | undefined, onCredentialsPersisted: () => void) => Promise<AccountQuotaSummary | null>;
|
|
33
|
+
notify: DesktopNotifier;
|
|
34
|
+
notificationsSupported: () => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Called once per check when refreshing an account rotated its single-use
|
|
37
|
+
* refresh token to disk. The host wires this to its account-manager cache
|
|
38
|
+
* invalidation; see {@link fetchUsageForAccount} for why it is mandatory.
|
|
39
|
+
*/
|
|
40
|
+
onCredentialsPersisted: () => void;
|
|
41
|
+
now: () => number;
|
|
42
|
+
initialDelayMs: number;
|
|
43
|
+
};
|
|
44
|
+
export declare function aggregateQuotaUsage(summaries: readonly AccountQuotaSummary[], now?: number): AggregatedQuotaUsage;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether this observation crosses a threshold downward.
|
|
47
|
+
*
|
|
48
|
+
* A threshold fires when the window is at or below it *and* the previous
|
|
49
|
+
* observation was strictly above it, which is the "rises above it after a
|
|
50
|
+
* reset" contract in `docs/configuration.md`. The most severe matching
|
|
51
|
+
* threshold wins, and the selection does not assume `thresholds` is sorted.
|
|
52
|
+
*/
|
|
53
|
+
export declare function transitionQuotaWindow(previous: QuotaWindowNotificationState, current: AggregatedQuotaWindow, thresholds: readonly number[]): {
|
|
54
|
+
state: QuotaWindowNotificationState;
|
|
55
|
+
threshold?: number;
|
|
56
|
+
};
|
|
57
|
+
export declare function transitionQuotaState(previous: QuotaNotificationState | undefined, usage: AggregatedQuotaUsage, thresholds: readonly number[], now?: number): {
|
|
58
|
+
state: QuotaNotificationState;
|
|
59
|
+
crossings: QuotaThresholdCrossing[];
|
|
60
|
+
};
|
|
61
|
+
export declare function formatQuotaNotification(usage: AggregatedQuotaUsage): string;
|
|
62
|
+
export declare function createQuotaMonitor(overrides?: Partial<MonitorDependencies>): QuotaMonitor;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=quota-notifications.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-notifications.d.ts","sourceRoot":"","sources":["../../lib/quota-notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EASN,KAAK,iBAAiB,EACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGN,KAAK,eAAe,EACpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAGN,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAgB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAM3F,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,qBAAqB;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,MAAM,EAAE,qBAAqB,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;IAChB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,KAAK,mBAAmB,GAAG;IAC1B,UAAU,EAAE,MAAM,wBAAwB,CAAC;IAC3C,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACpD,YAAY,EAAE,CACb,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,iBAAiB,GAAG,SAAS,EACtC,sBAAsB,EAAE,MAAM,IAAI,KAC9B,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACzC,MAAM,EAAE,eAAe,CAAC;IACxB,sBAAsB,EAAE,MAAM,OAAO,CAAC;IACtC;;;;OAIG;IACH,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAAC;AAgDF,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,SAAS,mBAAmB,EAAE,EACzC,GAAG,SAAa,GACd,oBAAoB,CAKtB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,4BAA4B,EACtC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC3B;IAAE,KAAK,EAAE,4BAA4B,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAa7D;AAED,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,sBAAsB,GAAG,SAAS,EAC5C,KAAK,EAAE,oBAAoB,EAC3B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,GAAG,SAAa,GACd;IAAE,KAAK,EAAE,sBAAsB,CAAC;IAAC,SAAS,EAAE,sBAAsB,EAAE,CAAA;CAAE,CA8BxE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAQ3E;AAUD,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,OAAO,CAAC,mBAAmB,CAAM,GAAG,YAAY,CA0O7F"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { getQuotaNotifications, loadPluginConfig } from "./config.js";
|
|
2
|
+
import { deduplicateUsageAccountIndices, ensureCodexUsageAccessToken, fetchCodexUsage, formatUsageReset, getUsageLeftPercent, hasUsageWindow, parseCodexUsagePayload, resolveCodexUsageAccountId, } from "./codex-usage.js";
|
|
3
|
+
import { logDebug, logInfo, logWarn } from "./logger.js";
|
|
4
|
+
import { isDesktopNotificationSupported, sendDesktopNotification, } from "./desktop-notifications.js";
|
|
5
|
+
import { getQuotaNotificationStatePath, updateQuotaNotificationState, } from "./quota-notification-state.js";
|
|
6
|
+
import { registerCleanup, unregisterCleanup } from "./shutdown.js";
|
|
7
|
+
import { loadAccounts } from "./storage.js";
|
|
8
|
+
import { getStoragePath } from "./storage/state.js";
|
|
9
|
+
const INITIAL_CHECK_DELAY_MS = 10_000;
|
|
10
|
+
const MAX_CONCURRENCY = 2;
|
|
11
|
+
/**
|
|
12
|
+
* Reduce one quota window across accounts to the single account that has the
|
|
13
|
+
* most room left, reporting that account's own reset time.
|
|
14
|
+
*
|
|
15
|
+
* Two rules matter here:
|
|
16
|
+
*
|
|
17
|
+
* - A window the plan has switched off is skipped via {@link hasUsageWindow}.
|
|
18
|
+
* Such a window still reports `used_percent: 0`, so counting it would score
|
|
19
|
+
* a disabled window as 100% remaining and mask every other account.
|
|
20
|
+
* - The percentage and the reset time are taken from the *same* account. A
|
|
21
|
+
* max-percent/min-reset pair describes a quota no account actually has.
|
|
22
|
+
*/
|
|
23
|
+
function aggregateWindow(summaries, select, now) {
|
|
24
|
+
let best;
|
|
25
|
+
for (const accountSummary of summaries) {
|
|
26
|
+
const window = select(accountSummary.usage);
|
|
27
|
+
if (!hasUsageWindow(window))
|
|
28
|
+
continue;
|
|
29
|
+
const remaining = getUsageLeftPercent(window.usedPercent);
|
|
30
|
+
if (remaining === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
const resetAtMs = typeof window.resetAtMs === "number" &&
|
|
33
|
+
Number.isFinite(window.resetAtMs) &&
|
|
34
|
+
window.resetAtMs > now
|
|
35
|
+
? window.resetAtMs
|
|
36
|
+
: undefined;
|
|
37
|
+
if (best === undefined || remaining > best.remainingPercent) {
|
|
38
|
+
best = { remainingPercent: remaining, resetAtMs };
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Tie on headroom: prefer the account that recovers first, and prefer a
|
|
42
|
+
// known reset over an unknown one.
|
|
43
|
+
if (remaining === best.remainingPercent &&
|
|
44
|
+
resetAtMs !== undefined &&
|
|
45
|
+
(best.resetAtMs === undefined || resetAtMs < best.resetAtMs)) {
|
|
46
|
+
best = { remainingPercent: remaining, resetAtMs };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best ? { remainingPercent: best.remainingPercent, resetAtMs: best.resetAtMs } : {};
|
|
50
|
+
}
|
|
51
|
+
export function aggregateQuotaUsage(summaries, now = Date.now()) {
|
|
52
|
+
return {
|
|
53
|
+
fiveHour: aggregateWindow(summaries, (summary) => summary.primary, now),
|
|
54
|
+
weekly: aggregateWindow(summaries, (summary) => summary.secondary, now),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Decide whether this observation crosses a threshold downward.
|
|
59
|
+
*
|
|
60
|
+
* A threshold fires when the window is at or below it *and* the previous
|
|
61
|
+
* observation was strictly above it, which is the "rises above it after a
|
|
62
|
+
* reset" contract in `docs/configuration.md`. The most severe matching
|
|
63
|
+
* threshold wins, and the selection does not assume `thresholds` is sorted.
|
|
64
|
+
*/
|
|
65
|
+
export function transitionQuotaWindow(previous, current, thresholds) {
|
|
66
|
+
const remaining = current.remainingPercent;
|
|
67
|
+
if (remaining === undefined)
|
|
68
|
+
return { state: previous };
|
|
69
|
+
const matching = thresholds.filter((threshold) => remaining <= threshold &&
|
|
70
|
+
(previous.lastPercent === undefined || previous.lastPercent > threshold));
|
|
71
|
+
return {
|
|
72
|
+
state: { lastPercent: remaining },
|
|
73
|
+
threshold: matching.length > 0 ? Math.min(...matching) : undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function transitionQuotaState(previous, usage, thresholds, now = Date.now()) {
|
|
77
|
+
const baseline = previous ?? { fiveHour: {}, weekly: {}, updatedAt: now };
|
|
78
|
+
const fiveHour = transitionQuotaWindow(baseline.fiveHour, usage.fiveHour, thresholds);
|
|
79
|
+
const weekly = transitionQuotaWindow(baseline.weekly, usage.weekly, thresholds);
|
|
80
|
+
const crossings = [];
|
|
81
|
+
if (fiveHour.threshold !== undefined && usage.fiveHour.remainingPercent !== undefined) {
|
|
82
|
+
crossings.push({
|
|
83
|
+
window: "fiveHour",
|
|
84
|
+
threshold: fiveHour.threshold,
|
|
85
|
+
remainingPercent: usage.fiveHour.remainingPercent,
|
|
86
|
+
resetAtMs: usage.fiveHour.resetAtMs,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (weekly.threshold !== undefined && usage.weekly.remainingPercent !== undefined) {
|
|
90
|
+
crossings.push({
|
|
91
|
+
window: "weekly",
|
|
92
|
+
threshold: weekly.threshold,
|
|
93
|
+
remainingPercent: usage.weekly.remainingPercent,
|
|
94
|
+
resetAtMs: usage.weekly.resetAtMs,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
state: {
|
|
99
|
+
fiveHour: fiveHour.state,
|
|
100
|
+
weekly: weekly.state,
|
|
101
|
+
lastDeliveredAt: baseline.lastDeliveredAt,
|
|
102
|
+
updatedAt: now,
|
|
103
|
+
},
|
|
104
|
+
crossings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function formatQuotaNotification(usage) {
|
|
108
|
+
const fiveHourLine = usage.fiveHour.remainingPercent !== undefined
|
|
109
|
+
? `5h: ${usage.fiveHour.remainingPercent}% | resets ${formatUsageReset(usage.fiveHour.resetAtMs) ?? "unavailable"}`
|
|
110
|
+
: "5h: unavailable";
|
|
111
|
+
const weeklyLine = usage.weekly.remainingPercent !== undefined
|
|
112
|
+
? `Weekly: ${usage.weekly.remainingPercent}% | resets ${formatUsageReset(usage.weekly.resetAtMs) ?? "unavailable"}`
|
|
113
|
+
: "Weekly: unavailable";
|
|
114
|
+
return `${fiveHourLine}\n${weeklyLine}`;
|
|
115
|
+
}
|
|
116
|
+
export function createQuotaMonitor(overrides = {}) {
|
|
117
|
+
const dependencies = {
|
|
118
|
+
loadConfig: () => getQuotaNotifications(loadPluginConfig()),
|
|
119
|
+
loadStorage: loadAccounts,
|
|
120
|
+
fetchSummary: fetchUsageForAccount,
|
|
121
|
+
notify: sendDesktopNotification,
|
|
122
|
+
notificationsSupported: isDesktopNotificationSupported,
|
|
123
|
+
onCredentialsPersisted: () => undefined,
|
|
124
|
+
now: Date.now,
|
|
125
|
+
initialDelayMs: INITIAL_CHECK_DELAY_MS,
|
|
126
|
+
...overrides,
|
|
127
|
+
};
|
|
128
|
+
let timer;
|
|
129
|
+
let started = false;
|
|
130
|
+
let disposed = false;
|
|
131
|
+
let running = false;
|
|
132
|
+
let generation = 0;
|
|
133
|
+
// Keyed by state path: the host flips `setStoragePath` at runtime, so one
|
|
134
|
+
// project's threshold state must never be carried into another's file.
|
|
135
|
+
const memoryStateByPath = new Map();
|
|
136
|
+
const schedule = (delayMs, expectedGeneration) => {
|
|
137
|
+
if (!started || disposed || expectedGeneration !== generation)
|
|
138
|
+
return;
|
|
139
|
+
if (timer)
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
timer = setTimeout(() => {
|
|
142
|
+
timer = undefined;
|
|
143
|
+
void tick(expectedGeneration, true);
|
|
144
|
+
}, delayMs);
|
|
145
|
+
timer.unref();
|
|
146
|
+
};
|
|
147
|
+
const releaseUndeliveredClaim = async (statePath, claim, claimedAt) => {
|
|
148
|
+
const reverted = {
|
|
149
|
+
fiveHour: claim.state.fiveHour,
|
|
150
|
+
weekly: claim.state.weekly,
|
|
151
|
+
lastDeliveredAt: claim.previousLastDeliveredAt > 0 ? claim.previousLastDeliveredAt : undefined,
|
|
152
|
+
updatedAt: dependencies.now(),
|
|
153
|
+
};
|
|
154
|
+
for (const crossing of claim.crossings) {
|
|
155
|
+
reverted[crossing.window] = claim.baseline?.[crossing.window] ?? {};
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const applied = await updateQuotaNotificationState(statePath, (persisted) => {
|
|
159
|
+
if (persisted?.updatedAt !== claimedAt) {
|
|
160
|
+
// Someone else wrote after our claim; leave their state alone.
|
|
161
|
+
return { state: persisted ?? claim.state, result: false };
|
|
162
|
+
}
|
|
163
|
+
return { state: reverted, result: true };
|
|
164
|
+
});
|
|
165
|
+
if (applied)
|
|
166
|
+
memoryStateByPath.set(statePath, reverted);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
logWarn(`Failed to release an undelivered quota notification claim: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const check = async (config, expectedGeneration) => {
|
|
173
|
+
const storage = await dependencies.loadStorage();
|
|
174
|
+
if (!storage || storage.accounts.length === 0 || disposed || expectedGeneration !== generation)
|
|
175
|
+
return;
|
|
176
|
+
// Resolved next to the load that produced `storage`, not after the
|
|
177
|
+
// fetches: the host flips `setStoragePath` when the project changes, and
|
|
178
|
+
// this aggregate describes the accounts that were just read.
|
|
179
|
+
const statePath = getQuotaNotificationStatePath(getStoragePath());
|
|
180
|
+
const indices = deduplicateUsageAccountIndices(storage);
|
|
181
|
+
const summaries = [];
|
|
182
|
+
// Fired at the moment of rotation, not at the end of the check. The
|
|
183
|
+
// rotated refresh token is already durable on disk, and the host's
|
|
184
|
+
// cached AccountManager has a 500ms debounced save still holding the old
|
|
185
|
+
// one; a check over N accounts takes far longer than that, so deferring
|
|
186
|
+
// this leaves exactly the lost-update window open that it exists to
|
|
187
|
+
// close. Cheap to call more than once: it is a no-op with no cached
|
|
188
|
+
// manager.
|
|
189
|
+
const markCredentialsPersisted = () => {
|
|
190
|
+
try {
|
|
191
|
+
dependencies.onCredentialsPersisted();
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
logWarn(`Failed to invalidate account cache after a quota refresh: ${error.message}`);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
for (let offset = 0; offset < indices.length; offset += MAX_CONCURRENCY) {
|
|
198
|
+
if (disposed || expectedGeneration !== generation)
|
|
199
|
+
break;
|
|
200
|
+
const chunk = indices.slice(offset, offset + MAX_CONCURRENCY);
|
|
201
|
+
const results = await Promise.all(chunk.map((index) => dependencies.fetchSummary(storage, storage.accounts[index], markCredentialsPersisted)));
|
|
202
|
+
for (const summary of results) {
|
|
203
|
+
if (summary)
|
|
204
|
+
summaries.push(summary);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (summaries.length === 0 || disposed || expectedGeneration !== generation)
|
|
208
|
+
return;
|
|
209
|
+
const now = dependencies.now();
|
|
210
|
+
const usage = aggregateQuotaUsage(summaries, now);
|
|
211
|
+
const memoryState = memoryStateByPath.get(statePath);
|
|
212
|
+
let claim;
|
|
213
|
+
try {
|
|
214
|
+
// Phase 1: claim the delivery slot under the cross-process lease. The
|
|
215
|
+
// notifier is NOT called here — its 10s timeout is an order of
|
|
216
|
+
// magnitude longer than the lease's ~660ms retry budget, so holding
|
|
217
|
+
// the lease across delivery makes concurrent processes drop whole
|
|
218
|
+
// checks with ELOCKED.
|
|
219
|
+
claim = await updateQuotaNotificationState(statePath, (persisted) => {
|
|
220
|
+
const baseline = memoryState && (!persisted || memoryState.updatedAt > persisted.updatedAt)
|
|
221
|
+
? memoryState
|
|
222
|
+
: persisted;
|
|
223
|
+
const transition = transitionQuotaState(baseline, usage, config.thresholds, now);
|
|
224
|
+
const previousLastDeliveredAt = Math.max(persisted?.lastDeliveredAt ?? 0, memoryState?.lastDeliveredAt ?? 0);
|
|
225
|
+
if (previousLastDeliveredAt > 0) {
|
|
226
|
+
transition.state.lastDeliveredAt = previousLastDeliveredAt;
|
|
227
|
+
}
|
|
228
|
+
const everyCheckDue = config.notifyEveryCheck &&
|
|
229
|
+
(previousLastDeliveredAt === 0 || now - previousLastDeliveredAt >= config.intervalMs);
|
|
230
|
+
const delivering = transition.crossings.length > 0 || everyCheckDue;
|
|
231
|
+
if (delivering)
|
|
232
|
+
transition.state.lastDeliveredAt = now;
|
|
233
|
+
return {
|
|
234
|
+
state: transition.state,
|
|
235
|
+
result: {
|
|
236
|
+
delivering,
|
|
237
|
+
state: transition.state,
|
|
238
|
+
baseline,
|
|
239
|
+
crossings: transition.crossings,
|
|
240
|
+
previousLastDeliveredAt,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
logWarn(`Failed to update quota notification state: ${error.message}`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
memoryStateByPath.set(statePath, claim.state);
|
|
250
|
+
if (!claim.delivering)
|
|
251
|
+
return;
|
|
252
|
+
// Phase 2: deliver outside the lease.
|
|
253
|
+
const delivered = disposed || expectedGeneration !== generation
|
|
254
|
+
? false
|
|
255
|
+
: await dependencies
|
|
256
|
+
.notify("Codex quota status", formatQuotaNotification(usage))
|
|
257
|
+
.catch((error) => {
|
|
258
|
+
logDebug(`Failed to deliver quota notification: ${error.message}`);
|
|
259
|
+
return false;
|
|
260
|
+
});
|
|
261
|
+
if (delivered) {
|
|
262
|
+
logInfo("Quota notification delivered");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Phase 3: delivery failed, so release the claim and re-arm the crossed
|
|
266
|
+
// windows. Another process that claimed in the meantime keeps its claim.
|
|
267
|
+
await releaseUndeliveredClaim(statePath, claim, now);
|
|
268
|
+
};
|
|
269
|
+
const scheduleNext = (expectedGeneration) => {
|
|
270
|
+
let intervalMs;
|
|
271
|
+
try {
|
|
272
|
+
intervalMs = dependencies.loadConfig().intervalMs;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
// Reading the config is the only thing that can throw out here, and
|
|
276
|
+
// `tick`'s catch cannot cover its own `finally`.
|
|
277
|
+
logDebug(`Quota monitor could not schedule the next check: ${error.message}`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
schedule(intervalMs, expectedGeneration);
|
|
281
|
+
};
|
|
282
|
+
const tick = async (expectedGeneration, reschedule) => {
|
|
283
|
+
if (disposed || expectedGeneration !== generation)
|
|
284
|
+
return;
|
|
285
|
+
if (running) {
|
|
286
|
+
if (reschedule)
|
|
287
|
+
scheduleNext(expectedGeneration);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
running = true;
|
|
291
|
+
// Stays true when the config read throws, so a transient failure retries
|
|
292
|
+
// on the next interval instead of killing the monitor for the session.
|
|
293
|
+
let keepPolling = true;
|
|
294
|
+
try {
|
|
295
|
+
const config = dependencies.loadConfig();
|
|
296
|
+
// `thresholds: []` with `notifyEveryCheck: false` can never deliver
|
|
297
|
+
// anything, so polling it would query the usage endpoint for every
|
|
298
|
+
// account, forever, with no possible output.
|
|
299
|
+
const canDeliver = config.notifyEveryCheck || config.thresholds.length > 0;
|
|
300
|
+
keepPolling = config.enabled && canDeliver && dependencies.notificationsSupported();
|
|
301
|
+
if (keepPolling)
|
|
302
|
+
await check(config, expectedGeneration);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
logDebug(`Quota monitor tick failed: ${error.message}`);
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
running = false;
|
|
309
|
+
// A feature that is switched off, or a platform without Notification
|
|
310
|
+
// Center, stops polling rather than waking every interval forever.
|
|
311
|
+
// Turning it on is a restart, which `docs/configuration.md` requires.
|
|
312
|
+
if (reschedule && keepPolling)
|
|
313
|
+
scheduleNext(expectedGeneration);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const disposeMonitor = () => {
|
|
317
|
+
if (disposed)
|
|
318
|
+
return;
|
|
319
|
+
disposed = true;
|
|
320
|
+
started = false;
|
|
321
|
+
generation += 1;
|
|
322
|
+
if (timer)
|
|
323
|
+
clearTimeout(timer);
|
|
324
|
+
timer = undefined;
|
|
325
|
+
unregisterCleanup(disposeMonitor);
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
start() {
|
|
329
|
+
if (started && !disposed)
|
|
330
|
+
return;
|
|
331
|
+
started = true;
|
|
332
|
+
disposed = false;
|
|
333
|
+
generation += 1;
|
|
334
|
+
// `server.instance.disposed` is not emitted on SIGINT/SIGTERM, nor by
|
|
335
|
+
// every host, so the timer is also torn down by the shared drain.
|
|
336
|
+
registerCleanup(disposeMonitor);
|
|
337
|
+
schedule(dependencies.initialDelayMs, generation);
|
|
338
|
+
},
|
|
339
|
+
dispose: disposeMonitor,
|
|
340
|
+
async runNow() {
|
|
341
|
+
await tick(generation, false);
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
async function fetchUsageForAccount(storage, account, onCredentialsPersisted = () => undefined) {
|
|
346
|
+
if (!account)
|
|
347
|
+
return null;
|
|
348
|
+
try {
|
|
349
|
+
const credentials = await ensureCodexUsageAccessToken({ storage, account });
|
|
350
|
+
// Signal BEFORE the usage fetch: the rotation is already durable, so a
|
|
351
|
+
// later throw must not swallow the cache invalidation.
|
|
352
|
+
if (credentials.persisted)
|
|
353
|
+
onCredentialsPersisted();
|
|
354
|
+
const accountId = resolveCodexUsageAccountId({ account, accessToken: credentials.accessToken });
|
|
355
|
+
if (!accountId)
|
|
356
|
+
return null;
|
|
357
|
+
return {
|
|
358
|
+
usage: parseCodexUsagePayload(await fetchCodexUsage({
|
|
359
|
+
accountId,
|
|
360
|
+
accessToken: credentials.accessToken,
|
|
361
|
+
organizationId: account.organizationId,
|
|
362
|
+
normalizeAccountErrors: true,
|
|
363
|
+
})),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
logDebug(`Failed to fetch quota for one account: ${error.message}`);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=quota-notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-notifications.js","sourceRoot":"","sources":["../../lib/quota-notifications.ts"],"names":[],"mappings":"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"}
|