@bitkyc08/opencodex 2.69.0 → 2.70.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/gui/dist/assets/App-_MrbTrBe.js +51 -0
- package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
- package/gui/dist/assets/index-DKrO2eF3.js +86 -0
- package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-model-contract.ts +119 -0
- package/src/adapters/anthropic.ts +14 -83
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +542 -0
- package/src/adapters/command-code-tool-text.ts +99 -67
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
- package/src/adapters/devin/cloud-direct/chat.ts +78 -48
- package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
- package/src/adapters/devin/context-overflow.ts +75 -0
- package/src/adapters/devin/live-models.ts +174 -13
- package/src/adapters/devin/reasoning-signature.ts +100 -0
- package/src/adapters/devin.ts +310 -117
- package/src/adapters/kiro/payload.ts +10 -10
- package/src/adapters/kiro-images.ts +58 -7
- package/src/adapters/openai-chat/passthrough.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/bridge/response-json.ts +15 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/cli/account-extended.ts +31 -1
- package/src/cli/account.ts +3 -0
- package/src/cli/capabilities.ts +13 -0
- package/src/cli/client-start-fence.ts +36 -0
- package/src/cli/dispatch.ts +3 -2
- package/src/cli/index.ts +98 -117
- package/src/cli/provider-runtime.ts +23 -2
- package/src/cli/provider.ts +3 -0
- package/src/cli/resolve.ts +18 -0
- package/src/cli/start-owner-fence.ts +30 -0
- package/src/cli/stop-approval.ts +13 -1
- package/src/cli/stop-restore.ts +73 -0
- package/src/cli/tray-proxy.ts +221 -18
- package/src/cli/version-skew.ts +25 -23
- package/src/client/runtime.ts +8 -4
- package/src/codex/auth-api/account-list.ts +11 -5
- package/src/codex/auth-api/login-flow.ts +5 -3
- package/src/codex/auth-api/main-account-probe.ts +186 -110
- package/src/codex/auth-api/pool-mode-gate.ts +61 -7
- package/src/codex/auth-api/pool-quota-probe.ts +142 -90
- package/src/codex/auth-api/reset-credit-service.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +1 -0
- package/src/codex/catalog/model-hints.ts +3 -3
- package/src/codex/catalog/provider-models.ts +2 -0
- package/src/codex/low-quota-events.ts +28 -0
- package/src/codex/low-quota-observer.ts +23 -0
- package/src/codex/low-quota-protection.ts +272 -0
- package/src/codex/quota-query-backoff.ts +148 -0
- package/src/codex/quota.ts +7 -2
- package/src/config/diagnostics.ts +10 -1
- package/src/config/load-degrade.ts +17 -0
- package/src/config/mutation-lock.ts +3 -4
- package/src/config/process-state.ts +1 -1
- package/src/config/provider-validation.ts +22 -0
- package/src/config/schema/blocked-model-redirects.ts +17 -0
- package/src/config/schema/config-schema.ts +4 -1
- package/src/config/schema/leaf-validators.ts +33 -2
- package/src/config/serving-runtimes.ts +412 -0
- package/src/generated/compatibility-version.json +211 -115
- package/src/generated/model-metadata.ts +3 -3
- package/src/images/loop.ts +3 -0
- package/src/lib/errors.ts +2 -1
- package/src/lib/handled-signal-exit.ts +23 -0
- package/src/lib/request-failure-model.ts +1 -0
- package/src/lib/self-launch-argv.ts +9 -0
- package/src/lib/shadow-call.ts +5 -2
- package/src/lib/strict-semver.ts +26 -0
- package/src/lib/transient-service-path.ts +16 -0
- package/src/lib/windows-service-wrappers.ts +10 -6
- package/src/lib/winsw.ts +3 -1
- package/src/oauth/anthropic-model-routes.ts +105 -0
- package/src/oauth/anthropic-routing.ts +85 -48
- package/src/oauth/devin.ts +135 -5
- package/src/oauth/index.ts +20 -3
- package/src/oauth/kiro-terminal-failover.ts +23 -9
- package/src/oauth/pool-settings-capability.ts +10 -2
- package/src/oauth/store.ts +1 -1
- package/src/protocols/encoders/adapter-events.ts +3 -1
- package/src/providers/github-copilot-context.ts +27 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/quota/account-cache.ts +8 -3
- package/src/providers/quota/devin.ts +286 -0
- package/src/providers/quota/vendor-probes-oauth.ts +6 -2
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +1 -1
- package/src/providers/registry/model-seeds.ts +5 -2
- package/src/responses/compaction-images.ts +27 -0
- package/src/router.ts +104 -16
- package/src/routing/analytics.ts +1 -0
- package/src/server/auth-cors.ts +9 -1
- package/src/server/background-lifecycle.ts +21 -10
- package/src/server/chat-completions.ts +2 -1
- package/src/server/chat-native.ts +1 -0
- package/src/server/index.ts +4 -8
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/management/context.ts +3 -0
- package/src/server/management/low-quota-routes.ts +14 -0
- package/src/server/management/oauth-account-routes.ts +21 -1
- package/src/server/management/provider-overwrite-carry.ts +79 -6
- package/src/server/management/provider-routes.ts +41 -28
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management-api.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +3 -0
- package/src/server/responses/adapter-delivery.ts +3 -0
- package/src/server/responses/adapter-dispatch.ts +20 -4
- package/src/server/responses/antigravity-validation-refusal.ts +20 -0
- package/src/server/responses/core-auth.ts +1 -0
- package/src/server/responses/core-codex-account.ts +1 -0
- package/src/server/responses/core-normalize.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +8 -1
- package/src/server/responses/request-sidecar-auth.ts +2 -0
- package/src/server/responses/request-transport.ts +27 -10
- package/src/server/responses/response-effects.ts +4 -1
- package/src/server/responses/run-turn-execution.ts +126 -42
- package/src/server/responses/sidecar-execution.ts +4 -1
- package/src/service/diagnostics.ts +3 -1
- package/src/service/guarded-manager-target.ts +39 -12
- package/src/service/launchd.ts +14 -6
- package/src/service/repair.ts +6 -1
- package/src/service/service-child-ownership.ts +168 -0
- package/src/service/state.ts +22 -4
- package/src/service/systemd.ts +2 -2
- package/src/service/windows-guarded-manager.ts +530 -0
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service.ts +2 -0
- package/src/types/config.ts +26 -0
- package/src/types/provider.ts +12 -0
- package/src/types/request.ts +2 -0
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +13 -0
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/vision/anthropic-describe.ts +2 -1
- package/src/web-search/anthropic-executor.ts +2 -1
- package/src/web-search/loop.ts +1 -0
- package/gui/dist/assets/App-DDbgOTh6.js +0 -51
- package/gui/dist/assets/index-CuaBJkG4.js +0 -86
- package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { fetchCodexUsage } from "../quota-query-backoff";
|
|
2
|
+
import type { CodexUsageOwner } from "../quota-query-backoff";
|
|
3
|
+
import { loadConfig } from "../../config";
|
|
4
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
5
|
+
import { providerCodexAccountMode } from "../../providers/registry";
|
|
6
|
+
import { isSelectableCodexPoolAccount } from "../account-id";
|
|
7
|
+
import type { OcxConfig } from "../../types";
|
|
1
8
|
import { parseMainPolicyUsageQuota, parseUsageQuota, setAccountQuotaFromParsed } from "../quota";
|
|
2
9
|
import type { StoredAccountQuota, WhamUsageResponse } from "../quota";
|
|
3
10
|
import { reconcileMainCodexAccountRuntimeState } from "../account-lifecycle";
|
|
@@ -15,7 +22,7 @@ import { nonEmptyPlan } from "./runtime-config";
|
|
|
15
22
|
import { tryAcquireNativeMainProfileClaim } from "../native-main-admission";
|
|
16
23
|
import { WHAM_REQUEST_TIMEOUT_MS } from "../quota-recovery-timing";
|
|
17
24
|
import { withNativeMainCredentialClaim, isNativeMainClaimUnavailable } from "./http";
|
|
18
|
-
import { MAIN_TERMINAL_AUTH_CODES, readMainAuthErrorCode, nextQuotaDispatchSequence, isQuotaDispatchCurrent, publishQuotaDispatch } from "./pool-quota-probe";
|
|
25
|
+
import { MAIN_TERMINAL_AUTH_CODES, readMainAuthErrorCode, currentQuotaDispatchSequence, nextQuotaDispatchSequence, isQuotaDispatchCurrent, publishQuotaDispatch } from "./pool-quota-probe";
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Last reset-credit count this process parsed for the main account, tagged with the
|
|
@@ -52,6 +59,14 @@ export function mainResetCreditsForCurrentIdentity(): number | undefined {
|
|
|
52
59
|
|
|
53
60
|
export const MAIN_CACHE_TTL = 5 * 60_000;
|
|
54
61
|
|
|
62
|
+
function sharedMainQuotaPacingEnabled(config: OcxConfig): boolean {
|
|
63
|
+
const openai = config.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
64
|
+
if (openai?.disabled === true || openai?.codexAccountMode === "direct") return false;
|
|
65
|
+
return (openai !== undefined && isCanonicalOpenAiForwardProvider(openai)
|
|
66
|
+
&& providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, openai) === "pool")
|
|
67
|
+
|| (config.codexAccounts ?? []).some(isSelectableCodexPoolAccount);
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
/**
|
|
56
71
|
* A WHAM 401 is not itself proof the local credential died. Upstream edges can
|
|
57
72
|
* transiently reject a still-valid access token (region/anti-abuse/rotation
|
|
@@ -80,6 +95,8 @@ export interface MainResetQuotaProof {
|
|
|
80
95
|
|
|
81
96
|
export interface MainAccountInfoFetchResult {
|
|
82
97
|
info: MainAccountInfo;
|
|
98
|
+
/** Parsed ordinary info from a stale credential; callers must not display it as shared state. */
|
|
99
|
+
infoUnpublished?: true;
|
|
83
100
|
resetRecoveryProof?: MainResetQuotaProof & { dispatchSequence: number };
|
|
84
101
|
/** Ephemeral result of this attempt, omitted when no WHAM request was made. */
|
|
85
102
|
quotaRefresh?: CodexQuotaRefreshOutcome;
|
|
@@ -91,22 +108,25 @@ export interface MainAccountInfoFetchResult {
|
|
|
91
108
|
hasCredential: boolean;
|
|
92
109
|
/** Main identity generation captured while the native-main claim was held. */
|
|
93
110
|
identityGeneration?: number;
|
|
94
|
-
/**
|
|
111
|
+
/** Freshly parsed usage from the current credential; stale ordinary return values are excluded. */
|
|
95
112
|
freshQuota?: Omit<StoredAccountQuota, "updatedAt">;
|
|
96
|
-
/**
|
|
113
|
+
/** Current-credential response's `rate_limit_reset_credits.available_count`, when present. */
|
|
97
114
|
freshResetCredits?: number;
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
export interface MainAccountInfoSnapshot {
|
|
101
118
|
info: MainAccountInfo;
|
|
119
|
+
/** Ordinary info that was never published and must not become provider quota. */
|
|
120
|
+
infoUnpublished?: true;
|
|
102
121
|
mainIdentityGeneration: number;
|
|
103
122
|
quotaRefresh?: CodexQuotaRefreshOutcome;
|
|
104
123
|
}
|
|
105
124
|
|
|
106
|
-
export async function fetchMainAccountInfoSnapshot(forceRefresh = false): Promise<MainAccountInfoSnapshot> {
|
|
107
|
-
const result = await fetchMainAccountInfoAttempt(forceRefresh, 1);
|
|
125
|
+
export async function fetchMainAccountInfoSnapshot(forceRefresh = false, config?: OcxConfig): Promise<MainAccountInfoSnapshot> {
|
|
126
|
+
const result = await fetchMainAccountInfoAttempt(forceRefresh, 1, undefined, false, forceRefresh, false, config);
|
|
108
127
|
return {
|
|
109
128
|
info: result.info,
|
|
129
|
+
...(result.infoUnpublished ? { infoUnpublished: true as const } : {}),
|
|
110
130
|
...(result.quotaRefresh && result.quotaRefreshGeneration !== undefined
|
|
111
131
|
&& isMainAccountIdentityGenerationLive(result.quotaRefreshGeneration)
|
|
112
132
|
? { quotaRefresh: result.quotaRefresh } : {}),
|
|
@@ -114,8 +134,8 @@ export async function fetchMainAccountInfoSnapshot(forceRefresh = false): Promis
|
|
|
114
134
|
};
|
|
115
135
|
}
|
|
116
136
|
|
|
117
|
-
export async function fetchMainAccountInfo(forceRefresh = false): Promise<MainAccountInfo> {
|
|
118
|
-
return (await fetchMainAccountInfoSnapshot(forceRefresh)).info;
|
|
137
|
+
export async function fetchMainAccountInfo(forceRefresh = false, config?: OcxConfig): Promise<MainAccountInfo> {
|
|
138
|
+
return (await fetchMainAccountInfoSnapshot(forceRefresh, config)).info;
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
export const EMPTY_MAIN_ACCOUNT_INFO: MainAccountInfo = { email: null, plan: null, quota: null };
|
|
@@ -125,12 +145,13 @@ export async function retryMainAccountInfoIfIdentityChanged(
|
|
|
125
145
|
retriesRemaining: number,
|
|
126
146
|
nativeMainLease: AdmissionLease,
|
|
127
147
|
explicitRefresh: boolean,
|
|
148
|
+
paced: boolean,
|
|
128
149
|
): Promise<MainAccountInfoFetchResult | null> {
|
|
129
150
|
const currentAccountId = getMainChatgptAccountId();
|
|
130
151
|
if (currentAccountId === null || currentAccountId === requestAccountId) return null;
|
|
131
152
|
reconcileMainCodexAccountRuntimeState();
|
|
132
153
|
return retriesRemaining > 0
|
|
133
|
-
? fetchMainAccountInfoWhileOwned(true, retriesRemaining - 1, nativeMainLease, explicitRefresh)
|
|
154
|
+
? fetchMainAccountInfoWhileOwned(true, retriesRemaining - 1, nativeMainLease, explicitRefresh, false, paced)
|
|
134
155
|
: { info: EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: true, hasCredential: true };
|
|
135
156
|
}
|
|
136
157
|
|
|
@@ -140,6 +161,8 @@ export async function fetchMainAccountInfoAttempt(
|
|
|
140
161
|
existingNativeMainLease?: AdmissionLease,
|
|
141
162
|
nativeMainSharedClaimHeld = false,
|
|
142
163
|
explicitRefresh: boolean = forceRefresh,
|
|
164
|
+
postReset = false,
|
|
165
|
+
config?: OcxConfig,
|
|
143
166
|
): Promise<MainAccountInfoFetchResult> {
|
|
144
167
|
const nativeMainLease = existingNativeMainLease ?? tryAcquireNativeMainProfileClaim();
|
|
145
168
|
if (!nativeMainLease) {
|
|
@@ -151,8 +174,9 @@ export async function fetchMainAccountInfoAttempt(
|
|
|
151
174
|
};
|
|
152
175
|
}
|
|
153
176
|
try {
|
|
177
|
+
const paced = sharedMainQuotaPacingEnabled(config ?? loadConfig());
|
|
154
178
|
const operation = async () => ({
|
|
155
|
-
...await fetchMainAccountInfoWhileOwned(forceRefresh, retriesRemaining, nativeMainLease, explicitRefresh),
|
|
179
|
+
...await fetchMainAccountInfoWhileOwned(forceRefresh, retriesRemaining, nativeMainLease, explicitRefresh, postReset, paced),
|
|
156
180
|
identityGeneration: captureMainAccountIdentityGeneration(),
|
|
157
181
|
});
|
|
158
182
|
if (nativeMainSharedClaimHeld) return await operation();
|
|
@@ -174,6 +198,11 @@ export async function fetchMainAccountInfoAttempt(
|
|
|
174
198
|
}
|
|
175
199
|
}
|
|
176
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Read native-main usage while ownership is held, publishing only current credential evidence.
|
|
203
|
+
* A replaced same-account bearer may return its parsed ordinary info without mutating shared
|
|
204
|
+
* state or supplying recovery proof. Conflicting identities and stale errors return cached info.
|
|
205
|
+
*/
|
|
177
206
|
export async function fetchMainAccountInfoWhileOwned(
|
|
178
207
|
forceRefresh: boolean,
|
|
179
208
|
retriesRemaining: number,
|
|
@@ -185,6 +214,9 @@ export async function fetchMainAccountInfoWhileOwned(
|
|
|
185
214
|
* promoting a background poll into operator intent below.
|
|
186
215
|
*/
|
|
187
216
|
explicitRefresh: boolean = forceRefresh,
|
|
217
|
+
/** Reset-credit consume needs a fresh post-spend observation, not an earlier read. */
|
|
218
|
+
postReset = false,
|
|
219
|
+
paced = true,
|
|
188
220
|
): Promise<MainAccountInfoFetchResult> {
|
|
189
221
|
const writerGeneration = captureConfigGeneration();
|
|
190
222
|
reconcileMainCodexAccountRuntimeState();
|
|
@@ -212,120 +244,164 @@ export async function fetchMainAccountInfoWhileOwned(
|
|
|
212
244
|
? observeMainQuotaCredential(tokens.access_token, tokens.account_id)
|
|
213
245
|
: undefined;
|
|
214
246
|
const mainQuotaCredentialGeneration = getMainQuotaCredentialGeneration();
|
|
247
|
+
/** A disk replacement may have no second probe to advance the credential generation. */
|
|
248
|
+
const credentialIsCurrent = (): boolean => {
|
|
249
|
+
const current = readCodexTokensResult(undefined, { bounded: true });
|
|
250
|
+
if (current.status !== "ok") return false;
|
|
251
|
+
const effectiveAccountId = extractAccountId(current.tokens.id_token, current.tokens.access_token)
|
|
252
|
+
?? (current.tokens.account_id || null);
|
|
253
|
+
if (effectiveAccountId !== current.tokens.account_id || effectiveAccountId !== requestAccountId) return false;
|
|
254
|
+
observeMainQuotaCredential(current.tokens.access_token, current.tokens.account_id);
|
|
255
|
+
return mainQuotaWriter !== undefined
|
|
256
|
+
&& isMainQuotaWriterLive(mainQuotaWriter)
|
|
257
|
+
&& mainQuotaCredentialGeneration === getMainQuotaCredentialGeneration()
|
|
258
|
+
&& matchesMainQuotaCredential(tokens.access_token, tokens.account_id);
|
|
259
|
+
};
|
|
215
260
|
// Keep diagnostics separate from authentication and freshness policy. Never serialize errors.
|
|
216
261
|
const quotaSignal = AbortSignal.timeout(WHAM_REQUEST_TIMEOUT_MS);
|
|
217
262
|
let quotaPhase: "request" | "body" | "decode" | "publish" = "request";
|
|
218
263
|
let quotaRefreshGeneration = captureMainAccountIdentityGeneration();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
264
|
+
const readState: { owner?: CodexUsageOwner<MainAccountInfoFetchResult>; usable: boolean } = { usable: false };
|
|
265
|
+
const read = async (): Promise<MainAccountInfoFetchResult> => {
|
|
266
|
+
try {
|
|
267
|
+
let dispatchSequence = 0;
|
|
268
|
+
const baseKey = `main:${writerGeneration}:${mainQuotaCredentialGeneration}`;
|
|
269
|
+
const resetEpoch = postReset ? `:post-reset:${currentQuotaDispatchSequence()}` : "";
|
|
270
|
+
const admission = await fetchCodexUsage<MainAccountInfoFetchResult>(
|
|
271
|
+
`${baseKey}${resetEpoch}`, {
|
|
272
|
+
headers: { Authorization: `Bearer ${tokens.access_token}`, "ChatGPT-Account-Id": tokens.account_id },
|
|
273
|
+
signal: quotaSignal,
|
|
274
|
+
}, () => { dispatchSequence = nextQuotaDispatchSequence(); },
|
|
275
|
+
paced ? { pacingKey: baseKey } : { unpaced: true });
|
|
276
|
+
if (!admission) return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
277
|
+
credentialChecked: true, hasCredential: true };
|
|
278
|
+
if (admission.kind === "joined") {
|
|
279
|
+
const current = credentialIsCurrent() && writerGeneration === captureConfigGeneration();
|
|
280
|
+
if (!current) return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
281
|
+
credentialChecked: true, hasCredential: true };
|
|
282
|
+
if (explicitRefresh && (admission.result.quotaRefresh?.status === "ok"
|
|
283
|
+
|| admission.result.quotaRefresh?.status === "not_reported")) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
284
|
+
return admission.result;
|
|
285
|
+
}
|
|
286
|
+
readState.owner = admission;
|
|
287
|
+
const resp = admission.response;
|
|
288
|
+
quotaPhase = "publish";
|
|
289
|
+
if (!resp.ok) {
|
|
290
|
+
const terminalAuthFailure = await isTerminalMainAuthResponse(resp, isMainAccountTokenVerifiablyLive());
|
|
291
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, nativeMainLease, explicitRefresh, paced);
|
|
292
|
+
if (retried) return retried;
|
|
293
|
+
if (!isQuotaDispatchCurrent(dispatchSequence) || !credentialIsCurrent()) {
|
|
294
|
+
return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
295
|
+
credentialChecked: true, hasCredential: true };
|
|
296
|
+
}
|
|
297
|
+
if (terminalAuthFailure) {
|
|
298
|
+
// Account for this attempt's own synchronous invalidation, never prior external drift.
|
|
299
|
+
const diagnosticStillLive = isMainAccountIdentityGenerationLive(quotaRefreshGeneration);
|
|
300
|
+
clearMainAccountInfoCache();
|
|
301
|
+
if (diagnosticStillLive) quotaRefreshGeneration = captureMainAccountIdentityGeneration();
|
|
302
|
+
markAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID, writerGeneration);
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
info: EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: true, hasCredential: true,
|
|
306
|
+
quotaRefresh: { status: "http_error", httpStatus: resp.status },
|
|
307
|
+
quotaRefreshGeneration,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
quotaPhase = "body";
|
|
311
|
+
const data = (await resp.json()) as WhamUsageResponse;
|
|
312
|
+
quotaPhase = "publish";
|
|
313
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, nativeMainLease, explicitRefresh, paced);
|
|
229
314
|
if (retried) return retried;
|
|
315
|
+
quotaPhase = "decode";
|
|
316
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
317
|
+
throw new Error("Invalid WHAM usage object");
|
|
318
|
+
}
|
|
319
|
+
// A newer published response wins over this attempt, including its returned display info.
|
|
230
320
|
if (!isQuotaDispatchCurrent(dispatchSequence)) {
|
|
231
321
|
return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
232
322
|
credentialChecked: true, hasCredential: true };
|
|
233
323
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
324
|
+
quotaPhase = "publish";
|
|
325
|
+
// A delayed response from a replaced bearer cannot revoke a newer Reserve grant,
|
|
326
|
+
// even in the same workspace or after an A→B→A credential transition.
|
|
327
|
+
if (credentialIsCurrent()) {
|
|
328
|
+
observeMainReserveRevocation(data, mainQuotaWriter);
|
|
329
|
+
}
|
|
330
|
+
quotaPhase = "decode";
|
|
331
|
+
const plan = nonEmptyPlan(data.plan_type) ?? nonEmptyPlan(cached?.plan) ?? nonEmptyPlan(getMainAccountPlan());
|
|
332
|
+
const usage = { ...data, ...(plan ? { plan_type: plan } : {}) };
|
|
333
|
+
const quota = parseUsageQuota(usage);
|
|
334
|
+
const policyQuota = parseMainPolicyUsageQuota(usage);
|
|
335
|
+
quotaPhase = "publish";
|
|
336
|
+
const result = {
|
|
337
|
+
email: data.email ?? null,
|
|
338
|
+
plan,
|
|
339
|
+
quota,
|
|
340
|
+
ts: Date.now(),
|
|
341
|
+
};
|
|
342
|
+
if (!credentialIsCurrent()) {
|
|
343
|
+
// Preserve same-identity ordinary info, but never publish stale evidence or state.
|
|
344
|
+
if (mainQuotaWriter && isMainQuotaWriterLive(mainQuotaWriter)) {
|
|
345
|
+
return { info: result, infoUnpublished: true, credentialChecked: true, hasCredential: true };
|
|
346
|
+
}
|
|
347
|
+
return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
348
|
+
credentialChecked: true, hasCredential: true };
|
|
349
|
+
}
|
|
350
|
+
const freshResetCredits = quota?.resetCredits;
|
|
351
|
+
// Tag the count with the identity it was read from, so a later response that omits the
|
|
352
|
+
// summary can restore the badge without ever crossing an account boundary.
|
|
353
|
+
rememberMainResetCredits(requestAccountId, freshResetCredits);
|
|
354
|
+
setMainAccountInfoCache(result);
|
|
355
|
+
// Only an explicit refresh may retract a reauth quarantine. A 200 from
|
|
356
|
+
// /wham/usage proves the token authenticates to the usage endpoint; it does not
|
|
357
|
+
// prove the account can serve Responses traffic, which is a different backend path
|
|
358
|
+
// and still answers 403 for a workspace the token may no longer select (#327).
|
|
359
|
+
// Letting the background poll clear the flag put such an account straight back into
|
|
360
|
+
// rotation: the next request failed the same way and re-marked it, so needsReauth
|
|
361
|
+
// never settled and the dashboard kept showing nothing — the symptom #327 reported.
|
|
362
|
+
// An explicit refresh is an operator asking to re-evaluate, normally right after
|
|
363
|
+
// signing in again, so it stays authoritative.
|
|
364
|
+
if (explicitRefresh) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
365
|
+
// Mirror main quota + plan into the shared stores so the rotation engine can
|
|
366
|
+
// score and auto-switch the main account exactly like a pool account (Option A).
|
|
367
|
+
setMainAccountPlan(result.plan);
|
|
368
|
+
if (result.quota) {
|
|
369
|
+
setAccountQuotaFromParsed(MAIN_CODEX_ACCOUNT_ID, result.quota, writerGeneration, mainQuotaWriter, policyQuota);
|
|
240
370
|
}
|
|
371
|
+
publishQuotaDispatch(dispatchSequence);
|
|
372
|
+
readState.usable = quota !== null;
|
|
373
|
+
return {
|
|
374
|
+
info: result,
|
|
375
|
+
quotaRefresh: { status: quota ? "ok" : "not_reported" },
|
|
376
|
+
quotaRefreshGeneration,
|
|
377
|
+
credentialChecked: true,
|
|
378
|
+
hasCredential: true,
|
|
379
|
+
...(quota ? { freshQuota: quota } : {}),
|
|
380
|
+
...(quota && mainQuotaWriter && credentialIsCurrent()
|
|
381
|
+
? { resetRecoveryProof: { writer: mainQuotaWriter, credentialGeneration: mainQuotaCredentialGeneration, dispatchSequence } }
|
|
382
|
+
: {}),
|
|
383
|
+
...(freshResetCredits !== undefined ? { freshResetCredits } : {}),
|
|
384
|
+
};
|
|
385
|
+
} catch (error) {
|
|
386
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, nativeMainLease, explicitRefresh, paced);
|
|
387
|
+
if (retried) return retried;
|
|
388
|
+
if (!credentialIsCurrent()) {
|
|
389
|
+
return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
390
|
+
credentialChecked: true, hasCredential: true };
|
|
391
|
+
}
|
|
392
|
+
let status: CodexQuotaRefreshOutcome["status"] = "internal_error";
|
|
393
|
+
if ((quotaPhase === "request" || quotaPhase === "body") && quotaSignal.aborted) status = "timeout";
|
|
394
|
+
else if (quotaPhase === "request") status = "network_error";
|
|
395
|
+
else if (quotaPhase === "body") status = error instanceof SyntaxError ? "invalid_response" : "network_error";
|
|
396
|
+
else if (quotaPhase === "decode") status = "invalid_response";
|
|
241
397
|
return {
|
|
242
398
|
info: EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: true, hasCredential: true,
|
|
243
|
-
quotaRefresh: { status
|
|
399
|
+
quotaRefresh: { status },
|
|
244
400
|
quotaRefreshGeneration,
|
|
245
401
|
};
|
|
246
402
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (retried) return retried;
|
|
252
|
-
quotaPhase = "decode";
|
|
253
|
-
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
254
|
-
throw new Error("Invalid WHAM usage object");
|
|
255
|
-
}
|
|
256
|
-
// Check after body/retry awaits and before any cache, credits, policy or
|
|
257
|
-
// Reserve publication. Returning cached state supplies no fresh recovery proof.
|
|
258
|
-
if (!isQuotaDispatchCurrent(dispatchSequence)) {
|
|
259
|
-
return { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO,
|
|
260
|
-
credentialChecked: true, hasCredential: true };
|
|
261
|
-
}
|
|
262
|
-
quotaPhase = "publish";
|
|
263
|
-
// A delayed response from a replaced bearer cannot revoke a newer Reserve grant,
|
|
264
|
-
// even in the same workspace or after an A→B→A credential transition.
|
|
265
|
-
if (mainQuotaCredentialGeneration === getMainQuotaCredentialGeneration()
|
|
266
|
-
&& matchesMainQuotaCredential(tokens.access_token, tokens.account_id)) {
|
|
267
|
-
observeMainReserveRevocation(data, mainQuotaWriter);
|
|
268
|
-
}
|
|
269
|
-
quotaPhase = "decode";
|
|
270
|
-
const plan = nonEmptyPlan(data.plan_type) ?? nonEmptyPlan(cached?.plan) ?? nonEmptyPlan(getMainAccountPlan());
|
|
271
|
-
const usage = { ...data, ...(plan ? { plan_type: plan } : {}) };
|
|
272
|
-
const quota = parseUsageQuota(usage);
|
|
273
|
-
const policyQuota = parseMainPolicyUsageQuota(usage);
|
|
274
|
-
quotaPhase = "publish";
|
|
275
|
-
const freshResetCredits = quota?.resetCredits;
|
|
276
|
-
// Tag the count with the identity it was read from, so a later response that omits the
|
|
277
|
-
// summary can restore the badge without ever crossing an account boundary.
|
|
278
|
-
rememberMainResetCredits(requestAccountId, freshResetCredits);
|
|
279
|
-
const result = {
|
|
280
|
-
email: data.email ?? null,
|
|
281
|
-
plan,
|
|
282
|
-
quota,
|
|
283
|
-
ts: Date.now(),
|
|
284
|
-
};
|
|
285
|
-
setMainAccountInfoCache(result);
|
|
286
|
-
// Only an explicit refresh may retract a reauth quarantine. A 200 from
|
|
287
|
-
// /wham/usage proves the token authenticates to the usage endpoint; it does not
|
|
288
|
-
// prove the account can serve Responses traffic, which is a different backend path
|
|
289
|
-
// and still answers 403 for a workspace the token may no longer select (#327).
|
|
290
|
-
// Letting the background poll clear the flag put such an account straight back into
|
|
291
|
-
// rotation: the next request failed the same way and re-marked it, so needsReauth
|
|
292
|
-
// never settled and the dashboard kept showing nothing — the symptom #327 reported.
|
|
293
|
-
// An explicit refresh is an operator asking to re-evaluate, normally right after
|
|
294
|
-
// signing in again, so it stays authoritative.
|
|
295
|
-
if (explicitRefresh) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
296
|
-
// Mirror main quota + plan into the shared stores so the rotation engine can
|
|
297
|
-
// score and auto-switch the main account exactly like a pool account (Option A).
|
|
298
|
-
setMainAccountPlan(result.plan);
|
|
299
|
-
if (result.quota) {
|
|
300
|
-
setAccountQuotaFromParsed(MAIN_CODEX_ACCOUNT_ID, result.quota, writerGeneration, mainQuotaWriter, policyQuota);
|
|
301
|
-
}
|
|
302
|
-
publishQuotaDispatch(dispatchSequence);
|
|
303
|
-
return {
|
|
304
|
-
info: result,
|
|
305
|
-
quotaRefresh: { status: quota ? "ok" : "not_reported" },
|
|
306
|
-
quotaRefreshGeneration,
|
|
307
|
-
credentialChecked: true,
|
|
308
|
-
hasCredential: true,
|
|
309
|
-
...(quota ? { freshQuota: quota } : {}),
|
|
310
|
-
...(quota && mainQuotaWriter && isMainQuotaWriterLive(mainQuotaWriter)
|
|
311
|
-
&& mainQuotaCredentialGeneration === getMainQuotaCredentialGeneration()
|
|
312
|
-
&& matchesMainQuotaCredential(tokens.access_token, tokens.account_id)
|
|
313
|
-
? { resetRecoveryProof: { writer: mainQuotaWriter, credentialGeneration: mainQuotaCredentialGeneration, dispatchSequence } }
|
|
314
|
-
: {}),
|
|
315
|
-
...(freshResetCredits !== undefined ? { freshResetCredits } : {}),
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, nativeMainLease, explicitRefresh);
|
|
319
|
-
if (retried) return retried;
|
|
320
|
-
let status: CodexQuotaRefreshOutcome["status"] = "internal_error";
|
|
321
|
-
if ((quotaPhase === "request" || quotaPhase === "body") && quotaSignal.aborted) status = "timeout";
|
|
322
|
-
else if (quotaPhase === "request") status = "network_error";
|
|
323
|
-
else if (quotaPhase === "body") status = error instanceof SyntaxError ? "invalid_response" : "network_error";
|
|
324
|
-
else if (quotaPhase === "decode") status = "invalid_response";
|
|
325
|
-
return {
|
|
326
|
-
info: EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: true, hasCredential: true,
|
|
327
|
-
quotaRefresh: { status },
|
|
328
|
-
quotaRefreshGeneration,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
403
|
+
};
|
|
404
|
+
let outcome: MainAccountInfoFetchResult | undefined;
|
|
405
|
+
try { outcome = await read(); return outcome; }
|
|
406
|
+
finally { readState.owner?.settle(readState.usable, outcome); }
|
|
331
407
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nextCodexUsageQueryAt, nextQuotaQueryDelay, pruneRemovedCodexPoolUsageAccounts } from "../quota-query-backoff";
|
|
1
2
|
import { CODEX_PRIORITY_FAILBACK_REFRESH_MS } from "../account-priority";
|
|
2
3
|
import { codexQuotaHasFreshUsage } from "../quota-observation-freshness";
|
|
3
4
|
import { getCodexAccountCredential, getValidCodexToken, readCodexAccountRecord } from "../account-store";
|
|
@@ -8,7 +9,7 @@ import { readCodexTokens } from "../auth-collision";
|
|
|
8
9
|
import { isAccountNeedsReauth, markAccountNeedsReauth } from "../account-runtime-state";
|
|
9
10
|
import { getValidMainAccountToken, MainAccountTokenRefreshError, MAIN_CODEX_ACCOUNT_ID, getMainAccountPlan } from "../main-account";
|
|
10
11
|
import { captureConfigGeneration, registerStateSweepAfterTick } from "../../lib/state-store-sweeper";
|
|
11
|
-
import { captureMainAccountIdentityGeneration, isMainAccountIdentityGenerationLive } from "../main-account-cache";
|
|
12
|
+
import { observeMainQuotaCredential, getMainQuotaCredentialGeneration, captureMainAccountIdentityGeneration, isMainAccountIdentityGenerationLive } from "../main-account-cache";
|
|
12
13
|
import { getMainAccountHardLockStatus } from "../main-account-hard-lock";
|
|
13
14
|
import type { OcxConfig } from "../../types";
|
|
14
15
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
|
|
@@ -52,7 +53,8 @@ export async function runCodexCooldownRecoveryProbes(config: OcxConfig, now = Da
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
try {
|
|
55
|
-
const result = await fetchPoolAccountQuota(claim.accountId, true, account.plan
|
|
56
|
+
const result = await fetchPoolAccountQuota(claim.accountId, true, account.plan,
|
|
57
|
+
undefined, false, undefined, now);
|
|
56
58
|
// Defence in depth: independent scopes are already excluded at the claim site.
|
|
57
59
|
// Generic WHAM must never clear Reserve even if claim selection changes.
|
|
58
60
|
const recovered = (claim.scope === undefined || claim.scope === "shared")
|
|
@@ -71,12 +73,26 @@ export async function runCodexCooldownRecoveryProbes(config: OcxConfig, now = Da
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
let mainHardLockRecoveryInFlight: Promise<void> | null = null;
|
|
76
|
+
let mainHardLockRecoveryAttempt: { identity: number; credential: number; after: number; delay: number } | undefined;
|
|
74
77
|
|
|
75
78
|
/** Metadata-only recovery on the existing sweep; failures retain the observed policy block. */
|
|
76
79
|
export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise<void> {
|
|
77
80
|
if (mainHardLockRecoveryInFlight) return mainHardLockRecoveryInFlight;
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
const status = getMainAccountHardLockStatus(config);
|
|
82
|
+
if (status.state !== "blocked") { mainHardLockRecoveryAttempt = undefined; return; }
|
|
83
|
+
if (isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID)) return;
|
|
84
|
+
// A predicted reset is a scheduling hint, never proof that the hard lock can be lifted.
|
|
85
|
+
if (status.resetAt !== undefined && status.resetAt > Date.now()) return;
|
|
86
|
+
// Reconcile the owned physical credential before skipping work for an older one.
|
|
87
|
+
reconcileMainCodexAccountRuntimeState();
|
|
88
|
+
const physical = readCodexTokens();
|
|
89
|
+
if (physical) observeMainQuotaCredential(physical.access_token, physical.account_id);
|
|
90
|
+
const identity = captureMainAccountIdentityGeneration();
|
|
91
|
+
const credential = getMainQuotaCredentialGeneration();
|
|
92
|
+
const previous = mainHardLockRecoveryAttempt;
|
|
93
|
+
const queryAfter = nextCodexUsageQueryAt(`main:${captureConfigGeneration()}:${credential}`) ?? 0;
|
|
94
|
+
if ((previous?.identity === identity && previous.credential === credential
|
|
95
|
+
&& previous.after > Date.now()) || queryAfter > Date.now()) return;
|
|
80
96
|
const lease = tryAcquireNativeMainProfileClaim();
|
|
81
97
|
if (!lease) return;
|
|
82
98
|
mainHardLockRecoveryInFlight = (async () => {
|
|
@@ -88,7 +104,9 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
88
104
|
try {
|
|
89
105
|
// Refresh can require an exclusive credential claim: never hold WHAM's shared
|
|
90
106
|
// claim while obtaining a valid token. The runtime lease spans both operations.
|
|
91
|
-
|
|
107
|
+
const prepared = await getValidMainAccountToken({ preserveReauth: true });
|
|
108
|
+
if (!prepared) return;
|
|
109
|
+
observeMainQuotaCredential(prepared.accessToken, prepared.chatgptAccountId);
|
|
92
110
|
} catch (error) {
|
|
93
111
|
if (error instanceof MainAccountTokenRefreshError && error.reason === "reauth"
|
|
94
112
|
&& isMainAccountIdentityGenerationLive(identityGeneration)) {
|
|
@@ -97,7 +115,41 @@ export async function runMainAccountHardLockRecovery(config: OcxConfig): Promise
|
|
|
97
115
|
return;
|
|
98
116
|
}
|
|
99
117
|
if (isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID)) return;
|
|
100
|
-
|
|
118
|
+
const credential = getMainQuotaCredentialGeneration();
|
|
119
|
+
// Reconcile the physical bearer before consulting the previous credential's delay.
|
|
120
|
+
if (previous?.identity === identityGeneration && previous.credential === credential
|
|
121
|
+
&& previous.after > Date.now()) return;
|
|
122
|
+
const currentQueryAfter = nextCodexUsageQueryAt(`main:${captureConfigGeneration()}:${credential}`) ?? 0;
|
|
123
|
+
if (currentQueryAfter > Date.now()) {
|
|
124
|
+
mainHardLockRecoveryAttempt = { identity: identityGeneration, credential,
|
|
125
|
+
delay: previous?.identity === identityGeneration && previous.credential === credential ? previous.delay : 0,
|
|
126
|
+
after: currentQueryAfter };
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const result = await fetchMainAccountInfoAttempt(true, 1, lease, false, false, false, config);
|
|
130
|
+
// Never charge a replacement credential for a late result from its predecessor.
|
|
131
|
+
if (isMainAccountIdentityGenerationLive(identityGeneration)
|
|
132
|
+
&& credential === getMainQuotaCredentialGeneration()) {
|
|
133
|
+
const queryAfter = nextCodexUsageQueryAt(`main:${captureConfigGeneration()}:${credential}`) ?? 0;
|
|
134
|
+
const previousDelay = previous?.identity === identityGeneration && previous.credential === credential
|
|
135
|
+
? previous.delay : 0;
|
|
136
|
+
if (result.quotaRefresh) {
|
|
137
|
+
const authStatus = result.quotaRefresh.status === "http_error"
|
|
138
|
+
? result.quotaRefresh.httpStatus : undefined;
|
|
139
|
+
const transientAuth = (authStatus === 401 || authStatus === 403)
|
|
140
|
+
&& !isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
141
|
+
if (transientAuth) mainHardLockRecoveryAttempt = undefined;
|
|
142
|
+
else {
|
|
143
|
+
const delay = nextQuotaQueryDelay(previousDelay || undefined);
|
|
144
|
+
mainHardLockRecoveryAttempt = getMainAccountHardLockStatus(config).state === "blocked"
|
|
145
|
+
? { identity: identityGeneration, credential, delay, after: Math.max(Date.now() + delay, queryAfter) }
|
|
146
|
+
: undefined;
|
|
147
|
+
}
|
|
148
|
+
} else if (queryAfter > Date.now()) {
|
|
149
|
+
mainHardLockRecoveryAttempt = { identity: identityGeneration, credential,
|
|
150
|
+
delay: previousDelay, after: queryAfter };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
101
153
|
})().catch(() => {
|
|
102
154
|
// Best-effort background metadata read; no cooldown/pause or policy clearing on failure.
|
|
103
155
|
}).finally(() => {
|
|
@@ -169,6 +221,7 @@ export async function primeCodexPoolQuotas(
|
|
|
169
221
|
// retry the restored credential is entitled to.
|
|
170
222
|
const runtimeConfig = getRuntimeConfig(config);
|
|
171
223
|
const configuredPoolIds = new Set((runtimeConfig.codexAccounts ?? []).map(account => account.id));
|
|
224
|
+
pruneRemovedCodexPoolUsageAccounts(configuredPoolIds);
|
|
172
225
|
for (const accountId of poolQuotaPrimeAttemptedAt.keys()) {
|
|
173
226
|
if (!configuredPoolIds.has(accountId)) poolQuotaPrimeAttemptedAt.delete(accountId);
|
|
174
227
|
}
|
|
@@ -220,7 +273,8 @@ export async function primeCodexPoolQuotas(
|
|
|
220
273
|
const bypassCachedQuota = !!observationStale;
|
|
221
274
|
if (options.fetchMainInfo) await options.fetchMainInfo(bypassCachedQuota);
|
|
222
275
|
// Cache bypass is passive observation, never an explicit reauthentication recovery.
|
|
223
|
-
else await fetchMainAccountInfoAttempt(bypassCachedQuota, 1, mainLease, true,
|
|
276
|
+
else await fetchMainAccountInfoAttempt(bypassCachedQuota, 1, mainLease, true,
|
|
277
|
+
false, false, config);
|
|
224
278
|
});
|
|
225
279
|
} catch (error) {
|
|
226
280
|
if (!isNativeMainClaimUnavailable(error)) throw error;
|