@bitkyc08/opencodex 2.80.0 → 2.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +1 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +3 -2
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +320 -184
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +4 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +16 -11
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +27 -5
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -1,48 +1,15 @@
|
|
|
1
1
|
import { mergeAnthropicFamilyWindows, hasAnthropicFamilyEnumeration } from "./anthropic-family-headers";
|
|
2
|
-
import { getCachedProviderAccountQuota } from "./account-cache";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { getCachedProviderAccountQuota, captureProviderAccountQuotaEpoch } from "./account-cache";
|
|
3
|
+
import { anthropicModelQuotaFor } from "../../oauth/anthropic-model-quota";
|
|
4
|
+
import type { AnthropicInstanceId } from "../anthropic-instance-id";
|
|
5
|
+
import type { GenerationContext } from "../../lib/state-store-sweeper";
|
|
6
|
+
import { credentialGeneration, getAccountCredential, getAccountCredentialWithStatus, getAccountSet } from "../../oauth/store";
|
|
5
7
|
import type { ProviderQuota } from "../quota-types";
|
|
6
8
|
|
|
7
9
|
export class AnthropicQuotaProbeOwnershipError extends Error {}
|
|
8
10
|
|
|
9
|
-
/** Check live pause and bearer ownership at the last synchronous step before usage dispatch. */
|
|
10
|
-
export function assertAnthropicQuotaSendAllowed(accountId: string, token: string): void {
|
|
11
|
-
const row = getAccountCredentialWithStatus("anthropic", accountId);
|
|
12
|
-
if (!row || row.paused || row.needsReauth || row.credential.access !== token) {
|
|
13
|
-
throw new AnthropicQuotaProbeOwnershipError("anthropic quota account is no longer eligible");
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const cooldownGenerationByAccount = new Map<string, number>();
|
|
18
|
-
let nextCooldownGeneration = 1;
|
|
19
|
-
let afterSettlementForTests: (() => void) | undefined;
|
|
20
|
-
|
|
21
|
-
export function setAnthropicQuotaAfterSettlementForTests(hook: (() => void) | undefined): void {
|
|
22
|
-
afterSettlementForTests = hook;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Monotonic per-account fence shared by routing mutations and quota publication. */
|
|
26
|
-
export function anthropicCooldownGeneration(accountId: string): number {
|
|
27
|
-
return cooldownGenerationByAccount.get(accountId) ?? 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Keep same-state quota calls joinable while moving post-429 calls to a fresh flight. */
|
|
31
|
-
export function anthropicCooldownFlightKey(baseKey: string, accountId: string): string {
|
|
32
|
-
return `${baseKey}\0anthropic-cooldown:${anthropicCooldownGeneration(accountId)}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function noteAnthropicCooldownMutation(accountId: string): number {
|
|
36
|
-
const generation = nextCooldownGeneration++;
|
|
37
|
-
cooldownGenerationByAccount.set(accountId, generation);
|
|
38
|
-
return generation;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function clearAnthropicCooldownGenerations(): void {
|
|
42
|
-
cooldownGenerationByAccount.clear();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
11
|
export type AnthropicCooldownRecoveryProbe = Readonly<{
|
|
12
|
+
instance: AnthropicInstanceId;
|
|
46
13
|
requiresFreshDispatch: boolean;
|
|
47
14
|
isCurrentCredential(): boolean;
|
|
48
15
|
isCurrentFamily(): boolean;
|
|
@@ -51,86 +18,230 @@ export type AnthropicCooldownRecoveryProbe = Readonly<{
|
|
|
51
18
|
}>;
|
|
52
19
|
|
|
53
20
|
export type AnthropicQuotaRecoveryResult = Readonly<{
|
|
21
|
+
instance: AnthropicInstanceId;
|
|
54
22
|
quota: ProviderQuota;
|
|
55
23
|
/** Must be checked synchronously at each publication boundary. */
|
|
56
24
|
isCurrent(): boolean;
|
|
57
25
|
}>;
|
|
58
26
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* dispatch. A successful response may otherwise arrive after either credential replacement
|
|
62
|
-
* or a newer 429 and incorrectly make unrelated state eligible.
|
|
63
|
-
*/
|
|
64
|
-
export async function captureAnthropicCooldownRecoveryProbe(
|
|
65
|
-
accountId: string,
|
|
66
|
-
accessToken: string,
|
|
67
|
-
): Promise<AnthropicCooldownRecoveryProbe | null> {
|
|
68
|
-
const credential = getAccountCredential("anthropic", accountId);
|
|
69
|
-
if (!credential || credential.access !== accessToken) return null;
|
|
70
|
-
const generation = credentialGeneration(credential);
|
|
71
|
-
// Lazy because anthropic-routing reads the quota cache on normal request routing.
|
|
72
|
-
const routing = await import("../../oauth/anthropic-routing");
|
|
73
|
-
const cooldownGeneration = anthropicCooldownGeneration(accountId);
|
|
74
|
-
const familyGeneration = anthropicFamilyQuotaGeneration(accountId);
|
|
75
|
-
const claim = routing.captureAnthropicCooldownRecovery(accountId);
|
|
76
|
-
const isCurrentCredential = () => {
|
|
77
|
-
const current = getAccountCredential("anthropic", accountId);
|
|
78
|
-
return !!current && credentialGeneration(current) === generation;
|
|
79
|
-
};
|
|
80
|
-
return {
|
|
81
|
-
requiresFreshDispatch: claim !== null,
|
|
82
|
-
isCurrentCredential,
|
|
83
|
-
isCurrentFamily: () => anthropicFamilyQuotaGeneration(accountId) === familyGeneration,
|
|
84
|
-
isCurrent: () => isCurrentCredential()
|
|
85
|
-
&& anthropicCooldownGeneration(accountId) === cooldownGeneration,
|
|
86
|
-
settle: quota => claim === null ? "retained"
|
|
87
|
-
: routing.settleAnthropicCooldownRecovery(claim, quota),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
27
|
+
function createAnthropicCooldownRecovery(instance: AnthropicInstanceId) {
|
|
28
|
+
const familyGeneration = (accountId: string) => anthropicModelQuotaFor(instance).anthropicFamilyQuotaGeneration(accountId);
|
|
90
29
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
30
|
+
/** Check live pause and bearer ownership at the last synchronous step before usage dispatch. */
|
|
31
|
+
function assertAnthropicQuotaSendAllowed(accountId: string, token: string): void {
|
|
32
|
+
const row = getAccountCredentialWithStatus(instance, accountId);
|
|
33
|
+
if (!row || row.paused || row.needsReauth || row.credential.access !== token) {
|
|
34
|
+
throw new AnthropicQuotaProbeOwnershipError(`${instance} quota account is no longer eligible`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let cooldownGenerationByAccount: Map<string, number> | undefined;
|
|
39
|
+
let emptyGeneration = 0;
|
|
40
|
+
let nextCooldownGeneration = 1;
|
|
41
|
+
// Account lifetime is distinct from refusal/probe mutations; a valid refusal must not revoke itself.
|
|
42
|
+
let accountIncarnations: Map<string, number> | undefined;
|
|
43
|
+
let emptyAccountIncarnation = 0;
|
|
44
|
+
let nextAccountIncarnation = 1;
|
|
45
|
+
let afterSettlementForTests: (() => void) | undefined;
|
|
46
|
+
|
|
47
|
+
function setAnthropicQuotaAfterSettlementForTests(hook: (() => void) | undefined): void {
|
|
48
|
+
afterSettlementForTests = hook;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Monotonic per-account fence shared by routing mutations and quota publication. */
|
|
52
|
+
function anthropicCooldownGeneration(accountId: string): number {
|
|
53
|
+
return cooldownGenerationByAccount?.get(accountId) ?? emptyGeneration;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Keep same-state quota calls joinable while moving post-429 calls to a fresh flight. */
|
|
57
|
+
function anthropicCooldownFlightKey(baseKey: string, accountId: string): string {
|
|
58
|
+
// Flight reservation establishes an incarnation even without prior refusal evidence.
|
|
59
|
+
const generation = reserveCooldownGeneration(accountId);
|
|
60
|
+
return `${baseKey}\0${instance}-cooldown:${generation}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Active usage readers bind this key after token refresh; renewal keeps the legacy flight valid. */
|
|
64
|
+
function anthropicCredentialQuotaFlightKey(baseKey: string, accountId: string): string {
|
|
65
|
+
const row = getAccountSet(instance)?.accounts.find(row => row.id === accountId);
|
|
66
|
+
return `${anthropicCooldownFlightKey(baseKey, accountId)}\0${row ? JSON.stringify([credentialGeneration(row.credential), row.loginId, row.addedAt]) : "missing"}`;
|
|
105
67
|
}
|
|
106
|
-
|
|
107
|
-
|
|
68
|
+
|
|
69
|
+
function reserveCooldownGeneration(accountId: string): number {
|
|
70
|
+
return cooldownGenerationByAccount?.get(accountId) ?? noteAnthropicCooldownMutation(accountId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function noteAnthropicCooldownMutation(accountId: string): number {
|
|
74
|
+
const generation = nextCooldownGeneration++;
|
|
75
|
+
(cooldownGenerationByAccount ??= new Map()).set(accountId, generation);
|
|
76
|
+
return generation;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function anthropicAccountIncarnation(accountId: string): number {
|
|
80
|
+
return accountIncarnations?.get(accountId) ?? emptyAccountIncarnation;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function reserveAnthropicAccountIncarnation(accountId: string): number {
|
|
84
|
+
const existing = accountIncarnations?.get(accountId);
|
|
85
|
+
if (existing !== undefined) return existing;
|
|
86
|
+
const incarnation = nextAccountIncarnation++;
|
|
87
|
+
(accountIncarnations ??= new Map()).set(accountId, incarnation);
|
|
88
|
+
return incarnation;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function clearAnthropicCooldownGenerations(): void {
|
|
92
|
+
emptyGeneration = nextCooldownGeneration++;
|
|
93
|
+
cooldownGenerationByAccount = undefined;
|
|
94
|
+
emptyAccountIncarnation = nextAccountIncarnation++;
|
|
95
|
+
accountIncarnations = undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Bind a quota probe to both the credential and cooldown generations it observed before
|
|
100
|
+
* dispatch. A successful response may otherwise arrive after either credential replacement
|
|
101
|
+
* or a newer 429 and incorrectly make unrelated state eligible.
|
|
102
|
+
*/
|
|
103
|
+
async function captureAnthropicCooldownRecoveryProbe(
|
|
104
|
+
accountId: string,
|
|
105
|
+
accessToken: string,
|
|
106
|
+
): Promise<AnthropicCooldownRecoveryProbe | null> {
|
|
107
|
+
const credential = getAccountCredential(instance, accountId);
|
|
108
|
+
if (!credential || credential.access !== accessToken) return null;
|
|
109
|
+
const generation = credentialGeneration(credential);
|
|
110
|
+
const incarnation = reserveAnthropicAccountIncarnation(accountId);
|
|
111
|
+
const quotaEpoch = captureProviderAccountQuotaEpoch(instance);
|
|
112
|
+
const initialRow = getAccountSet(instance)?.accounts.find(row => row.id === accountId);
|
|
113
|
+
if (!initialRow || initialRow.paused || initialRow.needsReauth || credentialGeneration(initialRow.credential) !== generation) return null;
|
|
114
|
+
// Lazy because anthropic-routing reads the quota cache on normal request routing.
|
|
115
|
+
const { anthropicRoutingFor } = await import("../../oauth/anthropic-routing");
|
|
116
|
+
const routing = anthropicRoutingFor(instance);
|
|
117
|
+
const live = getAccountCredential(instance, accountId);
|
|
118
|
+
if (!live || credentialGeneration(live) !== generation) return null;
|
|
119
|
+
const cooldownGeneration = reserveCooldownGeneration(accountId);
|
|
120
|
+
const capturedFamilyGeneration = anthropicModelQuotaFor(instance).captureAnthropicFamilyQuotaGeneration(accountId);
|
|
121
|
+
const claim = routing.captureAnthropicCooldownRecovery(accountId);
|
|
122
|
+
const isCurrentCredential = () => {
|
|
123
|
+
const currentRow = getAccountSet(instance)?.accounts.find(row => row.id === accountId);
|
|
124
|
+
const current = currentRow?.credential;
|
|
125
|
+
return quotaEpoch === captureProviderAccountQuotaEpoch(instance)
|
|
126
|
+
&& anthropicAccountIncarnation(accountId) === incarnation
|
|
127
|
+
&& currentRow?.loginId === initialRow?.loginId && currentRow?.addedAt === initialRow?.addedAt
|
|
128
|
+
&& !!current && credentialGeneration(current) === generation;
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
instance,
|
|
132
|
+
requiresFreshDispatch: claim !== null,
|
|
133
|
+
isCurrentCredential,
|
|
134
|
+
isCurrentFamily: () => familyGeneration(accountId) === capturedFamilyGeneration,
|
|
135
|
+
isCurrent: () => isCurrentCredential()
|
|
136
|
+
&& anthropicCooldownGeneration(accountId) === cooldownGeneration,
|
|
137
|
+
settle: quota => !isCurrentCredential() || anthropicCooldownGeneration(accountId) !== cooldownGeneration ? "superseded"
|
|
138
|
+
: claim === null ? "retained" : routing.settleAnthropicCooldownRecovery(claim, quota),
|
|
139
|
+
};
|
|
108
140
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
141
|
+
|
|
142
|
+
/** Run one authoritative usage read and publish its recovery effect only while still owned. */
|
|
143
|
+
async function probeAnthropicQuotaWithRecovery(
|
|
144
|
+
accountId: string,
|
|
145
|
+
accessToken: string,
|
|
146
|
+
read: (requireFreshDispatch: boolean) => Promise<ProviderQuota | null>,
|
|
147
|
+
mayPublish: () => boolean,
|
|
148
|
+
): Promise<AnthropicQuotaRecoveryResult | null> {
|
|
149
|
+
const probe = await captureAnthropicCooldownRecoveryProbe(accountId, accessToken);
|
|
150
|
+
if (!probe) throw new AnthropicQuotaProbeOwnershipError(`${instance} quota probe lost credential ownership`);
|
|
151
|
+
if (!probe.isCurrent() || !mayPublish()) throw new AnthropicQuotaProbeOwnershipError(`${instance} quota probe lost dispatch ownership`);
|
|
152
|
+
let quota: ProviderQuota | null;
|
|
153
|
+
try { quota = await read(probe.requiresFreshDispatch); }
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (!probe.isCurrent() || !mayPublish()) throw new AnthropicQuotaProbeOwnershipError(`${instance} quota probe failure is stale`);
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
if (!probe.isCurrent() || !mayPublish()) {
|
|
159
|
+
throw new AnthropicQuotaProbeOwnershipError(`${instance} quota probe result is stale`);
|
|
160
|
+
}
|
|
161
|
+
if (!quota) return null;
|
|
162
|
+
const settlement = probe.settle(quota);
|
|
163
|
+
if (settlement === "superseded") {
|
|
164
|
+
throw new AnthropicQuotaProbeOwnershipError(`${instance} quota probe lost cooldown ownership`);
|
|
165
|
+
}
|
|
166
|
+
// Clearing the claimed cooldown intentionally advances the fence. Adopt that exact new
|
|
167
|
+
// generation; any later observation/429 then invalidates publication before a cache write.
|
|
168
|
+
const ownsFamily = probe.isCurrentFamily();
|
|
169
|
+
const authoritative = ownsFamily && hasAnthropicFamilyEnumeration(quota);
|
|
170
|
+
if (ownsFamily) anthropicModelQuotaFor(instance).observeAnthropicFamilyQuota(accountId, quota.customWindows ?? [], quota.updatedAt, authoritative);
|
|
171
|
+
if (!authoritative) {
|
|
172
|
+
const cached = getCachedProviderAccountQuota(instance, accountId)?.customWindows;
|
|
173
|
+
const windows = ownsFamily ? mergeAnthropicFamilyWindows(cached, quota.customWindows)
|
|
174
|
+
: cached ?? [];
|
|
175
|
+
// A superseded family enumeration has no authority, including over absent families.
|
|
176
|
+
if (!ownsFamily || windows.length) quota = { ...quota, customWindows: windows };
|
|
177
|
+
}
|
|
178
|
+
const publicationGeneration = anthropicCooldownGeneration(accountId);
|
|
179
|
+
const publicationFamilyGeneration = familyGeneration(accountId);
|
|
180
|
+
afterSettlementForTests?.();
|
|
181
|
+
return {
|
|
182
|
+
instance,
|
|
183
|
+
quota,
|
|
184
|
+
isCurrent: () => probe.isCurrentCredential()
|
|
185
|
+
&& anthropicCooldownGeneration(accountId) === publicationGeneration
|
|
186
|
+
&& familyGeneration(accountId) === publicationFamilyGeneration
|
|
187
|
+
&& mayPublish(),
|
|
188
|
+
};
|
|
113
189
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!ownsFamily || windows.length) quota = { ...quota, customWindows: windows };
|
|
190
|
+
|
|
191
|
+
function reconcileAnthropicCooldownGenerations(context: GenerationContext): number {
|
|
192
|
+
// Probe reservations include accounts with no cooldown, so every claim retires here.
|
|
193
|
+
for (const id of cooldownGenerationByAccount?.keys() ?? []) {
|
|
194
|
+
if (!context.oauthAccountKeys.has(`${instance}\0${id}`)) noteAnthropicCooldownMutation(id);
|
|
195
|
+
}
|
|
196
|
+
for (const id of accountIncarnations?.keys() ?? []) {
|
|
197
|
+
if (!context.oauthAccountKeys.has(`${instance}\0${id}`)) accountIncarnations!.set(id, nextAccountIncarnation++);
|
|
198
|
+
}
|
|
199
|
+
return 0; // Tombstones are retained, never reclaimed into an older generation.
|
|
125
200
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
201
|
+
return Object.freeze({ instance, assertAnthropicQuotaSendAllowed, setAnthropicQuotaAfterSettlementForTests,
|
|
202
|
+
anthropicCooldownGeneration, anthropicCooldownFlightKey, anthropicCredentialQuotaFlightKey, noteAnthropicCooldownMutation,
|
|
203
|
+
anthropicAccountIncarnation, reserveAnthropicAccountIncarnation,
|
|
204
|
+
clearAnthropicCooldownGenerations, captureAnthropicCooldownRecoveryProbe,
|
|
205
|
+
probeAnthropicQuotaWithRecovery, reconcileAnthropicCooldownGenerations });
|
|
206
|
+
}
|
|
207
|
+
export type AnthropicCooldownRecovery = ReturnType<typeof createAnthropicCooldownRecovery>;
|
|
208
|
+
const instances = new Map<AnthropicInstanceId, AnthropicCooldownRecovery>();
|
|
209
|
+
export function anthropicCooldownRecoveryFor(instance: AnthropicInstanceId): AnthropicCooldownRecovery {
|
|
210
|
+
let facade = instances.get(instance);
|
|
211
|
+
if (!facade) { facade = createAnthropicCooldownRecovery(instance); instances.set(instance, facade); }
|
|
212
|
+
return facade;
|
|
213
|
+
}
|
|
214
|
+
export function reconcileAllAnthropicCooldownGenerations(context: GenerationContext): number {
|
|
215
|
+
for (const facade of instances.values()) facade.reconcileAnthropicCooldownGenerations(context);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
export function clearAllAnthropicCooldownGenerations(): void {
|
|
219
|
+
for (const facade of instances.values()) facade.clearAnthropicCooldownGenerations();
|
|
220
|
+
}
|
|
221
|
+
export function assertAnthropicQuotaSendAllowed(accountId: string, token: string): void {
|
|
222
|
+
anthropicCooldownRecoveryFor("anthropic").assertAnthropicQuotaSendAllowed(accountId, token);
|
|
223
|
+
}
|
|
224
|
+
export function setAnthropicQuotaAfterSettlementForTests(hook: (() => void) | undefined): void {
|
|
225
|
+
anthropicCooldownRecoveryFor("anthropic").setAnthropicQuotaAfterSettlementForTests(hook);
|
|
226
|
+
}
|
|
227
|
+
export function anthropicCooldownGeneration(accountId: string): number {
|
|
228
|
+
return anthropicCooldownRecoveryFor("anthropic").anthropicCooldownGeneration(accountId);
|
|
229
|
+
}
|
|
230
|
+
export function anthropicCooldownFlightKey(baseKey: string, accountId: string): string {
|
|
231
|
+
return anthropicCooldownRecoveryFor("anthropic").anthropicCooldownFlightKey(baseKey, accountId);
|
|
232
|
+
}
|
|
233
|
+
export function noteAnthropicCooldownMutation(accountId: string): number {
|
|
234
|
+
return anthropicCooldownRecoveryFor("anthropic").noteAnthropicCooldownMutation(accountId);
|
|
235
|
+
}
|
|
236
|
+
export function clearAnthropicCooldownGenerations(): void {
|
|
237
|
+
anthropicCooldownRecoveryFor("anthropic").clearAnthropicCooldownGenerations();
|
|
238
|
+
}
|
|
239
|
+
export function captureAnthropicCooldownRecoveryProbe(accountId: string, accessToken: string): Promise<AnthropicCooldownRecoveryProbe | null> {
|
|
240
|
+
return anthropicCooldownRecoveryFor("anthropic").captureAnthropicCooldownRecoveryProbe(accountId, accessToken);
|
|
241
|
+
}
|
|
242
|
+
export function probeAnthropicQuotaWithRecovery(
|
|
243
|
+
accountId: string, accessToken: string,
|
|
244
|
+
read: (requireFreshDispatch: boolean) => Promise<ProviderQuota | null>, mayPublish: () => boolean,
|
|
245
|
+
): Promise<AnthropicQuotaRecoveryResult | null> {
|
|
246
|
+
return anthropicCooldownRecoveryFor("anthropic").probeAnthropicQuotaWithRecovery(accountId, accessToken, read, mayPublish);
|
|
136
247
|
}
|
|
@@ -8,7 +8,7 @@ import { apiKeyPoolEntryId } from "../api-keys";
|
|
|
8
8
|
import { getProviderRegistryEntry, providerCodexAccountMode } from "../registry";
|
|
9
9
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../openai-tiers";
|
|
10
10
|
import { CODEX_CAPACITY_MAX_QUOTA_AGE_MS, type CodexCapacityAggregation, type CodexCapacityQuota } from "../codex-capacity";
|
|
11
|
-
import { clearCachedProviderQuotas, providerQuotaRoutingBinding, type ProviderQuotaRoutingEvidence } from "../quota-routing-cache";
|
|
11
|
+
import { clearCachedProviderQuotas, providerQuotaRoutingBinding, replaceCachedProviderQuotas, type ProviderQuotaRoutingEvidence } from "../quota-routing-cache";
|
|
12
12
|
import { clearProviderApiKeyQuotaCache } from "../quota-key-accounts";
|
|
13
13
|
import { QUOTA_JSON_READ_FAILURE, readQuotaJson } from "../quota-wire";
|
|
14
14
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
@@ -101,6 +101,29 @@ export function clearProviderQuotaCache(): void {
|
|
|
101
101
|
invalidationEpoch += 1;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
const providerReportEpochs = new Map<string, number>();
|
|
105
|
+
|
|
106
|
+
export function captureProviderReportEpochs(): ReadonlyMap<string, number> {
|
|
107
|
+
return new Map(providerReportEpochs);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function providerReportEpochChanged(captured: ReadonlyMap<string, number>, provider: string): boolean {
|
|
111
|
+
return (providerReportEpochs.get(provider) ?? 0) !== (captured.get(provider) ?? 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Provider-scoped invalidation: drop one provider's report and routing row. The global epoch
|
|
116
|
+
* is untouched, so another provider's cached row and in-flight commit authority survive; an
|
|
117
|
+
* aggregate already in flight drops only this provider's row when it publishes.
|
|
118
|
+
*/
|
|
119
|
+
export function clearProviderQuotaCacheFor(provider: string): void {
|
|
120
|
+
providerReportEpochs.set(provider, (providerReportEpochs.get(provider) ?? 0) + 1);
|
|
121
|
+
if (!cache) return;
|
|
122
|
+
const reports = cache.response.reports.filter(report => report.provider !== provider);
|
|
123
|
+
cache = { ...cache, response: { ...cache.response, reports } };
|
|
124
|
+
replaceCachedProviderQuotas(reports, routingEvidence);
|
|
125
|
+
}
|
|
126
|
+
|
|
104
127
|
function cacheKey(config: OcxConfig): string {
|
|
105
128
|
const providers = Object.entries(config.providers)
|
|
106
129
|
.map(([name, provider]) => {
|
|
@@ -2,7 +2,9 @@ import { markAnthropicFamilyEnumeration } from "./anthropic-family-headers";
|
|
|
2
2
|
import { effectiveCodexAuthAccountId, fetchMainAccountInfoSnapshot, listCodexAuthAccountsSnapshot } from "../../codex/auth-api";
|
|
3
3
|
import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/main-account";
|
|
4
4
|
import { getValidAccessToken } from "../../oauth";
|
|
5
|
-
import {
|
|
5
|
+
import { loadConfig } from "../../config";
|
|
6
|
+
import { configuredAnthropicInstance, type AnthropicInstanceId } from "../anthropic-instance";
|
|
7
|
+
import { captureOAuthAccountSelection, credentialGeneration, getAccountCredential, getAccountSet } from "../../oauth/store";
|
|
6
8
|
import { hydrateKiroAccountState, persistKiroAccountState } from "../kiro-account-state-disk";
|
|
7
9
|
import { kiroProbeCurrent, kiroProbeIdentity } from "./kiro-account-probe";
|
|
8
10
|
import { fetchMuseKeyQuotaSnapshot } from "../muse-key-quota";
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
} from "./report-cache";
|
|
34
36
|
import {
|
|
35
37
|
accountCacheKey,
|
|
38
|
+
captureProviderAccountQuotaEpoch,
|
|
36
39
|
accountQuotaCache,
|
|
37
40
|
hydrateAccountQuotaCache,
|
|
38
41
|
mayCommitAccountQuotaKey,
|
|
@@ -40,7 +43,7 @@ import {
|
|
|
40
43
|
} from "./account-cache";
|
|
41
44
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
42
45
|
import type { ProviderQuota, ProviderQuotaWindow } from "../quota-types";
|
|
43
|
-
import { AnthropicQuotaProbeOwnershipError,
|
|
46
|
+
import { AnthropicQuotaProbeOwnershipError, anthropicCooldownRecoveryFor } from "./anthropic-cooldown-recovery";
|
|
44
47
|
|
|
45
48
|
const XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing";
|
|
46
49
|
const XAI_CREDITS_URL = `${XAI_BILLING_URL}?format=credits`;
|
|
@@ -336,39 +339,68 @@ async function readAnthropicUsageQuota(accessToken: string): Promise<ProviderQuo
|
|
|
336
339
|
export async function fetchAnthropicUsageQuota(
|
|
337
340
|
accessToken: string,
|
|
338
341
|
requireFreshDispatch = false,
|
|
342
|
+
): Promise<ProviderQuota | null> {
|
|
343
|
+
return fetchAnthropicUsageQuotaForInstance("anthropic", accessToken, requireFreshDispatch);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function fetchAnthropicUsageQuotaForInstance(
|
|
347
|
+
instance: AnthropicInstanceId,
|
|
348
|
+
accessToken: string,
|
|
349
|
+
requireFreshDispatch = false,
|
|
339
350
|
): Promise<ProviderQuota | null> {
|
|
340
351
|
// Recovery evidence must be requested after the claimed cooldown. Joining an older request
|
|
341
352
|
// can return after the 429 while still describing provider state from before that refusal.
|
|
342
|
-
const
|
|
353
|
+
const credentialIdentity = getAccountSet(instance)?.accounts.filter(row => row.credential.access === accessToken)
|
|
354
|
+
.map(row => [row.id, row.loginId, row.addedAt, credentialGeneration(row.credential)]);
|
|
355
|
+
const key = `${instance}\0${captureProviderAccountQuotaEpoch(instance)}\0${JSON.stringify(credentialIdentity)}\0${accessToken}`;
|
|
356
|
+
const joinable = requireFreshDispatch ? undefined : anthropicUsageInflight.get(key);
|
|
343
357
|
if (joinable) return joinable;
|
|
344
358
|
|
|
345
359
|
const probe = readAnthropicUsageQuota(accessToken).finally(() => {
|
|
346
|
-
if (anthropicUsageInflight.get(
|
|
360
|
+
if (anthropicUsageInflight.get(key) === probe) anthropicUsageInflight.delete(key);
|
|
347
361
|
});
|
|
348
|
-
anthropicUsageInflight.set(
|
|
362
|
+
anthropicUsageInflight.set(key, probe);
|
|
349
363
|
return probe;
|
|
350
364
|
}
|
|
351
365
|
|
|
352
|
-
|
|
366
|
+
/** `config` is the caller's live object; currency re-reads it instead of reloading the file. */
|
|
367
|
+
export async function fetchAnthropicQuota(provider: string, config: OcxConfig = loadConfig()): Promise<ProviderQuotaReport | null> {
|
|
368
|
+
const instance = configuredAnthropicInstance(config, provider);
|
|
369
|
+
const target = config.providers[provider];
|
|
370
|
+
if (!instance || target?.disabled || target && target.authMode !== "oauth") return null;
|
|
371
|
+
const targetIdentity = JSON.stringify(target);
|
|
372
|
+
const targetCurrent = () => configuredAnthropicInstance(config, provider) === instance
|
|
373
|
+
&& JSON.stringify(config.providers[provider]) === targetIdentity;
|
|
374
|
+
const recovery = anthropicCooldownRecoveryFor(instance);
|
|
353
375
|
// Capture the account we intend to probe before awaiting — a mid-flight active
|
|
354
376
|
// switch must not seed the wrong account's cache with this response.
|
|
355
|
-
const selection = captureOAuthAccountSelection(
|
|
377
|
+
const selection = captureOAuthAccountSelection(instance);
|
|
356
378
|
const probedAccountId = selection?.accountId;
|
|
357
|
-
const
|
|
379
|
+
const initialRow = getAccountSet(instance)?.accounts.find(row => row.id === probedAccountId);
|
|
380
|
+
const epoch = captureProviderAccountQuotaEpoch(instance);
|
|
381
|
+
const incarnation = probedAccountId ? recovery.reserveAnthropicAccountIncarnation(probedAccountId) : undefined;
|
|
382
|
+
const probedAccountKey = probedAccountId ? accountCacheKey(instance, probedAccountId) : null;
|
|
358
383
|
const writerGeneration = captureConfigGeneration();
|
|
359
384
|
let accessToken: string;
|
|
360
385
|
try {
|
|
361
|
-
accessToken = await getValidAccessToken(
|
|
386
|
+
accessToken = await getValidAccessToken(instance);
|
|
362
387
|
} catch {
|
|
363
388
|
return null;
|
|
364
389
|
}
|
|
365
390
|
if (!probedAccountId || !probedAccountKey) return null;
|
|
391
|
+
const currentRow = getAccountSet(instance)?.accounts.find(row => row.id === probedAccountId);
|
|
392
|
+
if (!initialRow || !currentRow || initialRow.loginId !== currentRow.loginId || initialRow.addedAt !== currentRow.addedAt
|
|
393
|
+
|| epoch !== captureProviderAccountQuotaEpoch(instance) || recovery.anthropicAccountIncarnation(probedAccountId) !== incarnation) return null;
|
|
366
394
|
let quota: ProviderQuota | null;
|
|
367
395
|
let anthropicCurrent: (() => boolean) | undefined;
|
|
368
396
|
try {
|
|
369
|
-
const result = await probeAnthropicQuotaWithRecovery(probedAccountId, accessToken,
|
|
370
|
-
fresh => {
|
|
371
|
-
|
|
397
|
+
const result = await recovery.probeAnthropicQuotaWithRecovery(probedAccountId, accessToken,
|
|
398
|
+
fresh => {
|
|
399
|
+
if (!targetCurrent()) throw new AnthropicQuotaProbeOwnershipError("quota target changed");
|
|
400
|
+
recovery.assertAnthropicQuotaSendAllowed(probedAccountId, accessToken);
|
|
401
|
+
return fetchAnthropicUsageQuotaForInstance(instance, accessToken, fresh);
|
|
402
|
+
},
|
|
403
|
+
() => targetCurrent() && mayCommitAccountQuotaKey(probedAccountKey, writerGeneration));
|
|
372
404
|
if (result && !result.isCurrent()) return null;
|
|
373
405
|
quota = result?.quota ?? null;
|
|
374
406
|
anthropicCurrent = result?.isCurrent;
|
|
@@ -380,14 +412,14 @@ export async function fetchAnthropicQuota(provider: string): Promise<ProviderQuo
|
|
|
380
412
|
// Share the active-account probe with the per-account cache so Providers-page
|
|
381
413
|
// loads do not double-hit Anthropic's rate-limited usage endpoint.
|
|
382
414
|
if (probedAccountId && probedAccountKey) {
|
|
383
|
-
const stillOwnsToken = getAccountCredential(
|
|
415
|
+
const stillOwnsToken = getAccountCredential(instance, probedAccountId)?.access === accessToken;
|
|
384
416
|
if (stillOwnsToken && anthropicCurrent?.() && mayCommitAccountQuotaKey(probedAccountKey, writerGeneration)) {
|
|
385
417
|
accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota, isCurrent: anthropicCurrent });
|
|
386
418
|
}
|
|
387
419
|
}
|
|
388
420
|
const quotaReport = report(provider, "anthropic:oauth-usage", quota);
|
|
389
421
|
if (quotaReport && anthropicCurrent) {
|
|
390
|
-
accountReportCurrent.set(quotaReport, () => anthropicCurrent() && getAccountSet(
|
|
422
|
+
accountReportCurrent.set(quotaReport, () => anthropicCurrent() && getAccountSet(instance)?.activeAccountId === probedAccountId);
|
|
391
423
|
}
|
|
392
424
|
return quotaReport;
|
|
393
425
|
}
|