codex-multi-auth 2.23.0 → 2.24.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 +159 -152
- 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 +33 -10
- package/dist/lib/account-policy.d.ts +2 -0
- package/dist/lib/account-policy.js +3 -1
- package/dist/lib/accounts/rate-limits.d.ts +6 -0
- package/dist/lib/accounts/rate-limits.js +14 -1
- package/dist/lib/accounts.d.ts +6 -1
- package/dist/lib/accounts.js +33 -8
- package/dist/lib/auth/token-utils.d.ts +5 -0
- package/dist/lib/auth/token-utils.js +10 -0
- package/dist/lib/codex-manager/commands/account.js +15 -0
- package/dist/lib/codex-manager/commands/resets.js +18 -5
- package/dist/lib/codex-manager/commands/workspace.d.ts +4 -2
- package/dist/lib/codex-manager/commands/workspace.js +33 -2
- package/dist/lib/codex-manager/help.js +2 -2
- package/dist/lib/codex-manager.js +14 -1
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/context-budget-response.js +8 -8
- package/dist/lib/context-overflow.js +10 -10
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/policy/runtime-policy-cache.js +1 -1
- package/dist/lib/policy/runtime-policy.d.ts +1 -0
- package/dist/lib/policy/runtime-policy.js +4 -0
- package/dist/lib/preemptive-quota-scheduler.js +12 -3
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.js +79 -79
- package/dist/lib/quota-cache.d.ts +4 -0
- package/dist/lib/quota-readiness.d.ts +1 -1
- package/dist/lib/quota-readiness.js +9 -2
- package/dist/lib/request/rate-limit-decision.js +1 -1
- package/dist/lib/runtime/account-reset-credits.d.ts +11 -2
- package/dist/lib/runtime/account-reset-credits.js +35 -12
- package/dist/lib/runtime/automatic-account-checks.js +6 -5
- package/dist/lib/runtime/automatic-subscription-checks.js +4 -3
- package/dist/lib/runtime/model-discovery-status.js +2 -2
- package/dist/lib/runtime/reset-credit-routing.d.ts +11 -3
- package/dist/lib/runtime/reset-credit-routing.js +66 -21
- package/dist/lib/runtime/reset-credits.d.ts +31 -7
- package/dist/lib/runtime/reset-credits.js +135 -28
- package/dist/lib/runtime/rotation-proxy-state.d.ts +3 -0
- package/dist/lib/runtime/subscription-quota-order.d.ts +1 -0
- package/dist/lib/runtime/subscription-quota-order.js +7 -3
- package/dist/lib/runtime/workspace-model-scopes.d.ts +2 -0
- package/dist/lib/runtime/workspace-model-scopes.js +14 -4
- package/dist/lib/runtime-rotation-proxy.js +108 -29
- package/dist/lib/storage/snapshot-merge.js +15 -4
- package/dist/lib/web-dashboard-html.js +53 -53
- 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 +209 -209
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +446 -446
- package/scripts/codex-bin-resolver.js +332 -332
- package/scripts/codex-multi-auth.js +65 -65
- package/scripts/codex-routing.js +63 -63
- package/scripts/codex.js +7143 -7143
- 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
|
@@ -672,12 +672,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
672
672
|
capabilityPolicy: capabilityPolicyStore,
|
|
673
673
|
});
|
|
674
674
|
}
|
|
675
|
-
catch
|
|
676
|
-
logWarn("Runtime policy evaluation failed
|
|
677
|
-
error: policyError instanceof Error
|
|
678
|
-
? policyError.message
|
|
679
|
-
: String(policyError),
|
|
680
|
-
});
|
|
675
|
+
catch {
|
|
676
|
+
logWarn("Runtime policy evaluation failed; request blocked.");
|
|
681
677
|
}
|
|
682
678
|
usageRecorder = createRuntimeUsageRecorder({
|
|
683
679
|
source: "plugin-host",
|
|
@@ -692,6 +688,25 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
692
688
|
statusCode: null,
|
|
693
689
|
errorCode: "plugin_host_request_failed",
|
|
694
690
|
};
|
|
691
|
+
if (!runtimePolicyDecision) {
|
|
692
|
+
runtimeMetrics.failedRequests += 1;
|
|
693
|
+
runtimeMetrics.lastError = "Runtime policy unavailable";
|
|
694
|
+
syncRuntimeObservability(requestTraceId);
|
|
695
|
+
usageCompletion = {
|
|
696
|
+
outcome: "blocked",
|
|
697
|
+
statusCode: 503,
|
|
698
|
+
errorCode: "runtime_policy_unavailable",
|
|
699
|
+
};
|
|
700
|
+
return new Response(JSON.stringify({
|
|
701
|
+
error: {
|
|
702
|
+
message: "Runtime policy could not be loaded for this local request.",
|
|
703
|
+
code: "runtime_policy_unavailable",
|
|
704
|
+
},
|
|
705
|
+
}), {
|
|
706
|
+
status: 503,
|
|
707
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
708
|
+
});
|
|
709
|
+
}
|
|
695
710
|
if (runtimePolicyDecision && !runtimePolicyDecision.allowed) {
|
|
696
711
|
runtimeMetrics.failedRequests += 1;
|
|
697
712
|
runtimeMetrics.lastError = "Runtime policy blocked request";
|
|
@@ -715,6 +730,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
715
730
|
},
|
|
716
731
|
});
|
|
717
732
|
}
|
|
733
|
+
// This host cannot prove ordinary capacity and permitted reset
|
|
734
|
+
// recovery are exhausted, so emergency accounts stay reserved.
|
|
735
|
+
const blockedAccountIndexes = new Set([
|
|
736
|
+
...(runtimePolicyDecision?.blockedAccountIndexes ?? []),
|
|
737
|
+
...(runtimePolicyDecision?.emergencyAccountIndexes ?? []),
|
|
738
|
+
]);
|
|
718
739
|
const abortSignal = requestInit?.signal ?? init?.signal ?? null;
|
|
719
740
|
const sleep = createAbortableSleep(abortSignal);
|
|
720
741
|
const poolCooldownRemainingMs = getPoolExhaustionCooldownRemaining();
|
|
@@ -843,7 +864,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
843
864
|
typeof preferredSessionAccountIndex === "number") {
|
|
844
865
|
usedPreferredSessionAccount = true;
|
|
845
866
|
if (accountManager.isAccountAvailableForFamily(preferredSessionAccountIndex, modelFamily, model) &&
|
|
846
|
-
!
|
|
867
|
+
!blockedAccountIndexes.has(preferredSessionAccountIndex)) {
|
|
847
868
|
account = accountManager.getAccountByIndex(preferredSessionAccountIndex);
|
|
848
869
|
if (account) {
|
|
849
870
|
account.lastUsed = Date.now();
|
|
@@ -858,13 +879,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
858
879
|
account = accountManager.getCurrentOrNextForFamilyHybrid(modelFamily, model, {
|
|
859
880
|
pidOffsetEnabled,
|
|
860
881
|
scoreBoostByAccount: capabilityBoostByAccount,
|
|
882
|
+
blockedAccountIndexes,
|
|
861
883
|
});
|
|
862
884
|
}
|
|
863
885
|
if (!account || attempted.has(account.index)) {
|
|
864
886
|
break;
|
|
865
887
|
}
|
|
866
888
|
attempted.add(account.index);
|
|
867
|
-
if (
|
|
889
|
+
if (blockedAccountIndexes.has(account.index)) {
|
|
868
890
|
continue;
|
|
869
891
|
}
|
|
870
892
|
// Log account selection for debugging rotation
|
|
@@ -1451,7 +1473,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1451
1473
|
runtimeMetrics.cumulativeLatencyMs += fetchLatencyMs;
|
|
1452
1474
|
let responseForSuccess = response;
|
|
1453
1475
|
if (isStreaming) {
|
|
1454
|
-
const streamFallbackCandidateOrder = buildAdaptiveStreamFailoverCandidateOrder(account.index, accountSnapshotList);
|
|
1476
|
+
const streamFallbackCandidateOrder = buildAdaptiveStreamFailoverCandidateOrder(account.index, accountSnapshotList.filter((candidate) => !blockedAccountIndexes.has(candidate.index)));
|
|
1455
1477
|
runtimeMetrics.lastStreamFailoverCandidateCount =
|
|
1456
1478
|
streamFallbackCandidateOrder.length;
|
|
1457
1479
|
runtimeMetrics.streamFailoverCandidatesConsidered +=
|
|
@@ -1472,7 +1494,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1472
1494
|
if (abortSignal?.aborted) {
|
|
1473
1495
|
return null;
|
|
1474
1496
|
}
|
|
1475
|
-
if (
|
|
1497
|
+
if (blockedAccountIndexes.has(candidateIndex) ||
|
|
1498
|
+
!accountManager.isAccountAvailableForFamily(candidateIndex, modelFamily, model)) {
|
|
1476
1499
|
continue;
|
|
1477
1500
|
}
|
|
1478
1501
|
const fallbackAccount = accountManager.getAccountByIndex(candidateIndex);
|
|
@@ -7,6 +7,8 @@ export interface AccountPolicy {
|
|
|
7
7
|
priority?: number;
|
|
8
8
|
/** Opt in to periodic first-use completion for unused personal subscriptions. */
|
|
9
9
|
autoPrime?: boolean;
|
|
10
|
+
/** Pool backup only, after primary subscriptions and reset credits are exhausted. */
|
|
11
|
+
emergencyOnly?: boolean;
|
|
10
12
|
paused: boolean;
|
|
11
13
|
drained: boolean;
|
|
12
14
|
note: string | null;
|
|
@@ -37,6 +37,7 @@ function normalizePolicy(key, value) {
|
|
|
37
37
|
weight: normalizeWeight(record.weight),
|
|
38
38
|
priority: normalizePriority(record.priority),
|
|
39
39
|
autoPrime: record.autoPrime === true,
|
|
40
|
+
emergencyOnly: record.emergencyOnly === true,
|
|
40
41
|
paused: record.paused === true,
|
|
41
42
|
drained: record.drained === true,
|
|
42
43
|
note: note.length > 0 ? note.slice(0, 500) : null,
|
|
@@ -112,7 +113,7 @@ export async function loadAccountPolicyStore(options = {}) {
|
|
|
112
113
|
if (!key.startsWith("sha256:"))
|
|
113
114
|
continue;
|
|
114
115
|
// Legacy rows may omit flags; explicit malformed values are not OFF.
|
|
115
|
-
if (!isRecord(row) || ["autoPrime", "paused", "drained"].some(flag => flag in row && typeof row[flag] !== "boolean")) {
|
|
116
|
+
if (!isRecord(row) || ["autoPrime", "emergencyOnly", "paused", "drained"].some(flag => flag in row && typeof row[flag] !== "boolean")) {
|
|
116
117
|
throw new Error("Invalid account policy row");
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -241,6 +242,7 @@ export function upsertAccountPolicy(store, accountKey, mutate, now = Date.now())
|
|
|
241
242
|
next.weight = normalizeWeight(next.weight);
|
|
242
243
|
next.priority = normalizePriority(next.priority);
|
|
243
244
|
next.autoPrime = next.autoPrime === true;
|
|
245
|
+
next.emergencyOnly = next.emergencyOnly === true;
|
|
244
246
|
next.updatedAt = stampUpdatedAt(now, store.accounts[accountKey]?.updatedAt);
|
|
245
247
|
store.accounts[accountKey] = next;
|
|
246
248
|
return next;
|
|
@@ -15,6 +15,12 @@ export interface RateLimitState {
|
|
|
15
15
|
export interface RateLimitedEntity {
|
|
16
16
|
rateLimitResetTimes: RateLimitState;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Bound legacy persisted routing deadlines once, when loading the account.
|
|
20
|
+
* These deadlines schedule revalidation; quota snapshots retain upstream resets.
|
|
21
|
+
* Never call this on every selection: that would slide the deadline indefinitely.
|
|
22
|
+
*/
|
|
23
|
+
export declare function boundRateLimitCache(times: RateLimitState, now?: number): RateLimitState;
|
|
18
24
|
export declare function clearExpiredRateLimits(entity: RateLimitedEntity): void;
|
|
19
25
|
/**
|
|
20
26
|
* Remove every rate-limit reset entry from `entity`, regardless of whether the
|
|
@@ -2,7 +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
|
+
import { LUNA_RESERVE_MODEL, RATE_LIMIT_CACHE_TTL_MS } from "../constants.js";
|
|
6
6
|
import { nowMs } from "../utils.js";
|
|
7
7
|
export function parseRateLimitReason(code) {
|
|
8
8
|
if (!code)
|
|
@@ -28,6 +28,19 @@ export function clampNonNegativeInt(value, fallback) {
|
|
|
28
28
|
}
|
|
29
29
|
return value < 0 ? 0 : Math.floor(value);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Bound legacy persisted routing deadlines once, when loading the account.
|
|
33
|
+
* These deadlines schedule revalidation; quota snapshots retain upstream resets.
|
|
34
|
+
* Never call this on every selection: that would slide the deadline indefinitely.
|
|
35
|
+
*/
|
|
36
|
+
export function boundRateLimitCache(times, now = nowMs()) {
|
|
37
|
+
return Object.fromEntries(Object.entries(times).map(([key, until]) => [
|
|
38
|
+
key,
|
|
39
|
+
typeof until === "number"
|
|
40
|
+
? Math.min(until, now + RATE_LIMIT_CACHE_TTL_MS)
|
|
41
|
+
: until,
|
|
42
|
+
]));
|
|
43
|
+
}
|
|
31
44
|
export function clearExpiredRateLimits(entity) {
|
|
32
45
|
const now = nowMs();
|
|
33
46
|
const keys = Object.keys(entity.rateLimitResetTimes);
|
package/dist/lib/accounts.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export declare class AccountManager {
|
|
|
70
70
|
private readonly persistedWorkspaceSelections;
|
|
71
71
|
private readonly persistedWorkspaces;
|
|
72
72
|
private hadPersistedStorage;
|
|
73
|
+
private hasUnpersistedRateLimitBounds;
|
|
73
74
|
private readonly loadedAccountStorageSnapshot;
|
|
74
75
|
private cursorByFamily;
|
|
75
76
|
private currentAccountIndexByFamily;
|
|
@@ -334,7 +335,11 @@ export declare class AccountManager {
|
|
|
334
335
|
commitRefreshedAuth(source: Pick<ManagedAccount, "index" | "accountId" | "email" | "refreshToken">, auth: OAuthAuthDetails): Promise<ManagedAccount | null>;
|
|
335
336
|
toAuthDetails(account: ManagedAccount): Auth;
|
|
336
337
|
getMinWaitTime(): number;
|
|
337
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Accounts in `excludedIndexes` (policy-blocked or reserved for this request)
|
|
340
|
+
* are not capacity the caller can retry into, so they never shorten the wait.
|
|
341
|
+
*/
|
|
342
|
+
getMinWaitTimeForFamily(family: ModelFamily, model?: string | null, excludedIndexes?: ReadonlySet<number>): number;
|
|
338
343
|
/**
|
|
339
344
|
* Walks forward from `start` (inclusive) looking for the next enabled
|
|
340
345
|
* account, wrapping around at most once. Returns -1 when every account in
|
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, LUNA_RESERVE_MODEL,
|
|
12
|
+
import { ERROR_MESSAGES, HTTP_STATUS, LUNA_RESERVE_MODEL, RATE_LIMIT_CACHE_TTL_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";
|
|
@@ -21,7 +21,7 @@ export { extractAccountId, extractAccountEmail, getAccountIdCandidates, selectBe
|
|
|
21
21
|
export { parseRateLimitReason, getQuotaKey, clampNonNegativeInt, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForQuotaKey, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
22
22
|
export { lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./codex-cli/state.js";
|
|
23
23
|
import { codexCliAccountIdFor, extractAccountId, extractAccountEmail, shouldUpdateAccountIdFromToken, sanitizeEmail, } from "./auth/token-utils.js";
|
|
24
|
-
import { clampNonNegativeInt, getQuotaKey, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
24
|
+
import { boundRateLimitCache, clampNonNegativeInt, getQuotaKey, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
25
25
|
const log = createLogger("accounts");
|
|
26
26
|
let nextRuntimeCircuitKeyId = 0;
|
|
27
27
|
function getAccountCircuitKey(account) {
|
|
@@ -142,6 +142,7 @@ export class AccountManager {
|
|
|
142
142
|
persistedWorkspaceSelections = new WeakMap();
|
|
143
143
|
persistedWorkspaces = new WeakMap();
|
|
144
144
|
hadPersistedStorage = false;
|
|
145
|
+
hasUnpersistedRateLimitBounds = false;
|
|
145
146
|
loadedAccountStorageSnapshot;
|
|
146
147
|
cursorByFamily = initFamilyState(0);
|
|
147
148
|
currentAccountIndexByFamily = initFamilyState(-1);
|
|
@@ -201,6 +202,19 @@ export class AccountManager {
|
|
|
201
202
|
}
|
|
202
203
|
const manager = new AccountManager(authFallback, sourceOfTruthStorage);
|
|
203
204
|
await manager.hydrateFromCodexCli();
|
|
205
|
+
if (manager.hasUnpersistedRateLimitBounds) {
|
|
206
|
+
// Short-lived wrappers may exit without another save; persisting the
|
|
207
|
+
// bound now keeps each restart from re-bounding from its own load time.
|
|
208
|
+
try {
|
|
209
|
+
await manager.saveToDisk(true);
|
|
210
|
+
manager.hasUnpersistedRateLimitBounds = false;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
log.debug("Failed to persist bounded rate-limit deadlines", {
|
|
214
|
+
error: String(error),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
204
218
|
return manager;
|
|
205
219
|
}
|
|
206
220
|
/** @internal True when construction received a parsed/recovered storage snapshot. */
|
|
@@ -337,6 +351,11 @@ export class AccountManager {
|
|
|
337
351
|
return null;
|
|
338
352
|
}
|
|
339
353
|
const matchesFallback = !!authFallback && fallbackMatchedRowIndex === index;
|
|
354
|
+
const storedRateLimits = account.rateLimitResetTimes ?? {};
|
|
355
|
+
const rateLimitResetTimes = boundRateLimitCache(storedRateLimits, baseNow);
|
|
356
|
+
if (Object.entries(rateLimitResetTimes).some(([key, until]) => until !== storedRateLimits[key])) {
|
|
357
|
+
this.hasUnpersistedRateLimitBounds = true;
|
|
358
|
+
}
|
|
340
359
|
const refreshToken = matchesFallback && authFallback
|
|
341
360
|
? authFallback.refresh
|
|
342
361
|
: account.refreshToken;
|
|
@@ -362,7 +381,7 @@ export class AccountManager {
|
|
|
362
381
|
addedAt: clampNonNegativeInt(account.addedAt, baseNow),
|
|
363
382
|
lastUsed: clampNonNegativeInt(account.lastUsed, 0),
|
|
364
383
|
lastSwitchReason: account.lastSwitchReason,
|
|
365
|
-
rateLimitResetTimes
|
|
384
|
+
rateLimitResetTimes,
|
|
366
385
|
coolingDownUntil: account.coolingDownUntil,
|
|
367
386
|
cooldownReason: account.cooldownReason,
|
|
368
387
|
authInvalidatedAt: account.authInvalidatedAt,
|
|
@@ -1062,9 +1081,9 @@ export class AccountManager {
|
|
|
1062
1081
|
// Fail closed: retain this new limit if the selection cannot be read.
|
|
1063
1082
|
log.warn("Rate limit recorded while account selection metadata is unavailable");
|
|
1064
1083
|
}
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
const retryMs = Math.min(Math.max(0, Math.floor(retryAfterMs)),
|
|
1084
|
+
// A local rejection is authoritative for at most an hour. The next request
|
|
1085
|
+
// then revalidates upstream, including after an out-of-band quota reset.
|
|
1086
|
+
const retryMs = Math.min(Math.max(0, Math.floor(retryAfterMs)), RATE_LIMIT_CACHE_TTL_MS);
|
|
1068
1087
|
const observedAtMs = nowMs();
|
|
1069
1088
|
const resetAt = observedAtMs + retryMs;
|
|
1070
1089
|
// Only carry forward an unsequenced entry that is still replayable.
|
|
@@ -1561,9 +1580,15 @@ export class AccountManager {
|
|
|
1561
1580
|
getMinWaitTime() {
|
|
1562
1581
|
return this.getMinWaitTimeForFamily("codex");
|
|
1563
1582
|
}
|
|
1564
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* Accounts in `excludedIndexes` (policy-blocked or reserved for this request)
|
|
1585
|
+
* are not capacity the caller can retry into, so they never shorten the wait.
|
|
1586
|
+
*/
|
|
1587
|
+
getMinWaitTimeForFamily(family, model, excludedIndexes) {
|
|
1565
1588
|
const now = nowMs();
|
|
1566
|
-
const enabledAccounts = this.accounts.filter((account) => account.enabled !== false &&
|
|
1589
|
+
const enabledAccounts = this.accounts.filter((account) => account.enabled !== false &&
|
|
1590
|
+
!this.isAccountAuthInvalidated(account) &&
|
|
1591
|
+
!excludedIndexes?.has(account.index));
|
|
1567
1592
|
const available = enabledAccounts.filter((account) => {
|
|
1568
1593
|
clearExpiredRateLimits(account);
|
|
1569
1594
|
return (!isRateLimitedForFamily(account, family, model) &&
|
|
@@ -45,6 +45,11 @@ export declare function extractAccountId(accessToken?: string): string | undefin
|
|
|
45
45
|
*/
|
|
46
46
|
/** Whether an id is an OpenAI platform org id ("org-..."), never a ChatGPT workspace. */
|
|
47
47
|
export declare function isOpenAiOrgId(accountId: string | undefined): boolean;
|
|
48
|
+
/** Preserve the native client's org-alias mapping; this is not workspace fallback. */
|
|
49
|
+
export declare function nativeWorkspaceIdentity(account: {
|
|
50
|
+
access?: string;
|
|
51
|
+
codexCliMirror?: CodexCliMirror;
|
|
52
|
+
}, storedId: string | undefined): string | undefined;
|
|
48
53
|
export declare function resolveCodexAuthAccountId(accountId: string | undefined, accessToken?: string, idToken?: string): string | undefined;
|
|
49
54
|
/**
|
|
50
55
|
* The account id to hand the ~/.codex/auth.json writer for a saved account,
|
|
@@ -261,6 +261,16 @@ export function extractAccountId(accessToken) {
|
|
|
261
261
|
export function isOpenAiOrgId(accountId) {
|
|
262
262
|
return /^org-/i.test(accountId?.trim() ?? "");
|
|
263
263
|
}
|
|
264
|
+
/** Preserve the native client's org-alias mapping; this is not workspace fallback. */
|
|
265
|
+
export function nativeWorkspaceIdentity(account, storedId) {
|
|
266
|
+
const id = storedId?.trim();
|
|
267
|
+
if (!id)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (!isOpenAiOrgId(id))
|
|
270
|
+
return id;
|
|
271
|
+
const native = codexCliAccountIdFor({ accountId: id, codexCliMirror: account.codexCliMirror }, account.access);
|
|
272
|
+
return native && !isOpenAiOrgId(native) ? native : undefined;
|
|
273
|
+
}
|
|
264
274
|
export function resolveCodexAuthAccountId(accountId, accessToken, idToken) {
|
|
265
275
|
const trimmed = accountId?.trim();
|
|
266
276
|
if (!trimmed)
|
|
@@ -7,6 +7,7 @@ function printAccountUsage(logInfo) {
|
|
|
7
7
|
" codex-multi-auth account weight <index> <0..10>",
|
|
8
8
|
" codex-multi-auth account priority <index> <0..9>",
|
|
9
9
|
" codex-multi-auth account auto-prime <index> on|off",
|
|
10
|
+
" codex-multi-auth account emergency-only <index> on|off",
|
|
10
11
|
" codex-multi-auth account pause|unpause|drain|undrain <index>",
|
|
11
12
|
" codex-multi-auth account note <index> <text>",
|
|
12
13
|
" codex-multi-auth account policy list [--json]",
|
|
@@ -37,6 +38,7 @@ function policySummary(store, storage) {
|
|
|
37
38
|
weight: policy?.weight ?? 1,
|
|
38
39
|
priority: policy?.priority ?? 1,
|
|
39
40
|
autoPrime: policy?.autoPrime ?? false,
|
|
41
|
+
emergencyOnly: policy?.emergencyOnly ?? false,
|
|
40
42
|
paused: policy?.paused ?? false,
|
|
41
43
|
drained: policy?.drained ?? false,
|
|
42
44
|
note: policy?.note ?? null,
|
|
@@ -84,6 +86,7 @@ export async function runAccountCommand(args, deps) {
|
|
|
84
86
|
`weight=${entry.weight}`,
|
|
85
87
|
`priority=${entry.priority}`,
|
|
86
88
|
`auto-prime=${entry.autoPrime ? "on" : "off"}`,
|
|
89
|
+
entry.emergencyOnly ? "emergency-only" : null,
|
|
87
90
|
entry.paused ? "paused" : null,
|
|
88
91
|
entry.drained ? "drained" : null,
|
|
89
92
|
entry.tags.length > 0 ? `tags=${entry.tags.join(",")}` : null,
|
|
@@ -120,6 +123,18 @@ export async function runAccountCommand(args, deps) {
|
|
|
120
123
|
logInfo(`${command === "tag" ? "Tagged" : "Removed tag from"} account ${resolved.index + 1}: ${policy.tags.join(",") || "none"}`);
|
|
121
124
|
return 0;
|
|
122
125
|
}
|
|
126
|
+
if (command === "emergency-only") {
|
|
127
|
+
if (rest.length !== 2 || !["on", "off"].includes(rest[1] ?? "")) {
|
|
128
|
+
logError("emergency-only requires on or off.");
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
await updateStore(store => ({
|
|
132
|
+
result: upsertAccountPolicy(store, accountKey, next => { next.emergencyOnly = rest[1] === "on"; }, now),
|
|
133
|
+
dirty: true,
|
|
134
|
+
}));
|
|
135
|
+
logInfo(`Emergency-only ${rest[1]} for account ${resolved.index + 1}.`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
123
138
|
if (command === "auto-prime") {
|
|
124
139
|
if (rest.length !== 2 || !["on", "off"].includes(rest[1] ?? "")) {
|
|
125
140
|
logError("auto-prime requires on or off.");
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { AccountManager } from '../../accounts.js';
|
|
2
2
|
import { loadAccounts } from '../../storage.js';
|
|
3
3
|
import { runWithGlobalStoragePath } from '../../storage/path-state.js';
|
|
4
|
-
import { createResetCreditService, resetTargetForStoredAccount, resetAccountLabel } from '../../runtime/account-reset-credits.js';
|
|
4
|
+
import { createResetCreditService, resetTargetForStoredAccount, resetAccountLabel, formatResetCreditLine } from '../../runtime/account-reset-credits.js';
|
|
5
5
|
import { withCheckProgress } from '../../ui/check-progress.js';
|
|
6
|
+
// A pending result may belong to a workspace that was since disabled or deselected.
|
|
7
|
+
function ownsPendingKey(account, key) {
|
|
8
|
+
const workspaces = account.workspaces?.map(workspace => ({ ...workspace, enabled: true }));
|
|
9
|
+
const selections = [undefined, ...(workspaces ?? []).map((_, index) => index)];
|
|
10
|
+
return selections.some(index => resetTargetForStoredAccount({ ...account, enabled: true, workspaces: index === undefined ? undefined : workspaces, currentWorkspaceIndex: index ?? account.currentWorkspaceIndex })?.key === key);
|
|
11
|
+
}
|
|
6
12
|
const usage = 'Usage: codex-multi-auth resets list [--refresh] | redeem <account-number> | auto manual|last-resort';
|
|
7
13
|
export async function runResetsCommand(args) {
|
|
8
14
|
const [command = 'list', value, ...extra] = args;
|
|
@@ -47,7 +53,13 @@ export async function runResetsCommand(args) {
|
|
|
47
53
|
const index = (storage?.accounts ?? []).findIndex(a => resetTargetForStoredAccount(a)?.key === state.lastRedemption?.key);
|
|
48
54
|
console.log(`Last confirmed reset: ${index >= 0 ? `account ${index + 1}` : "removed account"}; ${state.lastRedemption.outcome}; ${state.lastRedemption.automatic ? "automatic" : "explicit"}`);
|
|
49
55
|
}
|
|
50
|
-
(storage?.accounts ?? []).forEach((a, i) => {
|
|
56
|
+
(storage?.accounts ?? []).forEach((a, i) => {
|
|
57
|
+
const target = resetTargetForStoredAccount(a);
|
|
58
|
+
const ambiguous = target && (keyCounts.get(target.key) ?? 0) > 1;
|
|
59
|
+
const snapshot = target && !ambiguous ? (refreshed ?? state.snapshots)[target.key] : undefined;
|
|
60
|
+
console.log(`${resetAccountLabel(a, i)}: ${snapshot?.availableCount ?? 'unknown'} reset credits${snapshot ? ` (checked ${Math.max(0, Math.floor((Date.now() - snapshot.updatedAt) / 1000))}s ago)` : ''}${ambiguous ? ' [ambiguous account identity; repair duplicate records]' : ''}${target?.key === state.pending?.key ? ' [redemption pending; retry this account]' : ''}`);
|
|
61
|
+
snapshot?.credits?.forEach((c, idx) => console.log(formatResetCreditLine(c, idx)));
|
|
62
|
+
});
|
|
51
63
|
const failed = refreshed ? targets.filter(target => (keyCounts.get(target.key) ?? 0) > 1 || !refreshed[target.key]).length : 0;
|
|
52
64
|
if (failed) {
|
|
53
65
|
console.error(`${failed} of ${targets.length} reset-credit reads failed. Check account authentication and the native Codex backend (CODEX_MULTI_AUTH_USAGE_CODEX_BIN selects an executable). No credits were redeemed.`);
|
|
@@ -61,10 +73,11 @@ export async function runResetsCommand(args) {
|
|
|
61
73
|
const pending = command === 'redeem' ? await service.status().then(s => s.pending ?? null, () => ({ key: undefined })) : null;
|
|
62
74
|
// The pending record can belong to another account (a redeem is refused while
|
|
63
75
|
// any result is pending); point at the account that actually needs the retry.
|
|
64
|
-
const
|
|
76
|
+
const pendingKey = pending?.key;
|
|
77
|
+
const owner = pendingKey ? (storage?.accounts ?? []).findIndex(a => ownsPendingKey(a, pendingKey)) : -1;
|
|
65
78
|
const pendingAccount = owner >= 0 ? storage?.accounts[owner] : undefined;
|
|
66
|
-
const disabled = pendingAccount ? resetTargetForStoredAccount(pendingAccount)
|
|
67
|
-
const retry = disabled ? `account ${owner + 1} or its workspace is disabled; re-enable it before retrying the pending result` : pending?.key && owner < 0 ? 'check it there (the pending account was removed)' : owner >= 0 && owner !== Number(value) - 1 ? `retry account ${owner + 1}, which holds the pending result` : 'retry the same account';
|
|
79
|
+
const disabled = pendingAccount ? resetTargetForStoredAccount(pendingAccount)?.key !== pendingKey : false;
|
|
80
|
+
const retry = disabled ? `account ${owner + 1} or its pending workspace is disabled or no longer selected; re-enable and select it before retrying the pending result` : pending?.key && owner < 0 ? 'check it there (the pending account was removed)' : owner >= 0 && owner !== Number(value) - 1 ? `retry account ${owner + 1}, which holds the pending result` : 'retry the same account';
|
|
68
81
|
console.error(command === 'list' ? 'Reset-credit availability could not be refreshed. No credits were redeemed.' : command === 'auto' ? 'Reset-credit settings could not be updated.' : pending ? `Reset operation could not be confirmed. No new automatic redemption will be attempted while a result is pending; use resets list and ${retry}.` : 'No reset credit was redeemed. Availability could not be confirmed or a reset was just redeemed; run resets list --refresh and try again.');
|
|
69
82
|
return 1;
|
|
70
83
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type { AccountStorageV3 } from "../../storage.js";
|
|
1
|
+
import type { AccountStorageV3, withAccountStorageTransaction } from "../../storage.js";
|
|
2
2
|
type LoadedStorage = AccountStorageV3 | null;
|
|
3
3
|
export interface WorkspaceCommandDeps {
|
|
4
4
|
setStoragePath: (path: string | null) => void;
|
|
5
5
|
loadAccounts: () => Promise<LoadedStorage>;
|
|
6
6
|
saveAccounts: (storage: AccountStorageV3) => Promise<void>;
|
|
7
|
+
withAccountStorageTransaction: typeof withAccountStorageTransaction;
|
|
7
8
|
logError?: (message: string) => void;
|
|
8
9
|
logInfo?: (message: string) => void;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* `codex-multi-auth workspace <account> [workspace]`
|
|
12
13
|
*
|
|
13
|
-
* With only an account index, lists the workspaces that account can
|
|
14
|
+
* With only an account index, lists the workspaces that account can select
|
|
14
15
|
* between (personal Plus vs business/team under one email, issue #491). With a
|
|
15
16
|
* workspace index too, sets it as the active workspace for that account.
|
|
17
|
+
* A final enable/disable action changes availability without changing selection.
|
|
16
18
|
*/
|
|
17
19
|
export declare function runWorkspaceCommand(args: string[], deps: WorkspaceCommandDeps): Promise<number>;
|
|
18
20
|
export {};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { formatAccountLabel, formatWorkspaceLines, } from "../../accounts.js";
|
|
1
|
+
import { formatAccountLabel, formatWorkspaceLines, resolveAccountRecordId, } from "../../accounts.js";
|
|
2
2
|
import { saveAccountsWithRetry } from "../forecast-report-shared.js";
|
|
3
3
|
/**
|
|
4
4
|
* `codex-multi-auth workspace <account> [workspace]`
|
|
5
5
|
*
|
|
6
|
-
* With only an account index, lists the workspaces that account can
|
|
6
|
+
* With only an account index, lists the workspaces that account can select
|
|
7
7
|
* between (personal Plus vs business/team under one email, issue #491). With a
|
|
8
8
|
* workspace index too, sets it as the active workspace for that account.
|
|
9
|
+
* A final enable/disable action changes availability without changing selection.
|
|
9
10
|
*/
|
|
10
11
|
export async function runWorkspaceCommand(args, deps) {
|
|
11
12
|
deps.setStoragePath(null);
|
|
@@ -76,6 +77,36 @@ export async function runWorkspaceCommand(args, deps) {
|
|
|
76
77
|
return 1;
|
|
77
78
|
}
|
|
78
79
|
const targetName = target.name?.trim() || "(unnamed)";
|
|
80
|
+
const action = args[2];
|
|
81
|
+
if (args.length > 3 || (action && action !== "enable" && action !== "disable")) {
|
|
82
|
+
logError("Invalid workspace action. Use enable or disable.");
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (action) {
|
|
86
|
+
const recordId = resolveAccountRecordId(account);
|
|
87
|
+
const workspaceId = target.id;
|
|
88
|
+
return deps.withAccountStorageTransaction(async (current, persist) => {
|
|
89
|
+
// An explicit command is an operation, even if its initial snapshot already
|
|
90
|
+
// had that value. Apply it to the latest row under the storage lock.
|
|
91
|
+
const matches = current?.accounts.filter(row => resolveAccountRecordId(row) === recordId) ?? [];
|
|
92
|
+
const latest = matches.length === 1 ? matches[0] : undefined;
|
|
93
|
+
const latestWorkspaces = latest?.workspaces ?? [];
|
|
94
|
+
const workspaceMatches = latestWorkspaces.filter(row => row.id === workspaceId);
|
|
95
|
+
const workspace = workspaceMatches.length === 1 ? workspaceMatches[0] : undefined;
|
|
96
|
+
if (!current || !latest || !workspace) {
|
|
97
|
+
logError("Account or workspace changed concurrently; reload before changing workspace availability.");
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
workspace.enabled = action === "enable";
|
|
101
|
+
// Absence of disabledAt distinguishes an operator exclusion from runtime health.
|
|
102
|
+
delete workspace.disabledAt;
|
|
103
|
+
await saveAccountsWithRetry(current, persist);
|
|
104
|
+
const currentIndex = latestWorkspaces.indexOf(workspace) + 1;
|
|
105
|
+
const currentName = workspace.name?.trim() || "(unnamed)";
|
|
106
|
+
logInfo(`Workspace ${currentIndex} [${currentName}] ${workspace.enabled ? "enabled" : "disabled"}. Selected workspace unchanged.`);
|
|
107
|
+
return 0;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
79
110
|
if (target.enabled === false) {
|
|
80
111
|
logError(`Workspace ${parsedWorkspace} ([${targetName}]) is disabled and cannot be selected.`);
|
|
81
112
|
return 1;
|
|
@@ -17,10 +17,10 @@ export function printUsage() {
|
|
|
17
17
|
" codex-multi-auth switch <index> (pins the account for runtime routing)",
|
|
18
18
|
" codex-multi-auth resets list [--refresh] | redeem <account> | auto manual|last-resort",
|
|
19
19
|
" codex-multi-auth unpin (clears the manual pin set by switch)",
|
|
20
|
-
" codex-multi-auth workspace <account> [workspace] (list or
|
|
20
|
+
" codex-multi-auth workspace <account> [workspace] (list or select; add enable/disable to change availability)",
|
|
21
21
|
" codex-multi-auth best [--live] [--json] [--model <model>] (clears any manual pin set by switch)",
|
|
22
22
|
" codex-multi-auth forecast [--live] [--json] [--explain] [--model <model>] [--no-runtime-overlay]",
|
|
23
|
-
" codex-multi-auth account tag|untag|weight|priority|auto-prime|pause|unpause|drain|undrain|note|policy list ...",
|
|
23
|
+
" codex-multi-auth account tag|untag|weight|priority|auto-prime|emergency-only|pause|unpause|drain|undrain|note|policy list ...",
|
|
24
24
|
"",
|
|
25
25
|
"Repair:",
|
|
26
26
|
" codex-multi-auth uninstall [--dry-run] [--json] [--clear-accounts]",
|
|
@@ -63,7 +63,7 @@ import { loadQuotaCache, saveQuotaCache } from "./quota-cache.js";
|
|
|
63
63
|
import { readAppRuntimeHelperAccountSignal } from "./runtime/runtime-current-account.js";
|
|
64
64
|
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
|
|
65
65
|
import { queuedRefresh } from "./refresh-queue.js";
|
|
66
|
-
import { clearAccounts, inspectStorageHealth, getLastAccountsSaveTimestamp, getStoragePath, loadAccounts, loadFlaggedAccounts, saveAccounts, setStoragePath, } from "./storage.js";
|
|
66
|
+
import { clearAccounts, inspectStorageHealth, getLastAccountsSaveTimestamp, getStoragePath, loadAccounts, loadFlaggedAccounts, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "./storage.js";
|
|
67
67
|
// Formatter implementations moved to lib/codex-manager/formatters/ (audit
|
|
68
68
|
// roadmap §4.1.1 phase 1). Re-exported here so existing consumers importing
|
|
69
69
|
// from lib/codex-manager.js keep working unchanged.
|
|
@@ -256,6 +256,18 @@ export function buildSelectAccountTraced() {
|
|
|
256
256
|
blockedReasonByAccount[index] = AUTH_INVALIDATION_MARKER;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
// This offline diagnostic cannot establish ordinary-pool exhaustion or
|
|
260
|
+
// perform permitted reset recovery. Leave emergency capacity reserved;
|
|
261
|
+
// keep any more specific policy, authentication, or runtime exclusion.
|
|
262
|
+
for (const account of accountsWithMetrics) {
|
|
263
|
+
if (policy.emergencyAccountIndexes?.has(account.index) &&
|
|
264
|
+
account.isAvailable &&
|
|
265
|
+
!blockedAccountIndexes.has(account.index)) {
|
|
266
|
+
blockedAccountIndexes.add(account.index);
|
|
267
|
+
blockedReasonByAccount[account.index] =
|
|
268
|
+
"emergency-only: reserved until runtime confirms ordinary capacity and permitted resets are exhausted";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
259
271
|
return selectHybridAccountTraced({
|
|
260
272
|
accounts: accountsWithMetrics,
|
|
261
273
|
healthTracker,
|
|
@@ -368,6 +380,7 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
368
380
|
setStoragePath,
|
|
369
381
|
loadAccounts,
|
|
370
382
|
saveAccounts,
|
|
383
|
+
withAccountStorageTransaction,
|
|
371
384
|
}),
|
|
372
385
|
],
|
|
373
386
|
["check", (rest) => runCheckCommand({
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -116,3 +116,5 @@ export type WireReasoningEffort = Exclude<ModelReasoningEffort, "ultra">;
|
|
|
116
116
|
* untouched.
|
|
117
117
|
*/
|
|
118
118
|
export declare function stripModelEffortSuffix(modelId: string): string;
|
|
119
|
+
/** Maximum lifetime of a local quota rejection before upstream revalidation. */
|
|
120
|
+
export declare const RATE_LIMIT_CACHE_TTL_MS: number;
|
package/dist/lib/constants.js
CHANGED
|
@@ -136,3 +136,5 @@ export function stripModelEffortSuffix(modelId) {
|
|
|
136
136
|
return modelId;
|
|
137
137
|
return modelId.replace(EFFORT_SUFFIX_PATTERN, "");
|
|
138
138
|
}
|
|
139
|
+
/** Maximum lifetime of a local quota rejection before upstream revalidation. */
|
|
140
|
+
export const RATE_LIMIT_CACHE_TTL_MS = 60 * 60_000;
|
|
@@ -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
|
/**
|