@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,5 @@
|
|
|
1
|
+
import { fetchCodexUsage } from "../quota-query-backoff";
|
|
2
|
+
import type { CodexUsageOwner } from "../quota-query-backoff";
|
|
1
3
|
import { capturePoolQuotaWriter, getValidCodexToken, isCodexAccountGenerationLive, forceRefreshCodexPoolToken, markCodexAccountValidated, markCodexAccountValidationFailed, readCodexAccountRecord, isTerminalCodexPoolRefreshFailure, CodexCredentialGenerationConflictError, CodexCredentialRefreshLockTimeoutError, CodexCredentialRefreshBusyError, CodexCredentialRefreshStaleError, TokenRefreshError } from "../account-store";
|
|
2
4
|
import type { PoolQuotaWriter } from "../quota-types";
|
|
3
5
|
import { isValidWhamHistoryObservation, getAccountQuota, isCompleteCodexQuotaRecoverySnapshot, parseUsageQuota, setAccountQuotaFromParsed } from "../quota";
|
|
@@ -101,7 +103,9 @@ export function withQuotaProbeEvidence(
|
|
|
101
103
|
result: PoolQuotaResult,
|
|
102
104
|
evidence: PoolQuotaProbeEvidence,
|
|
103
105
|
): PoolQuotaResult {
|
|
104
|
-
return evidence.attempted
|
|
106
|
+
return evidence.attempted
|
|
107
|
+
&& (result.quotaProbeAttempted?.dispatchSequence ?? 0) < evidence.attempted.dispatchSequence
|
|
108
|
+
? { ...result, quotaProbeAttempted: evidence.attempted } : result;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
export interface PoolQuotaRefreshFlight {
|
|
@@ -167,6 +171,7 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
167
171
|
resp: Response;
|
|
168
172
|
quotaProbeEvidence: PoolQuotaProbeEvidence;
|
|
169
173
|
onCredentialGeneration?: (generation: number) => void;
|
|
174
|
+
recoveryProbeNow?: number;
|
|
170
175
|
}): Promise<PoolQuotaResult> {
|
|
171
176
|
const { accountId, existing, configuredPlan, rejectedAccessToken, rejectedGeneration, resp } = ctx;
|
|
172
177
|
|
|
@@ -252,37 +257,61 @@ export async function recoverPoolQuotaFrom401(ctx: {
|
|
|
252
257
|
ctx.onCredentialGeneration?.(refreshed.generation);
|
|
253
258
|
|
|
254
259
|
const writerGeneration = captureConfigGeneration();
|
|
255
|
-
markQuotaProbeAttempted(ctx.quotaProbeEvidence, refreshed.generation);
|
|
256
260
|
const poolWriter = capturePoolQuotaWriter(accountId, refreshed);
|
|
257
|
-
const
|
|
261
|
+
const replayRead = await fetchCodexUsage<PoolQuotaResult>(`pool:${accountId}:${writerGeneration}:${refreshed.generation}`, {
|
|
258
262
|
headers: {
|
|
259
263
|
Authorization: `Bearer ${refreshed.accessToken}`,
|
|
260
264
|
"ChatGPT-Account-Id": refreshed.chatgptAccountId,
|
|
261
265
|
},
|
|
262
266
|
signal: AbortSignal.timeout(WHAM_REQUEST_TIMEOUT_MS),
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return { quota:
|
|
267
|
+
}, () => markQuotaProbeAttempted(ctx.quotaProbeEvidence, refreshed.generation),
|
|
268
|
+
ctx.recoveryProbeNow === undefined ? { poolAccountId: accountId }
|
|
269
|
+
: { poolAccountId: accountId, recoveryProbe: true, now: () => ctx.recoveryProbeNow! });
|
|
270
|
+
if (!replayRead) return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation, quotaProbeSkipped: true };
|
|
271
|
+
if (replayRead.kind === "joined") {
|
|
272
|
+
if (!isCodexAccountGenerationLive(accountId, refreshed.generation)
|
|
273
|
+
|| ctx.quotaProbeEvidence.mayPublish?.() === false) {
|
|
274
|
+
return { quota: getAccountQuota(accountId), needsReauth: false, credentialGeneration: refreshed.generation };
|
|
271
275
|
}
|
|
272
|
-
|
|
276
|
+
const joined = replayRead.result;
|
|
277
|
+
return joined.freshCredentialGeneration === refreshed.generation ? {
|
|
278
|
+
...joined,
|
|
279
|
+
resetRefreshLineage: {
|
|
280
|
+
fromGeneration: rejectedGeneration, toGeneration: refreshed.generation,
|
|
281
|
+
provenance: refreshed.provenance,
|
|
282
|
+
},
|
|
283
|
+
} : joined;
|
|
273
284
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
let usableReplay = false;
|
|
286
|
+
const finishReplay = async (): Promise<PoolQuotaResult> => {
|
|
287
|
+
const replay = replayRead.response;
|
|
288
|
+
if (!replay.ok) {
|
|
289
|
+
if (replay.status === 401 && await isTerminalPoolAuthResponse(replay)) {
|
|
290
|
+
// The refresh already settled this claim non-terminally, so the record alone would
|
|
291
|
+
// let the next poll call a dead credential healthy. The evidence is about the
|
|
292
|
+
// REFRESHED credential, which is what the replay used.
|
|
293
|
+
markAccountNeedsReauth(accountId, writerGeneration, refreshed.generation);
|
|
294
|
+
return { quota: existing ?? null, needsReauth: true, reauthReason: "quota_unauthorized", credentialGeneration: refreshed.generation };
|
|
295
|
+
}
|
|
296
|
+
return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation };
|
|
297
|
+
}
|
|
298
|
+
const result = await commitPoolQuotaResponse(replay, {
|
|
299
|
+
accountId, existing, configuredPlan, generation: refreshed.generation, writerGeneration, poolWriter,
|
|
300
|
+
mayPublish: ctx.quotaProbeEvidence.mayPublish,
|
|
301
|
+
});
|
|
302
|
+
usableReplay = result.freshQuota !== undefined;
|
|
303
|
+
return result.freshCredentialGeneration === refreshed.generation ? {
|
|
304
|
+
...result,
|
|
305
|
+
resetRefreshLineage: {
|
|
306
|
+
fromGeneration: rejectedGeneration,
|
|
307
|
+
toGeneration: refreshed.generation,
|
|
308
|
+
provenance: refreshed.provenance,
|
|
309
|
+
},
|
|
310
|
+
} : result;
|
|
311
|
+
};
|
|
312
|
+
let replayOutcome: PoolQuotaResult | undefined;
|
|
313
|
+
try { replayOutcome = await finishReplay(); return replayOutcome; }
|
|
314
|
+
finally { replayRead.settle(usableReplay, replayOutcome); }
|
|
286
315
|
}
|
|
287
316
|
|
|
288
317
|
/** Backoff after a refresh failure that proved nothing about the credential. */
|
|
@@ -338,8 +367,9 @@ export async function commitPoolQuotaResponse(
|
|
|
338
367
|
if (!isCodexAccountGenerationLive(accountId, generation)) {
|
|
339
368
|
return { quota: null, needsReauth: false, credentialGeneration: generation };
|
|
340
369
|
}
|
|
341
|
-
|
|
342
|
-
|
|
370
|
+
const validPolicyObservation = isValidWhamHistoryObservation(data);
|
|
371
|
+
setAccountQuotaFromParsed(accountId, quota, writerGeneration, undefined, validPolicyObservation ? quota : null,
|
|
372
|
+
ctx.poolWriter && validPolicyObservation ? { writer: ctx.poolWriter, observedAt, source: "wham", raw: quota } : undefined);
|
|
343
373
|
return {
|
|
344
374
|
quota: getAccountQuota(accountId),
|
|
345
375
|
needsReauth: false,
|
|
@@ -358,81 +388,100 @@ export async function fetchFreshPoolAccountQuota(
|
|
|
358
388
|
onCredentialGeneration?: (generation: number) => void,
|
|
359
389
|
getValidToken: typeof getValidCodexToken = getValidCodexToken,
|
|
360
390
|
quotaProbeEvidence: PoolQuotaProbeEvidence = {},
|
|
391
|
+
afterDispatchSequence?: number,
|
|
392
|
+
recoveryProbeNow?: number,
|
|
361
393
|
): Promise<PoolQuotaResult> {
|
|
362
394
|
const writerGeneration = captureConfigGeneration();
|
|
363
395
|
let requestCredentialGeneration = readCodexAccountRecord(accountId)?.generation;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
396
|
+
const readState: { owner?: CodexUsageOwner<PoolQuotaResult>; usable: boolean } = { usable: false };
|
|
397
|
+
const read = async (): Promise<PoolQuotaResult> => {
|
|
398
|
+
try {
|
|
399
|
+
const { accessToken, chatgptAccountId, generation } = await getValidToken(accountId);
|
|
400
|
+
const poolWriter = capturePoolQuotaWriter(accountId, { accessToken, chatgptAccountId, generation });
|
|
401
|
+
requestCredentialGeneration = generation;
|
|
402
|
+
onCredentialGeneration?.(generation);
|
|
403
|
+
const admission = await fetchCodexUsage<PoolQuotaResult>(
|
|
404
|
+
`pool:${accountId}:${writerGeneration}:${generation}${afterDispatchSequence === undefined ? "" : `:post-reset:${afterDispatchSequence}`}`, {
|
|
405
|
+
headers: { Authorization: `Bearer ${accessToken}`, "ChatGPT-Account-Id": chatgptAccountId },
|
|
406
|
+
signal: AbortSignal.timeout(8000),
|
|
407
|
+
}, () => markQuotaProbeAttempted(quotaProbeEvidence, generation),
|
|
408
|
+
recoveryProbeNow === undefined ? { poolAccountId: accountId }
|
|
409
|
+
: { poolAccountId: accountId, recoveryProbe: true, now: () => recoveryProbeNow });
|
|
410
|
+
if (!admission) return { quota: existing ?? null, needsReauth: false, credentialGeneration: generation, quotaProbeSkipped: true };
|
|
411
|
+
if (admission.kind === "joined") return isCodexAccountGenerationLive(accountId, generation)
|
|
412
|
+
&& quotaProbeEvidence.mayPublish?.() !== false
|
|
413
|
+
? admission.result
|
|
414
|
+
: { quota: getAccountQuota(accountId), needsReauth: false, credentialGeneration: generation };
|
|
415
|
+
readState.owner = admission;
|
|
416
|
+
const resp = admission.response;
|
|
417
|
+
if (!resp.ok) {
|
|
418
|
+
if (resp.status !== 401) {
|
|
419
|
+
return withQuotaProbeEvidence(
|
|
420
|
+
{ quota: existing ?? null, needsReauth: false, credentialGeneration: generation },
|
|
421
|
+
quotaProbeEvidence,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
// A bare 401 is what a stale-but-refreshable bearer produces after a plan change, so
|
|
425
|
+
// quarantining on it tells the operator to re-authenticate an account that was fine
|
|
426
|
+
// (#3019). Refresh once, replay once, and only then decide.
|
|
427
|
+
const recovered = await recoverPoolQuotaFrom401({
|
|
428
|
+
accountId,
|
|
429
|
+
existing,
|
|
430
|
+
configuredPlan,
|
|
431
|
+
rejectedAccessToken: accessToken,
|
|
432
|
+
rejectedGeneration: generation,
|
|
433
|
+
resp,
|
|
378
434
|
quotaProbeEvidence,
|
|
379
|
-
|
|
435
|
+
onCredentialGeneration,
|
|
436
|
+
recoveryProbeNow,
|
|
437
|
+
});
|
|
438
|
+
return withQuotaProbeEvidence(recovered, quotaProbeEvidence);
|
|
380
439
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const recovered = await recoverPoolQuotaFrom401({
|
|
385
|
-
accountId,
|
|
386
|
-
existing,
|
|
387
|
-
configuredPlan,
|
|
388
|
-
rejectedAccessToken: accessToken,
|
|
389
|
-
rejectedGeneration: generation,
|
|
390
|
-
resp,
|
|
391
|
-
quotaProbeEvidence,
|
|
392
|
-
onCredentialGeneration,
|
|
440
|
+
const committed = await commitPoolQuotaResponse(resp, {
|
|
441
|
+
accountId, existing, configuredPlan, generation, writerGeneration, poolWriter,
|
|
442
|
+
mayPublish: quotaProbeEvidence.mayPublish,
|
|
393
443
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
444
|
+
readState.usable = committed.freshQuota !== undefined;
|
|
445
|
+
return withQuotaProbeEvidence(committed, quotaProbeEvidence);
|
|
446
|
+
} catch (e) {
|
|
447
|
+
if (e instanceof CodexCredentialGenerationConflictError || e instanceof CodexCredentialRefreshLockTimeoutError
|
|
448
|
+
|| e instanceof CodexCredentialRefreshBusyError || e instanceof CodexCredentialRefreshStaleError) {
|
|
449
|
+
return withQuotaProbeEvidence({
|
|
450
|
+
quota: existing ?? null,
|
|
451
|
+
needsReauth: false,
|
|
452
|
+
credentialGeneration: requestCredentialGeneration,
|
|
453
|
+
quotaProbeSkipped: true,
|
|
454
|
+
}, quotaProbeEvidence);
|
|
455
|
+
}
|
|
456
|
+
// Terminal means the grant itself is dead or missing; an `unknown` refresh failure (a
|
|
457
|
+
// token-endpoint 5xx, a transport blip) may clear, so it reports transient like the
|
|
458
|
+
// 401-recovery path instead of quarantining a healthy account (#2887). A revoked or
|
|
459
|
+
// expired grant is also written to the record as a terminal validation failure, the same
|
|
460
|
+
// verdict the token guardian persists: the in-memory mark dies with this process, and
|
|
461
|
+
// the stored verdict is what keeps a cached listing from calling the dead grant healthy
|
|
462
|
+
// after a restart.
|
|
463
|
+
if (isTerminalCodexPoolRefreshFailure(e)) {
|
|
464
|
+
if (e instanceof TokenRefreshError && isTerminalRefreshError(e)) {
|
|
465
|
+
markCodexAccountValidationFailed(accountId, `refresh_${e.reason}`, {
|
|
466
|
+
expectedGeneration: requestCredentialGeneration,
|
|
467
|
+
terminal: true,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
markAccountNeedsReauth(accountId, captureConfigGeneration(), requestCredentialGeneration);
|
|
471
|
+
return withQuotaProbeEvidence(
|
|
472
|
+
{ quota: existing ?? null, needsReauth: true, reauthReason: "refresh_failed", credentialGeneration: requestCredentialGeneration },
|
|
473
|
+
quotaProbeEvidence,
|
|
474
|
+
);
|
|
424
475
|
}
|
|
425
|
-
markAccountNeedsReauth(accountId, captureConfigGeneration(), requestCredentialGeneration);
|
|
426
476
|
return withQuotaProbeEvidence(
|
|
427
|
-
{ quota: existing ?? null, needsReauth:
|
|
477
|
+
{ quota: existing ?? null, needsReauth: false, credentialGeneration: requestCredentialGeneration },
|
|
428
478
|
quotaProbeEvidence,
|
|
429
479
|
);
|
|
430
480
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
481
|
+
};
|
|
482
|
+
let outcome: PoolQuotaResult | undefined;
|
|
483
|
+
try { outcome = await read(); return outcome; }
|
|
484
|
+
finally { readState.owner?.settle(readState.usable, outcome); }
|
|
436
485
|
}
|
|
437
486
|
|
|
438
487
|
export async function fetchPoolAccountQuota(
|
|
@@ -442,6 +491,7 @@ export async function fetchPoolAccountQuota(
|
|
|
442
491
|
getValidToken: typeof getValidCodexToken = getValidCodexToken,
|
|
443
492
|
validatePending = false,
|
|
444
493
|
afterDispatchSequence?: number,
|
|
494
|
+
recoveryProbeNow?: number,
|
|
445
495
|
): Promise<PoolQuotaResult> {
|
|
446
496
|
const existing = getAccountQuota(accountId);
|
|
447
497
|
if (afterDispatchSequence === undefined && !forceRefresh && existing && Date.now() - existing.updatedAt < POOL_CACHE_TTL) {
|
|
@@ -489,6 +539,8 @@ export async function fetchPoolAccountQuota(
|
|
|
489
539
|
onDispatch: sequence => { state.dispatchSequence = sequence; },
|
|
490
540
|
mayPublish: () => state.superseded !== true,
|
|
491
541
|
},
|
|
542
|
+
afterDispatchSequence,
|
|
543
|
+
recoveryProbeNow,
|
|
492
544
|
).then(async result => {
|
|
493
545
|
// A passive flight has consumed its validation decision. Remove it before
|
|
494
546
|
// promise settlement queues other continuations, so a late explicit caller
|
|
@@ -225,7 +225,7 @@ async function refreshAfterManualReset(
|
|
|
225
225
|
if (!manualResetAuthStillLive(accountId, auth)) return undefined;
|
|
226
226
|
if (auth.isMain) {
|
|
227
227
|
const result = await fetchMainAccountInfoAttempt(true, 1, auth.nativeMainLease,
|
|
228
|
-
auth.nativeMainSharedClaimHeld === true, false);
|
|
228
|
+
auth.nativeMainSharedClaimHeld === true, false, didReset, config);
|
|
229
229
|
const proof = result.resetRecoveryProof;
|
|
230
230
|
const recovered = didReset && manualResetAuthStillLive(accountId, auth)
|
|
231
231
|
&& !!proof && !!auth.mainProof
|
|
@@ -531,6 +531,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
|
|
|
531
531
|
defaultModel: prov.defaultModel ?? null,
|
|
532
532
|
ctx: prov.contextWindow ?? null,
|
|
533
533
|
ctxW: prov.modelContextWindows ?? null,
|
|
534
|
+
ctxTier: prov.modelContextTiers ?? null,
|
|
534
535
|
maxIn: prov.modelMaxInputTokens ?? null,
|
|
535
536
|
maxOut: prov.modelMaxOutputTokens ?? null,
|
|
536
537
|
autoCompact: prov.modelAutoCompactTokenLimits ?? null,
|
|
@@ -45,6 +45,7 @@ import type { FastPolicyAuthority } from "../../providers/fastwire";
|
|
|
45
45
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../../providers/registry";
|
|
46
46
|
import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
|
|
47
47
|
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
48
|
+
import { githubCopilotCatalogContextWindow } from "../../providers/github-copilot-context";
|
|
48
49
|
import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
|
|
49
50
|
import { effectiveModelAliases } from "../../providers/default-aliases";
|
|
50
51
|
import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../../providers/slug-codec";
|
|
@@ -310,9 +311,8 @@ export function applyProviderConfigHints(
|
|
|
310
311
|
...modelWithoutServiceTier
|
|
311
312
|
} = model;
|
|
312
313
|
// 已发现窗口只允许被配置值压低;缺窗口时,已开的 Context cap 就是实际窗口。
|
|
313
|
-
const discoveredWindow =
|
|
314
|
-
? model.contextWindow
|
|
315
|
-
: undefined;
|
|
314
|
+
const discoveredWindow = githubCopilotCatalogContextWindow(name, prov, model.id,
|
|
315
|
+
typeof model.contextWindow === "number" && model.contextWindow > 0 ? model.contextWindow : undefined);
|
|
316
316
|
const projectedLimits = clampObservedModelLimits(staticPolicy.model, {
|
|
317
317
|
...(discoveredWindow !== undefined ? { contextWindow: discoveredWindow } : {}),
|
|
318
318
|
...(typeof model.maxInputTokens === "number" && model.maxInputTokens > 0 ? { maxInputTokens: model.maxInputTokens } : {}),
|
|
@@ -424,6 +424,8 @@ export async function fetchProviderModelsWithAuth(
|
|
|
424
424
|
// away, and every client that keys an effort control off this field —
|
|
425
425
|
// the Pi-shaped exports — renders no control at all.
|
|
426
426
|
...(liveResult.efforts[id]?.length ? { reasoningEfforts: liveResult.efforts[id] } : {}),
|
|
427
|
+
// The family's effective enabled default, matching an unset effort at request time.
|
|
428
|
+
...(liveResult.defaultEfforts[id] ? { defaultReasoningEffort: liveResult.defaultEfforts[id] } : {}),
|
|
427
429
|
// The account catalog's per-base supportsImages vote collapses to one
|
|
428
430
|
// modalities value. It spreads before the hints so exact
|
|
429
431
|
// modelCapabilities declarations, the legacy modelInputModalities
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Bounded event projection owned by one live low-quota registration. */
|
|
2
|
+
export type LowQuotaEvent = {
|
|
3
|
+
accountId: string;
|
|
4
|
+
window: "short" | "weekly";
|
|
5
|
+
percentUsed: number;
|
|
6
|
+
resetAt: number | null;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
status: "pending" | "logged" | "delivered" | "succeeded" | "failed" | "cancelled";
|
|
9
|
+
delivery: "notice" | "pause-save";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const CAPACITY = 100;
|
|
13
|
+
|
|
14
|
+
export function createLowQuotaEventLedger(): {
|
|
15
|
+
publish(event: LowQuotaEvent): void;
|
|
16
|
+
list(limit?: number): LowQuotaEvent[];
|
|
17
|
+
} {
|
|
18
|
+
const events: LowQuotaEvent[] = [];
|
|
19
|
+
return {
|
|
20
|
+
publish(event) {
|
|
21
|
+
events.unshift({ ...event });
|
|
22
|
+
if (events.length > CAPACITY) events.length = CAPACITY;
|
|
23
|
+
},
|
|
24
|
+
list(limit = 20) {
|
|
25
|
+
return events.slice(0, Math.max(0, Math.min(CAPACITY, limit))).map(event => ({ ...event }));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StoredAccountQuota } from "./quota-types";
|
|
2
|
+
|
|
3
|
+
type Observer = (accountId: string, quota: Omit<StoredAccountQuota, "updatedAt">) => void;
|
|
4
|
+
const observers = new Map<symbol, Observer>();
|
|
5
|
+
|
|
6
|
+
/** The composition root owns the live config; quota writers know only this synchronous slot. */
|
|
7
|
+
export function registerLowQuotaObserver(observer: Observer): () => void {
|
|
8
|
+
const owner = Symbol();
|
|
9
|
+
observers.set(owner, observer);
|
|
10
|
+
return () => { observers.delete(owner); };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Only newly accepted evidence belongs here, never carried or disk-hydrated windows. */
|
|
14
|
+
export function observeCodexLowQuota(accountId: string, quota: Omit<StoredAccountQuota, "updatedAt">): void {
|
|
15
|
+
for (const observer of observers.values()) {
|
|
16
|
+
try {
|
|
17
|
+
observer(accountId, quota);
|
|
18
|
+
} catch {
|
|
19
|
+
// One server's optional policy cannot suppress another server's observation.
|
|
20
|
+
console.warn("[codex-low-quota] protection action failed");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|