codex-multi-auth 2.19.1 → 2.21.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/.codex-plugin/plugin.json +9 -9
- package/LICENSE +21 -21
- package/README.md +149 -148
- package/assets/codex-multi-auth-icon.svg +39 -39
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +31 -31
- package/config/codex-legacy.json +1021 -1021
- package/config/codex-modern.json +347 -347
- package/config/minimal-codex.json +15 -15
- package/dist/index.js +1 -1
- package/dist/lib/accounts/rate-limits.js +5 -0
- package/dist/lib/accounts.js +5 -4
- package/dist/lib/auth/account-access.js +2 -2
- package/dist/lib/codex-cli/version.d.ts +22 -0
- package/dist/lib/codex-cli/version.js +78 -0
- package/dist/lib/codex-manager/commands/limits.d.ts +2 -0
- package/dist/lib/codex-manager/commands/limits.js +31 -0
- package/dist/lib/codex-manager/commands/status.js +1 -1
- package/dist/lib/codex-manager.js +2 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/context-budget/model-context-windows.d.ts +1 -1
- package/dist/lib/context-budget/model-context-windows.js +4 -0
- package/dist/lib/context-budget-response.js +8 -8
- package/dist/lib/context-overflow.js +10 -10
- package/dist/lib/luna-reserve.d.ts +26 -0
- package/dist/lib/luna-reserve.js +68 -0
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/policy/runtime-policy.js +14 -0
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.js +79 -79
- package/dist/lib/request/helpers/model-map.d.ts +1 -1
- package/dist/lib/request/helpers/model-map.js +12 -0
- package/dist/lib/request/response-outcome.d.ts +1 -1
- package/dist/lib/request/response-outcome.js +4 -1
- package/dist/lib/runtime/account-status.js +2 -1
- package/dist/lib/runtime/luna-reserve-usage.d.ts +9 -0
- package/dist/lib/runtime/luna-reserve-usage.js +68 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +6 -2
- package/dist/lib/runtime/model-discovery-status.js +13 -5
- package/dist/lib/runtime/reset-credit-routing.js +4 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +2 -0
- package/dist/lib/runtime/runtime-observability.d.ts +2 -0
- package/dist/lib/runtime/runtime-observability.js +1 -0
- package/dist/lib/runtime-rotation-proxy.js +200 -32
- package/dist/lib/usage/pricing.d.ts +1 -1
- package/dist/lib/usage/pricing.js +4 -0
- package/dist/lib/usage/usage-extraction.d.ts +2 -0
- package/dist/lib/usage/usage-extraction.js +25 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
- package/node_modules/@codex-ai/plugin/package.json +9 -9
- package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -4
- package/node_modules/@codex-ai/sdk/dist/index.js +2 -2
- package/node_modules/@codex-ai/sdk/package.json +8 -8
- package/package.json +207 -207
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +374 -373
- package/scripts/codex-bin-resolver.js +332 -332
- package/scripts/codex-multi-auth.js +65 -65
- package/scripts/codex-routing.js +62 -62
- package/scripts/codex.js +7006 -7005
- package/scripts/install-codex-auth-utils.js +213 -213
- package/scripts/mcodex.js +367 -367
- package/scripts/postinstall.js +114 -114
- package/scripts/preuninstall.js +265 -265
- package/skills/codex-auth-setup/SKILL.md +73 -73
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
- package/vendor/codex-ai-plugin/dist/index.js +2 -2
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
- package/vendor/codex-ai-plugin/dist/tool.js +29 -29
- package/vendor/codex-ai-plugin/package.json +9 -9
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
- package/vendor/codex-ai-sdk/dist/index.js +2 -2
- package/vendor/codex-ai-sdk/package.json +8 -8
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./schema/config.schema.json",
|
|
3
|
-
"plugin": ["codex-multi-auth"],
|
|
4
|
-
|
|
5
|
-
"provider": {
|
|
6
|
-
"openai": {
|
|
7
|
-
"options": {
|
|
8
|
-
"store": false
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"model": "openai/gpt-5.6-sol"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema/config.schema.json",
|
|
3
|
+
"plugin": ["codex-multi-auth"],
|
|
4
|
+
|
|
5
|
+
"provider": {
|
|
6
|
+
"openai": {
|
|
7
|
+
"options": {
|
|
8
|
+
"store": false
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"model": "openai/gpt-5.6-sol"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
package/dist/index.js
CHANGED
|
@@ -1043,7 +1043,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1043
1043
|
}
|
|
1044
1044
|
let response;
|
|
1045
1045
|
const fetchStart = performance.now();
|
|
1046
|
-
// Merge user AbortSignal with timeout
|
|
1046
|
+
// Merge user AbortSignal with timeout without AbortSignal.any (keeps listeners bounded)
|
|
1047
1047
|
const fetchController = new AbortController();
|
|
1048
1048
|
const requestTimeoutMs = fetchTimeoutMs;
|
|
1049
1049
|
let requestTimedOut = false;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Rate limiting utilities for account management.
|
|
3
3
|
* Extracted from accounts.ts to reduce module size and improve cohesion.
|
|
4
4
|
*/
|
|
5
|
+
import { LUNA_RESERVE_MODEL } from "../constants.js";
|
|
5
6
|
import { nowMs } from "../utils.js";
|
|
6
7
|
export function parseRateLimitReason(code) {
|
|
7
8
|
if (!code)
|
|
@@ -61,6 +62,10 @@ export function isRateLimitedForFamily(entity, family, model) {
|
|
|
61
62
|
if (isRateLimitedForQuotaKey(entity, modelKey)) {
|
|
62
63
|
return true;
|
|
63
64
|
}
|
|
65
|
+
// Luna Reserve is independently metered. Ordinary Luna/Codex family
|
|
66
|
+
// exhaustion must not suppress a separately available reserve allowance.
|
|
67
|
+
if (model === LUNA_RESERVE_MODEL)
|
|
68
|
+
return false;
|
|
64
69
|
}
|
|
65
70
|
const baseKey = getQuotaKey(family);
|
|
66
71
|
return isRateLimitedForQuotaKey(entity, baseKey);
|
package/dist/lib/accounts.js
CHANGED
|
@@ -9,7 +9,7 @@ import { MODEL_FAMILIES } from "./request/helpers/model-map.js";
|
|
|
9
9
|
import { getHealthTracker, getTokenTracker, resetTrackers, selectHybridAccount, } from "./rotation.js";
|
|
10
10
|
import { withRoutingMutex, } from "./routing-mutex.js";
|
|
11
11
|
import { nowMs } from "./utils.js";
|
|
12
|
-
import { ERROR_MESSAGES, HTTP_STATUS, MAX_RATE_LIMIT_DELAY_MS } from "./constants.js";
|
|
12
|
+
import { ERROR_MESSAGES, HTTP_STATUS, LUNA_RESERVE_MODEL, MAX_RATE_LIMIT_DELAY_MS } from "./constants.js";
|
|
13
13
|
import { CodexAuthError } from "./errors.js";
|
|
14
14
|
import { loadCodexCliState, } from "./codex-cli/state.js";
|
|
15
15
|
import { syncAccountStorageFromCodexCli } from "./codex-cli/sync.js";
|
|
@@ -1062,13 +1062,14 @@ export class AccountManager {
|
|
|
1062
1062
|
: undefined;
|
|
1063
1063
|
const newLimits = { ...carried?.limits };
|
|
1064
1064
|
const baseKey = getQuotaKey(family);
|
|
1065
|
-
|
|
1065
|
+
const independentReserve = model === LUNA_RESERVE_MODEL;
|
|
1066
|
+
if (!independentReserve && (!model || reason === "quota" || reason === "unknown")) {
|
|
1066
1067
|
const currentResetAt = account.rateLimitResetTimes[baseKey] ?? 0;
|
|
1067
1068
|
account.rateLimitResetTimes[baseKey] = Math.max(currentResetAt, resetAt);
|
|
1068
1069
|
newLimits[baseKey] = Math.max(newLimits[baseKey] ?? 0, resetAt);
|
|
1069
1070
|
}
|
|
1070
1071
|
if (model &&
|
|
1071
|
-
(reason === "tokens" || reason === "concurrent" || reason === "unknown")) {
|
|
1072
|
+
(independentReserve || reason === "tokens" || reason === "concurrent" || reason === "unknown")) {
|
|
1072
1073
|
const modelKey = getQuotaKey(family, model);
|
|
1073
1074
|
const currentResetAt = account.rateLimitResetTimes[modelKey] ?? 0;
|
|
1074
1075
|
account.rateLimitResetTimes[modelKey] = Math.max(currentResetAt, resetAt);
|
|
@@ -1550,7 +1551,7 @@ export class AccountManager {
|
|
|
1550
1551
|
for (const account of enabledAccounts) {
|
|
1551
1552
|
const perAccountWaitTimes = [];
|
|
1552
1553
|
const baseResetAt = account.rateLimitResetTimes[baseKey];
|
|
1553
|
-
if (typeof baseResetAt === "number") {
|
|
1554
|
+
if (model !== LUNA_RESERVE_MODEL && typeof baseResetAt === "number") {
|
|
1554
1555
|
perAccountWaitTimes.push(Math.max(0, baseResetAt - now));
|
|
1555
1556
|
}
|
|
1556
1557
|
if (modelKey) {
|
|
@@ -51,8 +51,8 @@ export async function fetchAuthorizedAccounts(accessToken, options = {}) {
|
|
|
51
51
|
return null;
|
|
52
52
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
53
53
|
const timeout = AbortSignal.timeout(ACCOUNTS_CHECK_TIMEOUT_MS);
|
|
54
|
-
// combineSignals wraps AbortSignal.any (available
|
|
55
|
-
// engines floor) with null-tolerance; the native composite also detaches
|
|
54
|
+
// combineSignals wraps AbortSignal.any (available on every Node the
|
|
55
|
+
// engines floor, `>=22.19`, permits) with null-tolerance; the native composite also detaches
|
|
56
56
|
// from a long-lived caller signal once this request completes.
|
|
57
57
|
const signal = options.signal
|
|
58
58
|
? combineSignals(options.signal, timeout)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ResolvedCodexBin {
|
|
2
|
+
path: string;
|
|
3
|
+
launchWithNode: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface CodexCliVersionDeps {
|
|
6
|
+
resolveBin?: () => Promise<ResolvedCodexBin | null>;
|
|
7
|
+
execFile?: (file: string, args: readonly string[], options: {
|
|
8
|
+
timeout: number;
|
|
9
|
+
}) => Promise<{
|
|
10
|
+
stdout: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare const CLIENT_VERSION_PATTERN: RegExp;
|
|
14
|
+
/** Extracts `0.160.1` from `codex --version` output such as `codex-cli 0.160.1`. */
|
|
15
|
+
export declare function parseCodexCliVersion(output: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the installed Codex CLI version, or null when no binary resolves or
|
|
18
|
+
* the probe fails. Detection failure must never break a check: the caller just
|
|
19
|
+
* sends no `client_version`, exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectInstalledCodexCliVersion(deps?: CodexCliVersionDeps): Promise<string | null>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the installed Codex CLI version for the `client_version` parameter
|
|
3
|
+
* the upstream models catalog requires (issue #733). The catalog filters by
|
|
4
|
+
* `minimal_client_version`, so the honest value is whatever `codex --version`
|
|
5
|
+
* reports on this host — never a hardcoded string.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
// scripts/codex-bin-resolver.js is plain shipped JS, so it loads by URL:
|
|
11
|
+
// dist/lib/codex-cli/ resolves ../../../scripts, the source tree ../../scripts.
|
|
12
|
+
const RESOLVER_CANDIDATES = [
|
|
13
|
+
fileURLToPath(new URL("../../../scripts/codex-bin-resolver.js", import.meta.url)),
|
|
14
|
+
fileURLToPath(new URL("../../scripts/codex-bin-resolver.js", import.meta.url)),
|
|
15
|
+
];
|
|
16
|
+
async function loadBinResolver() {
|
|
17
|
+
for (const candidate of RESOLVER_CANDIDATES) {
|
|
18
|
+
if (!existsSync(candidate))
|
|
19
|
+
continue;
|
|
20
|
+
const resolverModule = (await import(pathToFileURL(candidate).href).catch(() => null));
|
|
21
|
+
if (typeof resolverModule?.resolveRealCodexBin === "function") {
|
|
22
|
+
return resolverModule;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function defaultResolveBin() {
|
|
28
|
+
const resolverModule = await loadBinResolver();
|
|
29
|
+
const resolve = resolverModule?.resolveRealCodexBin;
|
|
30
|
+
if (!resolve)
|
|
31
|
+
return null;
|
|
32
|
+
const bin = resolve();
|
|
33
|
+
// PATH discovery can hand back npm's extensionless `codex` shim on Windows,
|
|
34
|
+
// which no spawn API can exec. The resolver knows the sibling package entry
|
|
35
|
+
// the shim points at; launch that through node instead.
|
|
36
|
+
if (bin &&
|
|
37
|
+
process.platform === "win32" &&
|
|
38
|
+
!bin.launchWithNode &&
|
|
39
|
+
resolverModule.isWindowsShimPath?.(bin.path)) {
|
|
40
|
+
const entry = resolverModule.resolveWindowsShimPackageEntry?.(bin.path);
|
|
41
|
+
if (entry)
|
|
42
|
+
return { path: entry, launchWithNode: true };
|
|
43
|
+
}
|
|
44
|
+
return bin;
|
|
45
|
+
}
|
|
46
|
+
const defaultExecFile = (file, args, options) => new Promise((resolve, reject) => {
|
|
47
|
+
execFile(file, [...args], { ...options, encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
48
|
+
if (error)
|
|
49
|
+
reject(error);
|
|
50
|
+
else
|
|
51
|
+
resolve({ stdout: String(stdout) });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
export const CLIENT_VERSION_PATTERN = /^[0-9A-Za-z.+_-]{1,80}$/;
|
|
55
|
+
/** Extracts `0.160.1` from `codex --version` output such as `codex-cli 0.160.1`. */
|
|
56
|
+
export function parseCodexCliVersion(output) {
|
|
57
|
+
const match = /(?:^|\s)v?(\d+\.\d+\.\d+[-0-9A-Za-z.+_]*)/.exec(output);
|
|
58
|
+
const version = match?.[1];
|
|
59
|
+
return version && CLIENT_VERSION_PATTERN.test(version) ? version : null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns the installed Codex CLI version, or null when no binary resolves or
|
|
63
|
+
* the probe fails. Detection failure must never break a check: the caller just
|
|
64
|
+
* sends no `client_version`, exactly as before.
|
|
65
|
+
*/
|
|
66
|
+
export async function detectInstalledCodexCliVersion(deps = {}) {
|
|
67
|
+
try {
|
|
68
|
+
const bin = await (deps.resolveBin ?? defaultResolveBin)();
|
|
69
|
+
if (!bin || typeof bin.path !== "string" || bin.path.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const exec = deps.execFile ?? defaultExecFile;
|
|
72
|
+
const { stdout } = await exec(bin.launchWithNode ? process.execPath : bin.path, bin.launchWithNode ? [bin.path, "--version"] : ["--version"], { timeout: 10_000 });
|
|
73
|
+
return parseCodexCliVersion(stdout);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type ModelFamily } from "../../prompts/codex.js";
|
|
2
2
|
import type { QuotaCacheData } from "../../quota-cache.js";
|
|
3
3
|
import type { AccountStorageV3 } from "../../storage.js";
|
|
4
|
+
import type { LunaReserveSnapshot } from "../../luna-reserve.js";
|
|
4
5
|
export interface LimitsCommandDeps {
|
|
5
6
|
setStoragePath: (path: string | null) => void;
|
|
6
7
|
loadAccounts: () => Promise<AccountStorageV3 | null>;
|
|
7
8
|
loadQuotaCache: () => Promise<QuotaCacheData>;
|
|
8
9
|
refreshQuotaCache: (storage: AccountStorageV3, cache: QuotaCacheData, maxAgeMs: number) => Promise<QuotaCacheData>;
|
|
10
|
+
refreshLunaReserveUsage?: (storage: AccountStorageV3) => Promise<Record<number, LunaReserveSnapshot>>;
|
|
9
11
|
resolveActiveIndex: (storage: AccountStorageV3, family?: ModelFamily) => number;
|
|
10
12
|
getNow?: () => number;
|
|
11
13
|
logInfo?: (message: string) => void;
|
|
@@ -46,6 +46,32 @@ function publicQuotaEntry(entry) {
|
|
|
46
46
|
secondary: publicWindow(entry.secondary),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/** Convert one Reserve window to the stable public JSON shape. */
|
|
50
|
+
function publicReserveWindow(window) {
|
|
51
|
+
return window
|
|
52
|
+
? {
|
|
53
|
+
usedPercent: window.usedPercent,
|
|
54
|
+
remainingPercent: window.remainingPercent,
|
|
55
|
+
windowMinutes: window.windowMinutes,
|
|
56
|
+
resetAtMs: window.resetAtMs,
|
|
57
|
+
}
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
/** Convert a Reserve snapshot to public JSON while preserving explicit unknowns. */
|
|
61
|
+
function publicLunaReserve(snapshot) {
|
|
62
|
+
if (!snapshot)
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
observedAt: snapshot.observedAt,
|
|
66
|
+
offered: snapshot.offered,
|
|
67
|
+
available: snapshot.available,
|
|
68
|
+
limitId: snapshot.limitId,
|
|
69
|
+
limitName: snapshot.limitName,
|
|
70
|
+
normalModelSlug: snapshot.normalModelSlug,
|
|
71
|
+
primary: publicReserveWindow(snapshot.primary),
|
|
72
|
+
secondary: publicReserveWindow(snapshot.secondary),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
49
75
|
/**
|
|
50
76
|
* Emit configured accounts joined to safe cached quota records.
|
|
51
77
|
*
|
|
@@ -86,8 +112,12 @@ export async function runLimitsCommand(args, deps) {
|
|
|
86
112
|
return 0;
|
|
87
113
|
}
|
|
88
114
|
let cache = await deps.loadQuotaCache();
|
|
115
|
+
let lunaReserveByAccount = {};
|
|
89
116
|
if (parsed.options.refresh) {
|
|
90
117
|
cache = await deps.refreshQuotaCache(storage, cache, LIMITS_REFRESH_MAX_AGE_MS);
|
|
118
|
+
if (deps.refreshLunaReserveUsage) {
|
|
119
|
+
lunaReserveByAccount = await deps.refreshLunaReserveUsage(storage);
|
|
120
|
+
}
|
|
91
121
|
}
|
|
92
122
|
const generatedAt = deps.getNow?.() ?? Date.now();
|
|
93
123
|
const selection = resolveSelection(storage, deps.resolveActiveIndex);
|
|
@@ -102,6 +132,7 @@ export async function runLimitsCommand(args, deps) {
|
|
|
102
132
|
enabled: account.enabled !== false,
|
|
103
133
|
current: index === selection.routedIndex,
|
|
104
134
|
quota: quota ? publicQuotaEntry(quota) : null,
|
|
135
|
+
lunaReserve: publicLunaReserve(lunaReserveByAccount[index]),
|
|
105
136
|
};
|
|
106
137
|
});
|
|
107
138
|
logInfo(JSON.stringify({
|
|
@@ -279,7 +279,7 @@ export async function runStatusCommand(deps) {
|
|
|
279
279
|
runtimeSnapshot.serverBurstCooldownUntil > now
|
|
280
280
|
? formatWaitTime(runtimeSnapshot.serverBurstCooldownUntil - now)
|
|
281
281
|
: null;
|
|
282
|
-
logInfo(`Runtime: responses=${runtimeSnapshot.responsesRequests}, refresh=${runtimeSnapshot.authRefreshRequests}, probes=${runtimeSnapshot.diagnosticProbeRequests}, budgetExhaustions=${runtimeMetrics.requestAttemptBudgetExhaustions}`);
|
|
282
|
+
logInfo(`Runtime: responses=${runtimeSnapshot.responsesRequests}, refresh=${runtimeSnapshot.authRefreshRequests}, probes=${runtimeSnapshot.diagnosticProbeRequests}, reserveFallbacks=${runtimeSnapshot.lunaReserveFallbacks ?? 0}, budgetExhaustions=${runtimeMetrics.requestAttemptBudgetExhaustions}`);
|
|
283
283
|
const lastRuntimeAccount = formatRuntimeLastAccount(runtimeSnapshot);
|
|
284
284
|
if (lastRuntimeAccount) {
|
|
285
285
|
logInfo(paint(`Last runtime account: ${lastRuntimeAccount}`, "accent"));
|
|
@@ -46,6 +46,7 @@ import { runUnpinCommand } from "./codex-manager/commands/unpin.js";
|
|
|
46
46
|
import { runWorkspaceCommand } from "./codex-manager/commands/workspace.js";
|
|
47
47
|
import { runUsageCommand } from "./codex-manager/commands/usage.js";
|
|
48
48
|
import { refreshQuotaCacheForMenu } from "./codex-manager/login-menu-data.js";
|
|
49
|
+
import { refreshLunaReserveUsage } from "./runtime/luna-reserve-usage.js";
|
|
49
50
|
import { printUsage } from "./codex-manager/help.js";
|
|
50
51
|
import { availabilityTone, formatBackupSavedAt, formatCompactQuotaSnapshot, formatRateLimitEntry, formatResultSummary, normalizeFailureDetail, riskTone, styleAccountDetailText, stylePromptText, styleQuotaSummary, } from "./codex-manager/formatters/index.js";
|
|
51
52
|
import { applyUiThemeFromDashboardSettings } from "./codex-manager/settings-hub.js";
|
|
@@ -307,6 +308,7 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
307
308
|
loadAccounts,
|
|
308
309
|
loadQuotaCache,
|
|
309
310
|
refreshQuotaCache: refreshQuotaCacheForMenu,
|
|
311
|
+
refreshLunaReserveUsage,
|
|
310
312
|
resolveActiveIndex,
|
|
311
313
|
}),
|
|
312
314
|
],
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export declare const PLUGIN_NAME = "codex-multi-auth";
|
|
7
7
|
/** Base URL for ChatGPT backend API */
|
|
8
8
|
export declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
9
|
+
/** Backend model used for the separately metered Luna Reserve allowance. */
|
|
10
|
+
export declare const LUNA_RESERVE_MODEL: "gpt-reserve";
|
|
9
11
|
/** Dummy API key used for OpenAI SDK (actual auth via OAuth) */
|
|
10
12
|
export declare const DUMMY_API_KEY = "chatgpt-oauth";
|
|
11
13
|
/** Provider ID for UI display - shows under "OpenAI" in auth dropdown */
|
package/dist/lib/constants.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export const PLUGIN_NAME = "codex-multi-auth";
|
|
7
7
|
/** Base URL for ChatGPT backend API */
|
|
8
8
|
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
9
|
+
/** Backend model used for the separately metered Luna Reserve allowance. */
|
|
10
|
+
export const LUNA_RESERVE_MODEL = "gpt-reserve";
|
|
9
11
|
/** Dummy API key used for OpenAI SDK (actual auth via OAuth) */
|
|
10
12
|
export const DUMMY_API_KEY = "chatgpt-oauth";
|
|
11
13
|
/** Provider ID for UI display - shows under "OpenAI" in auth dropdown */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* behavior (see the v2.5.0 release notes) closely enough to launch that no
|
|
7
7
|
* estimate here has been reviewed for it yet.
|
|
8
8
|
*/
|
|
9
|
-
export declare const UNESTIMATED_ROUTABLE_MODELS: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol", "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest"];
|
|
9
|
+
export declare const UNESTIMATED_ROUTABLE_MODELS: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-reserve", "gpt-6.1-sol", "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest"];
|
|
10
10
|
export interface EffectiveContextWindow {
|
|
11
11
|
tokens: number;
|
|
12
12
|
source: "override" | "estimate";
|
|
@@ -53,6 +53,10 @@ export const UNESTIMATED_ROUTABLE_MODELS = [
|
|
|
53
53
|
// upstream catalog as Astra, so the same reasoning keeps them unestimated.
|
|
54
54
|
"gpt-6-sol",
|
|
55
55
|
"gpt-6-luna",
|
|
56
|
+
// Luna Reserve is a quota alias of Luna, but the backend does not publish
|
|
57
|
+
// a distinct transport context ceiling for the alias. Keep it fail-closed
|
|
58
|
+
// alongside Luna rather than copying an unverified API/catalog number.
|
|
59
|
+
"gpt-reserve",
|
|
56
60
|
"gpt-6.1-sol",
|
|
57
61
|
"gpt-daybreak-blue-latest",
|
|
58
62
|
"gpt-daybreak-red-latest",
|
|
@@ -17,14 +17,14 @@ function buildPauseMessage(advisory) {
|
|
|
17
17
|
const windowNote = advisory.windowSource === "override"
|
|
18
18
|
? "your configured window size"
|
|
19
19
|
: "an estimated window size (unverified — set `contextBudgetGuardModelWindowOverrides` if you know the real limit)";
|
|
20
|
-
return `[Plugin Notice] Context budget guard paused this session at ${formatPercent(advisory.percent)}% of ${windowNote} for \`${advisory.model}\`, before hitting the model's actual limit.
|
|
21
|
-
|
|
22
|
-
Please use one of these commands to reduce context size:
|
|
23
|
-
|
|
24
|
-
• **/compact** - Compress conversation history (recommended)
|
|
25
|
-
• **/clear** - Start fresh with empty context
|
|
26
|
-
• **/undo** - Remove recent messages
|
|
27
|
-
|
|
20
|
+
return `[Plugin Notice] Context budget guard paused this session at ${formatPercent(advisory.percent)}% of ${windowNote} for \`${advisory.model}\`, before hitting the model's actual limit.
|
|
21
|
+
|
|
22
|
+
Please use one of these commands to reduce context size:
|
|
23
|
+
|
|
24
|
+
• **/compact** - Compress conversation history (recommended)
|
|
25
|
+
• **/clear** - Start fresh with empty context
|
|
26
|
+
• **/undo** - Remove recent messages
|
|
27
|
+
|
|
28
28
|
Then retry your request. This pause clears itself automatically once your context usage drops back below the threshold.`;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
@@ -33,16 +33,16 @@ export function isContextOverflowError(status, bodyText) {
|
|
|
33
33
|
/**
|
|
34
34
|
* The message shown to users when context overflow occurs
|
|
35
35
|
*/
|
|
36
|
-
const CONTEXT_OVERFLOW_MESSAGE = `[Plugin Notice] Context is too long for this model.
|
|
37
|
-
|
|
38
|
-
Please use one of these commands to reduce context size:
|
|
39
|
-
|
|
40
|
-
• **/compact** - Compress conversation history (recommended)
|
|
41
|
-
• **/clear** - Start fresh with empty context
|
|
42
|
-
• **/undo** - Remove recent messages
|
|
43
|
-
|
|
44
|
-
Then retry your request.
|
|
45
|
-
|
|
36
|
+
const CONTEXT_OVERFLOW_MESSAGE = `[Plugin Notice] Context is too long for this model.
|
|
37
|
+
|
|
38
|
+
Please use one of these commands to reduce context size:
|
|
39
|
+
|
|
40
|
+
• **/compact** - Compress conversation history (recommended)
|
|
41
|
+
• **/clear** - Start fresh with empty context
|
|
42
|
+
• **/undo** - Remove recent messages
|
|
43
|
+
|
|
44
|
+
Then retry your request.
|
|
45
|
+
|
|
46
46
|
Alternatively, you can switch to a model with a larger context window.`;
|
|
47
47
|
/**
|
|
48
48
|
* Creates a synthetic SSE response for context overflow errors.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { LUNA_RESERVE_MODEL } from "./constants.js";
|
|
2
|
+
export interface LunaReserveWindow {
|
|
3
|
+
usedPercent: number | null;
|
|
4
|
+
remainingPercent: number | null;
|
|
5
|
+
windowMinutes: number | null;
|
|
6
|
+
resetAtMs: number | null;
|
|
7
|
+
}
|
|
8
|
+
export interface LunaReserveSnapshot {
|
|
9
|
+
observedAt: number;
|
|
10
|
+
offered: boolean;
|
|
11
|
+
available: boolean | null;
|
|
12
|
+
limitId: string | null;
|
|
13
|
+
limitName: typeof LUNA_RESERVE_MODEL | null;
|
|
14
|
+
normalModelSlug: string | null;
|
|
15
|
+
primary: LunaReserveWindow | null;
|
|
16
|
+
secondary: LunaReserveWindow | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse the official Codex app-server `account/rateLimits/read` response for
|
|
20
|
+
* the separately metered Luna Reserve bucket.
|
|
21
|
+
*
|
|
22
|
+
* Absence is intentionally represented as `offered:false`, never as 0% left:
|
|
23
|
+
* the backend may omit Reserve when an account is not currently eligible or
|
|
24
|
+
* when the experiment is not active for that account.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseLunaReserveRateLimits(value: unknown, observedAt?: number): LunaReserveSnapshot;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LUNA_RESERVE_MODEL } from "./constants.js";
|
|
2
|
+
import { isRecord } from "./utils.js";
|
|
3
|
+
/** Return a finite numeric value or null for malformed backend data. */
|
|
4
|
+
function finiteNumber(value) {
|
|
5
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
6
|
+
}
|
|
7
|
+
/** Normalize backend percentages into the inclusive 0-100 range. */
|
|
8
|
+
function clampPercent(value) {
|
|
9
|
+
const number = finiteNumber(value);
|
|
10
|
+
return number === null ? null : Math.max(0, Math.min(100, number));
|
|
11
|
+
}
|
|
12
|
+
/** Parse one Reserve usage window without inventing missing values. */
|
|
13
|
+
function parseWindow(value) {
|
|
14
|
+
if (!isRecord(value))
|
|
15
|
+
return null;
|
|
16
|
+
const usedPercent = clampPercent(value.usedPercent);
|
|
17
|
+
const duration = finiteNumber(value.windowDurationMins);
|
|
18
|
+
const resetSeconds = finiteNumber(value.resetsAt);
|
|
19
|
+
return {
|
|
20
|
+
usedPercent,
|
|
21
|
+
remainingPercent: usedPercent === null ? null : Math.max(0, 100 - usedPercent),
|
|
22
|
+
windowMinutes: duration === null || duration < 0 ? null : duration,
|
|
23
|
+
resetAtMs: resetSeconds === null || resetSeconds <= 0 ? null : resetSeconds * 1000,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse the official Codex app-server `account/rateLimits/read` response for
|
|
28
|
+
* the separately metered Luna Reserve bucket.
|
|
29
|
+
*
|
|
30
|
+
* Absence is intentionally represented as `offered:false`, never as 0% left:
|
|
31
|
+
* the backend may omit Reserve when an account is not currently eligible or
|
|
32
|
+
* when the experiment is not active for that account.
|
|
33
|
+
*/
|
|
34
|
+
export function parseLunaReserveRateLimits(value, observedAt = Date.now()) {
|
|
35
|
+
const absent = {
|
|
36
|
+
observedAt,
|
|
37
|
+
offered: false,
|
|
38
|
+
available: null,
|
|
39
|
+
limitId: null,
|
|
40
|
+
limitName: null,
|
|
41
|
+
normalModelSlug: null,
|
|
42
|
+
primary: null,
|
|
43
|
+
secondary: null,
|
|
44
|
+
};
|
|
45
|
+
if (!isRecord(value) || !isRecord(value.rateLimitsByLimitId))
|
|
46
|
+
return absent;
|
|
47
|
+
for (const [limitId, candidate] of Object.entries(value.rateLimitsByLimitId)) {
|
|
48
|
+
if (!isRecord(candidate) || candidate.limitName !== LUNA_RESERVE_MODEL)
|
|
49
|
+
continue;
|
|
50
|
+
const primary = parseWindow(candidate.primary);
|
|
51
|
+
const secondary = parseWindow(candidate.secondary);
|
|
52
|
+
const knownWindows = [primary, secondary].filter((window) => window !== null && window.usedPercent !== null);
|
|
53
|
+
const exhausted = knownWindows.some((window) => (window.usedPercent ?? 0) >= 100);
|
|
54
|
+
return {
|
|
55
|
+
observedAt,
|
|
56
|
+
offered: true,
|
|
57
|
+
available: knownWindows.length === 0 ? null : !exhausted,
|
|
58
|
+
limitId: limitId || (typeof candidate.limitId === "string" ? candidate.limitId : null),
|
|
59
|
+
limitName: LUNA_RESERVE_MODEL,
|
|
60
|
+
normalModelSlug: typeof candidate.normalModelSlug === "string" && candidate.normalModelSlug.trim()
|
|
61
|
+
? candidate.normalModelSlug.trim()
|
|
62
|
+
: null,
|
|
63
|
+
primary,
|
|
64
|
+
secondary,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return absent;
|
|
68
|
+
}
|