@cortexkit/anthropic-auth-core 2.4.5 → 2.4.7

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.
@@ -400,6 +400,12 @@ export declare function getScopedQuotaWindowForModel(quota: OAuthQuotaSnapshot |
400
400
  export declare function quotaSnapshotModelScopeIsExhausted(quota: OAuthQuotaSnapshot | undefined, model: unknown): boolean;
401
401
  export declare function quotaSnapshotPassesModelScope(quota: OAuthQuotaSnapshot | undefined, model: unknown): boolean;
402
402
  export declare function quotaSnapshotPassesPolicy(quota: OAuthQuotaSnapshot | undefined, storage: AccountStorage | null): boolean;
403
+ /** A poll's omitted session window is evidence, unlike a partial header harvest. */
404
+ export declare function quotaSnapshotOmitsFiveHour(quota: OAuthQuotaSnapshot | undefined): quota is OAuthQuotaSnapshot & {
405
+ checkedAt: number;
406
+ five_hour?: undefined;
407
+ seven_day: AccountQuotaWindow;
408
+ };
403
409
  export declare function quotaSnapshotHasStandardWindows(quota: OAuthQuotaSnapshot | undefined): quota is OAuthQuotaSnapshot;
404
410
  export declare const DEFAULT_KILLSWITCH_THRESHOLDS: Record<QuotaWindowName | 'scoped', number>;
405
411
  export declare function normalizeKillswitchThresholds(thresholds: KillswitchThresholds | undefined): Record<QuotaWindowName | 'scoped', number>;
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export { CLAUDE_CODE_DEFAULT_BETAS, CLAUDE_MODEL_CAPABILITIES, getClaudeModelCap
25
25
  export * from './model-remap.ts';
26
26
  export * from './models.ts';
27
27
  export * from './native-account-refusal.ts';
28
- export type { NativeAccountRuntime, NativeAccountRuntimeOptions, NativeAccountWriteInput, NativeApiSubject, NativeLocalAuthorizeOptions, NativeRelayUpdate, } from './native-account-runtime.ts';
28
+ export type { NativeAccountPolicy, NativeAccountRuntime, NativeAccountRuntimeOptions, NativeAccountWriteInput, NativeApiSubject, NativeLocalAuthorizeOptions, NativeQuotaPolicyLimit, NativeRelayUpdate, } from './native-account-runtime.ts';
29
29
  export { acquireNativeAccountRuntime, createNativeAccountRuntime, nativeAccountPolicy, } from './native-account-runtime.ts';
30
30
  export type { NativeAccountMetadataPatch, NativeAccountSnapshot, NativeAccountView, } from './native-account-view.ts';
31
31
  export { formatNativeAccountState, nativeAccountCanServe, } from './native-account-view.ts';
@@ -62,6 +62,7 @@ export * from './native-usage-publication.ts';
62
62
  export { NATIVE_VAULT_NO_ACCOUNT_MESSAGE, NativeVaultNoAccountError, readNativeAccountsForRequest, } from './native-vault-admission.ts';
63
63
  export type { NativePiAuthSlot, NativePiAuthSlotErrorCode, NativePiAuthSlotInspection, NativePiAuthSlotOptions, NativeVaultDisconnectOptions, NativeVaultDisconnectResult, } from './native-vault-connection.ts';
64
64
  export { disconnectNativeVaultForHost, NativePiAuthSlotError, nativePiAuthSlotPaths, openNativePiAuthSlot, } from './native-vault-connection.ts';
65
+ export { createNativeVaultNoRouteResponse, NativeVaultQuotaPolicyError, } from './native-vault-refusal.ts';
65
66
  export type { NativeVaultQuotaPurpose, NativeVaultRuntime, NativeVaultRuntimeOptions, NativeVaultWatch, NativeVaultWatchOptions, } from './native-vault-runtime.ts';
66
67
  export { acquireNativeVaultRuntime, createNativeVaultRuntime, discoverNativeVaultInventory, publishNativeVaultRosterSeed, publishNativeVaultRuntimeSeed, } from './native-vault-runtime.ts';
67
68
  export * from './oauth-origin.ts';
package/dist/index.js CHANGED
@@ -4509,6 +4509,8 @@ function mergeHeaderOwnedWindow(existingSnapshot, incomingSnapshot, key) {
4509
4509
  const incoming = incomingSnapshot[key];
4510
4510
  if (!incoming)
4511
4511
  return existing;
4512
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(existingSnapshot) && incoming.checkedAt <= (existingSnapshot.checkedAt ?? 0))
4513
+ return;
4512
4514
  if (!existing)
4513
4515
  return incoming;
4514
4516
  if (incoming.checkedAt > existing.checkedAt)
@@ -4545,6 +4547,8 @@ function fieldSourcesForMergedQuota(existing, incoming, merged) {
4545
4547
  if (source)
4546
4548
  fieldSources[field] = source;
4547
4549
  }
4550
+ if (!merged.five_hour && quotaSnapshotOmitsFiveHour(existing))
4551
+ fieldSources.five_hour = "poll";
4548
4552
  return Object.keys(fieldSources).length > 0 ? fieldSources : undefined;
4549
4553
  }
4550
4554
  function mergeHeaderQuotaForPersistence(existing, incoming) {
@@ -4560,7 +4564,10 @@ function mergeHeaderQuotaForPersistence(existing, incoming) {
4560
4564
  extraUsage: existing.extraUsage ?? incoming.extraUsage,
4561
4565
  fallbackAdvised: incoming.fieldSources?.fallbackAdvised !== undefined ? incoming.fallbackAdvised : existing.fallbackAdvised ?? incoming.fallbackAdvised,
4562
4566
  bindingWindow: preservePollBinding ? existing.bindingWindow : incoming.bindingWindow ?? existing.bindingWindow,
4563
- bindingWindowSource: preservePollBinding ? "poll" : incoming.bindingWindowSource ?? existing.bindingWindowSource
4567
+ bindingWindowSource: preservePollBinding ? "poll" : incoming.bindingWindowSource ?? existing.bindingWindowSource,
4568
+ ...quotaSnapshotOmitsFiveHour(existing) && {
4569
+ checkedAt: existing.checkedAt
4570
+ }
4564
4571
  };
4565
4572
  const fieldSources = fieldSourcesForMergedQuota(existing, incoming, merged);
4566
4573
  return {
@@ -4993,20 +5000,25 @@ function quotaSnapshotPassesPolicy(quota, storage) {
4993
5000
  if (!quotaEnabled(storage))
4994
5001
  return true;
4995
5002
  const thresholds = getQuotaMinimumRemainingThresholds(storage);
5003
+ let sawUnknownWindow = false;
4996
5004
  for (const key of ["five_hour", "seven_day"]) {
5005
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(quota))
5006
+ continue;
4997
5007
  const window = quota?.[key];
4998
- if (!window)
4999
- return !failClosedOnUnknownQuota(storage);
5000
- if (!Number.isFinite(window.remainingPercent)) {
5001
- return !failClosedOnUnknownQuota(storage);
5008
+ if (!window || !Number.isFinite(window.remainingPercent)) {
5009
+ sawUnknownWindow = true;
5010
+ continue;
5002
5011
  }
5003
5012
  if (window.remainingPercent < thresholds[key])
5004
5013
  return false;
5005
5014
  }
5006
- return true;
5015
+ return !sawUnknownWindow || !failClosedOnUnknownQuota(storage);
5016
+ }
5017
+ function quotaSnapshotOmitsFiveHour(quota) {
5018
+ return Boolean((quota?.source === "poll" || quota?.fieldSources?.five_hour === "poll") && quota && !quota.five_hour && quota.seven_day && Number.isFinite(quota.checkedAt) && (quota.checkedAt ?? 0) > 0);
5007
5019
  }
5008
5020
  function quotaSnapshotHasStandardWindows(quota) {
5009
- return Boolean(quota?.five_hour && quota.seven_day);
5021
+ return Boolean(quota?.seven_day && (quota.five_hour || quotaSnapshotOmitsFiveHour(quota)));
5010
5022
  }
5011
5023
  var DEFAULT_KILLSWITCH_THRESHOLDS = {
5012
5024
  five_hour: 5,
@@ -5040,6 +5052,8 @@ function killswitchPassesPolicy(quota, storage, accountId, modelId) {
5040
5052
  const thresholds = getKillswitchThresholdsForAccount(storage, accountId);
5041
5053
  let sawUnknownWindow = false;
5042
5054
  for (const key of ["five_hour", "seven_day"]) {
5055
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(quota))
5056
+ continue;
5043
5057
  const window = quota?.[key];
5044
5058
  if (!window || !Number.isFinite(window.remainingPercent)) {
5045
5059
  if (thresholds[key] > 0)
@@ -5106,6 +5120,8 @@ function getQuotaNextRefreshAt(quota, storage, now) {
5106
5120
  const thresholds = getQuotaMinimumRemainingThresholds(storage);
5107
5121
  const blockedResetTimes = [];
5108
5122
  for (const key of ["five_hour", "seven_day"]) {
5123
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(quota))
5124
+ continue;
5109
5125
  const window = quota?.[key];
5110
5126
  if (!window)
5111
5127
  return capAtOldestWindow(now + intervalMs);
@@ -18792,6 +18808,13 @@ var nativeQuotaCodec = {
18792
18808
  return toNativeQuotaMap(mergeHeaderQuotaForPersistence(previous, incoming));
18793
18809
  const previousTime = quotaSnapshotCheckedAt(previous);
18794
18810
  const incomingTime = quotaSnapshotCheckedAt(incoming);
18811
+ if (incoming.source === "poll" && incoming.checkedAt !== undefined && previous.checkedAt !== undefined && (previous.source === "poll" || quotaSnapshotOmitsFiveHour(previous)) && incoming.checkedAt < previous.checkedAt)
18812
+ return toNativeQuotaMap(previous);
18813
+ if (incoming.source === "poll" && incoming.seven_day && !incoming.five_hour && incoming.checkedAt !== undefined && incoming.checkedAt > (previous.five_hour?.checkedAt ?? 0) && (previous.source !== "poll" || incoming.checkedAt >= (previous.checkedAt ?? 0)))
18814
+ return toNativeQuotaMap(previous.source === "headers" ? mergeHeaderQuotaForPersistence(incoming, {
18815
+ ...previous,
18816
+ five_hour: undefined
18817
+ }) : incoming);
18795
18818
  return toNativeQuotaMap(previousTime > incomingTime || previousTime === incomingTime && previous.source === "poll" && incoming.source !== "poll" ? previous : incoming);
18796
18819
  }
18797
18820
  };
@@ -20504,6 +20527,12 @@ function normalizeQuotaHeaders(headers, now = Date.now()) {
20504
20527
  };
20505
20528
  }
20506
20529
  function mergeHeaderQuotaSnapshot(existing, incoming) {
20530
+ if (quotaSnapshotOmitsFiveHour(existing) && incoming.five_hour && incoming.five_hour.checkedAt <= existing.checkedAt) {
20531
+ return mergeHeaderQuotaSnapshot(existing, {
20532
+ ...incoming,
20533
+ five_hour: undefined
20534
+ });
20535
+ }
20507
20536
  const {
20508
20537
  scoped: existingScoped,
20509
20538
  extraUsage: existingExtraUsage,
@@ -20525,6 +20554,8 @@ function mergeHeaderQuotaSnapshot(existing, incoming) {
20525
20554
  if (source2)
20526
20555
  fieldSources2[field] = source2;
20527
20556
  }
20557
+ if (quotaSnapshotOmitsFiveHour(existing) && !incoming.five_hour)
20558
+ fieldSources2.five_hour = "poll";
20528
20559
  return {
20529
20560
  ...existingWithoutPollOwnedFields,
20530
20561
  ...incomingWithoutPollOwnedFields,
@@ -20534,7 +20565,10 @@ function mergeHeaderQuotaSnapshot(existing, incoming) {
20534
20565
  bindingWindow: existing?.bindingWindowSource === "poll" ? existing.bindingWindow : incoming.bindingWindow ?? existing?.bindingWindow,
20535
20566
  bindingWindowSource: existing?.bindingWindowSource === "poll" ? "poll" : incoming.bindingWindowSource ?? existing?.bindingWindowSource,
20536
20567
  ...Object.keys(fieldSources2).length > 0 && { fieldSources: fieldSources2 },
20537
- source: "headers"
20568
+ source: "headers",
20569
+ ...quotaSnapshotOmitsFiveHour(existing) && {
20570
+ checkedAt: existing.checkedAt
20571
+ }
20538
20572
  };
20539
20573
  }
20540
20574
 
@@ -20575,7 +20609,8 @@ function createStickyNoRouteResponse(input) {
20575
20609
  const retryAfter = killswitchRetryAfterSeconds(firstQuota, remainingQuotas.map((quota2) => ({ quota: quota2 })), input.now ?? Date.now(), scopedBlocked ? modelId : undefined);
20576
20610
  const minutes = Math.floor(retryAfter / 60);
20577
20611
  const seconds = retryAfter % 60;
20578
- const reason = scopedBlocked && modelName ? `${modelName} weekly limit reached, no routable OAuth accounts.` : "No OAuth account currently satisfies sticky-balanced quota policy.";
20612
+ const limitNames = input.vaultPolicyLimits?.map((limit) => limit === "model-scoped" ? `${modelName ?? "Model"} scoped limit` : limit === "killswitch" ? "killswitch limit" : "quota floor");
20613
+ const reason = limitNames ? `No vault account currently satisfies quota policy: ${limitNames.join(", ")}. Open /claude → Killswitch / Quota to change these limits.` : scopedBlocked && modelName ? `${modelName} weekly limit reached, no routable OAuth accounts.` : "No OAuth account currently satisfies sticky-balanced quota policy.";
20579
20614
  const message = `${reason} Retry in ${minutes}m ${seconds}s.`;
20580
20615
  return new Response(JSON.stringify({
20581
20616
  type: "error",
@@ -20686,6 +20721,8 @@ function sessionKey(sessionId) {
20686
20721
  function stickyQuotaSnapshotIsFresh(quota2, storage, now = Date.now(), modelId) {
20687
20722
  const maxAge = getQuotaCheckIntervalMs(storage);
20688
20723
  const standardFresh = ["five_hour", "seven_day"].every((key) => {
20724
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(quota2))
20725
+ return true;
20689
20726
  const window2 = quota2?.[key];
20690
20727
  return Boolean(window2 && now - window2.checkedAt < maxAge);
20691
20728
  });
@@ -20740,14 +20777,23 @@ function stickyRouteCandidateWeight(input) {
20740
20777
  }
20741
20778
  const weights = [];
20742
20779
  for (const key of ["five_hour", "seven_day"]) {
20780
+ if (key === "five_hour" && quotaSnapshotOmitsFiveHour(quota2))
20781
+ continue;
20743
20782
  const window2 = quota2[key];
20744
20783
  if (!window2 || !Number.isFinite(window2.remainingPercent))
20745
20784
  return 0;
20746
- weights.push(sustainableWindowWeight(window2.remainingPercent, remainingThreshold(storage, candidate.accountId, key), window2.resetsAt, now));
20785
+ const threshold = remainingThreshold(storage, candidate.accountId, key);
20786
+ if (window2.remainingPercent <= threshold) {
20787
+ const quotaThreshold = getQuotaMinimumRemainingThresholds(storage)[key];
20788
+ input.onPolicyBlock?.(isKillswitchEnabled(storage) && threshold > quotaThreshold ? "killswitch" : "quota-floor");
20789
+ }
20790
+ weights.push(sustainableWindowWeight(window2.remainingPercent, threshold, window2.resetsAt, now));
20747
20791
  }
20748
20792
  if (input.family === "fable") {
20749
20793
  const window2 = getScopedQuotaWindowForModel(quota2, input.modelId);
20750
20794
  if (window2 && Number.isFinite(window2.remainingPercent)) {
20795
+ if (window2.remainingPercent <= remainingThreshold(storage, candidate.accountId, "scoped"))
20796
+ input.onPolicyBlock?.("model-scoped");
20751
20797
  weights.push(sustainableWindowWeight(window2.remainingPercent, remainingThreshold(storage, candidate.accountId, "scoped"), window2.resetsAt, now));
20752
20798
  }
20753
20799
  }
@@ -26565,19 +26611,19 @@ function normalizeInsulaClaudeQuota(entry2, context) {
26565
26611
  kind: "weekly",
26566
26612
  minutes: 10080
26567
26613
  });
26568
- if (!five_hour || !seven_day)
26614
+ if (!seven_day || usage?.primary != null && !five_hour)
26569
26615
  return;
26570
26616
  const scoped = scopedReadings(usage?.extraRateWindows, checkedAt);
26571
26617
  const extraUsage2 = extraUsageReading(entry2.pools ?? usage?.pools);
26572
26618
  return {
26573
- five_hour,
26619
+ ...five_hour && { five_hour },
26574
26620
  seven_day,
26575
26621
  scoped,
26576
26622
  ...extraUsage2 && { extraUsage: extraUsage2 },
26577
26623
  source: "poll",
26578
26624
  checkedAt,
26579
26625
  fieldSources: {
26580
- five_hour: "poll",
26626
+ ...five_hour && { five_hour: "poll" },
26581
26627
  seven_day: "poll",
26582
26628
  scoped: "poll",
26583
26629
  ...extraUsage2 && { extraUsage: "poll" }
@@ -26594,7 +26640,7 @@ function newer(reading2, stored) {
26594
26640
  function mergeInsulaQuota(reading2, stored) {
26595
26641
  if (!stored || quotaSnapshotCheckedAt(stored) <= reading2.checkedAt)
26596
26642
  return reading2;
26597
- const five_hour = newer(reading2.five_hour, stored.five_hour);
26643
+ const five_hour = quotaSnapshotOmitsFiveHour(stored) ? undefined : !reading2.five_hour && (stored.five_hour?.checkedAt ?? 0) <= reading2.checkedAt ? undefined : newer(reading2.five_hour, stored.five_hour);
26598
26644
  const seven_day = newer(reading2.seven_day, stored.seven_day);
26599
26645
  const scoped = new Map(reading2.scoped.map((window2) => [window2.id, window2]));
26600
26646
  for (const window2 of stored.scoped ?? []) {
@@ -26609,10 +26655,10 @@ function mergeInsulaQuota(reading2, stored) {
26609
26655
  fieldSources2.seven_day = stored.fieldSources.seven_day;
26610
26656
  return {
26611
26657
  ...reading2,
26612
- ...five_hour && { five_hour },
26658
+ five_hour,
26613
26659
  ...seven_day && { seven_day },
26614
26660
  scoped: [...scoped.values()],
26615
- checkedAt: Math.max(reading2.checkedAt, quotaSnapshotCheckedAt(stored)),
26661
+ checkedAt: quotaSnapshotOmitsFiveHour(stored) ? stored.checkedAt : quotaSnapshotOmitsFiveHour(reading2) ? reading2.checkedAt : Math.max(reading2.checkedAt, quotaSnapshotCheckedAt(stored)),
26616
26662
  fieldSources: fieldSources2
26617
26663
  };
26618
26664
  }
@@ -26898,9 +26944,11 @@ function pauseOnRealTimer(ms) {
26898
26944
  class QuotaRefreshDeferred extends Error {
26899
26945
  }
26900
26946
  function mergePollCompletionWithNewerHeaders(current, polled) {
26947
+ if (quotaSnapshotOmitsFiveHour(current) && (polled.checkedAt ?? quotaSnapshotCheckedAt(polled)) < current.checkedAt)
26948
+ return current;
26901
26949
  if (current?.source !== "headers")
26902
26950
  return polled;
26903
- const fiveHourIsNewer = Boolean(current.five_hour && current.five_hour.checkedAt > (polled.five_hour?.checkedAt ?? 0));
26951
+ const fiveHourIsNewer = Boolean(current.five_hour && current.five_hour.checkedAt > (polled.five_hour?.checkedAt ?? polled.checkedAt ?? 0));
26904
26952
  const sevenDayIsNewer = Boolean(current.seven_day && current.seven_day.checkedAt > (polled.seven_day?.checkedAt ?? 0));
26905
26953
  if (!fiveHourIsNewer && !sevenDayIsNewer)
26906
26954
  return polled;
@@ -30239,8 +30287,15 @@ function acquireNativeAccountRuntime(options) {
30239
30287
  function nativeAccountPolicy(account, snapshot, modelId) {
30240
30288
  if (!nativeAccountCanServe(account))
30241
30289
  return { status: "blocked", reason: "account-disabled" };
30242
- if (!quotaSnapshotPassesPolicy(account.quota, snapshot.policyStorage) || !quotaSnapshotPassesModelScope(account.quota, modelId) || !killswitchPassesPolicy(account.quota, snapshot.policyStorage, account.id === "main" ? undefined : account.id, modelId))
30243
- return { status: "blocked", reason: "quota-ineligible" };
30290
+ const quotaPolicyLimits = [];
30291
+ if (!quotaSnapshotPassesPolicy(account.quota, snapshot.policyStorage))
30292
+ quotaPolicyLimits.push("quota-floor");
30293
+ if (!quotaSnapshotPassesModelScope(account.quota, modelId))
30294
+ quotaPolicyLimits.push("model-scoped");
30295
+ if (!killswitchPassesPolicy(account.quota, snapshot.policyStorage, account.id === "main" ? undefined : account.id, modelId))
30296
+ quotaPolicyLimits.push("killswitch");
30297
+ if (quotaPolicyLimits.length)
30298
+ return { status: "blocked", reason: "quota-ineligible", quotaPolicyLimits };
30244
30299
  return { status: "allowed" };
30245
30300
  }
30246
30301
  function createNativeAccountRuntime(options) {
@@ -35238,6 +35293,60 @@ async function disconnectNativeVaultForHost(options) {
35238
35293
  closeNativeVaultAuthority(paths.tokenPath);
35239
35294
  return result;
35240
35295
  }
35296
+ // src/native-vault-refusal.ts
35297
+ function createNativeVaultNoRouteResponse(input) {
35298
+ const { snapshot, modelId } = input;
35299
+ const accounts = snapshot.accounts.filter((account) => account.type === "oauth");
35300
+ if (accounts.some((account) => account.enabled && account.state === "needs_reauth"))
35301
+ throw new NativeVaultNoAccountError;
35302
+ const active2 = accounts.filter(nativeAccountCanServe);
35303
+ const limits = new Set;
35304
+ for (const account of active2) {
35305
+ const policy = input.policies?.get(account.id) ?? nativeAccountPolicy(account, snapshot, modelId);
35306
+ for (const limit of policy.quotaPolicyLimits ?? [])
35307
+ limits.add(limit);
35308
+ if (input.sticky) {
35309
+ stickyRouteCandidateWeight({
35310
+ candidate: {
35311
+ accountId: account.id,
35312
+ order: 0,
35313
+ quota: account.quota ?? { kind: "unknown" }
35314
+ },
35315
+ family: stickyRouteFamilyForModel(modelId),
35316
+ modelId,
35317
+ storage: snapshot.policyStorage,
35318
+ onPolicyBlock: (limit) => limits.add(limit)
35319
+ });
35320
+ }
35321
+ }
35322
+ if (!limits.size)
35323
+ throw new NativeVaultNoAccountError;
35324
+ return createStickyNoRouteResponse({
35325
+ routeQuotas: active2.flatMap((account) => account.quota ? [account.quota] : []),
35326
+ modelId,
35327
+ vaultPolicyLimits: [...limits]
35328
+ });
35329
+ }
35330
+
35331
+ class NativeVaultQuotaPolicyError extends Error {
35332
+ response;
35333
+ body;
35334
+ kind = "quota-policy";
35335
+ status = 429;
35336
+ headers;
35337
+ retryAfter;
35338
+ constructor(response, body) {
35339
+ super(body.error.message);
35340
+ this.response = response;
35341
+ this.body = body;
35342
+ this.name = "NativeVaultQuotaPolicyError";
35343
+ this.headers = new Headers(response.headers);
35344
+ this.retryAfter = Number(this.headers.get("retry-after"));
35345
+ }
35346
+ static async fromResponse(response) {
35347
+ return new NativeVaultQuotaPolicyError(response, await response.clone().json());
35348
+ }
35349
+ }
35241
35350
  // src/oauth-origin.ts
35242
35351
  var ANTHROPIC_OAUTH_ORIGIN = "https://api.anthropic.com";
35243
35352
  var ANTHROPIC_OAUTH_ORIGIN_REFUSAL = "Claude OAuth requests can only go to https://api.anthropic.com. A provider baseURL that points elsewhere is not used for Claude sign-in accounts.";
@@ -35687,6 +35796,12 @@ class PrimeManager {
35687
35796
  });
35688
35797
  return;
35689
35798
  }
35799
+ if (quotaSnapshotOmitsFiveHour(evaluation.storedQuota)) {
35800
+ logger.debug("prime", "no five-hour limit", {
35801
+ account: evaluation.label
35802
+ });
35803
+ return;
35804
+ }
35690
35805
  const storedResetEpoch = storedResetMs(evaluation.storedFiveHour);
35691
35806
  if (storedResetEpoch !== undefined && now < storedResetEpoch + PRIME_DUE_OFFSET_MS) {
35692
35807
  logger.trace("prime", "not due", { account: evaluation.label });
@@ -35744,6 +35859,12 @@ class PrimeManager {
35744
35859
  });
35745
35860
  return;
35746
35861
  }
35862
+ if (quotaSnapshotOmitsFiveHour(fresh)) {
35863
+ logger.debug("prime", "no five-hour limit", {
35864
+ account: evaluation.label
35865
+ });
35866
+ return;
35867
+ }
35747
35868
  if (!killswitchPassesPolicy(fresh, reloaded, evaluation.id, CLAUDE_HAIKU_4_5_MODEL_ID)) {
35748
35869
  logger.debug("prime", "ineligible", {
35749
35870
  account: evaluation.label,
@@ -39153,6 +39274,7 @@ export {
39153
39274
  quotaTextParts,
39154
39275
  quotaSnapshotPassesPolicy,
39155
39276
  quotaSnapshotPassesModelScope,
39277
+ quotaSnapshotOmitsFiveHour,
39156
39278
  quotaSnapshotModelScopeIsExhausted,
39157
39279
  quotaSnapshotHasStandardWindows,
39158
39280
  quotaSnapshotCheckedAt,
@@ -39389,6 +39511,7 @@ export {
39389
39511
  createProviderErrorRedactor,
39390
39512
  createProcessFence,
39391
39513
  createNativeVaultRuntime,
39514
+ createNativeVaultNoRouteResponse,
39392
39515
  createNativeUsagePublisher,
39393
39516
  createNativeUi,
39394
39517
  createNativePoolStore,
@@ -39501,6 +39624,7 @@ export {
39501
39624
  PARAGRAPH_REMOVAL_ANCHORS,
39502
39625
  OPENCODE_IDENTITY_PREFIX,
39503
39626
  OAUTH_SCOPES,
39627
+ NativeVaultQuotaPolicyError,
39504
39628
  NativeVaultNoAccountError,
39505
39629
  NativeTransientAccountError,
39506
39630
  NativeRuntimeError,
@@ -2,7 +2,6 @@ import type { InsulaQuotaContext, InsulaUsageEntry } from "./internal-types/clau
2
2
  import { type AccountQuotaWindow, type AccountScopedQuotaWindow, type OAuthQuotaSnapshot } from './accounts.ts';
3
3
  /** An insula reading in the shape our usage poll produces, before the account identity is attached. */
4
4
  export type InsulaClaudeQuota = OAuthQuotaSnapshot & {
5
- five_hour: AccountQuotaWindow;
6
5
  seven_day: AccountQuotaWindow;
7
6
  scoped: AccountScopedQuotaWindow[];
8
7
  checkedAt: number;
@@ -10,8 +9,8 @@ export type InsulaClaudeQuota = OAuthQuotaSnapshot & {
10
9
  /**
11
10
  * Convert insula's Claude entry into our usage-poll snapshot. Every window's
12
11
  * checkedAt is when insula fetched the reading, never when we read its cache,
13
- * so a newer header or poll reading still wins. Both standard windows must be
14
- * present; otherwise this returns undefined and our own poll stays in charge.
12
+ * so a newer header or poll reading still wins. The weekly window is required;
13
+ * an absent primary slot means the five-hour limit does not apply.
15
14
  * The tertiary slot (legacy seven_day_opus/sonnet) is not a window we model.
16
15
  */
17
16
  export declare function normalizeInsulaClaudeQuota(entry: InsulaUsageEntry, context: InsulaQuotaContext): InsulaClaudeQuota | undefined;
@@ -127,8 +127,12 @@ export interface NativeAccountRuntime {
127
127
  }
128
128
  /** Host instances own leases, while stores and the vault connection live until the last release. */
129
129
  export declare function acquireNativeAccountRuntime(options: NativeAccountRuntimeOptions): NativeAccountRuntime;
130
+ export type NativeQuotaPolicyLimit = 'quota-floor' | 'killswitch' | 'model-scoped';
131
+ export type NativeAccountPolicy = NativeLocalExternalPolicy & {
132
+ quotaPolicyLimits?: readonly NativeQuotaPolicyLimit[];
133
+ };
130
134
  /** Evaluate quota and killswitch policy for this caller's model, outside shared credential refresh work. */
131
- export declare function nativeAccountPolicy(account: NativeAccountView, snapshot: NativeAccountSnapshot, modelId?: string): NativeLocalExternalPolicy;
135
+ export declare function nativeAccountPolicy(account: NativeAccountView, snapshot: NativeAccountSnapshot, modelId?: string): NativeAccountPolicy;
132
136
  /** The local migration journal records completed transfer from host credentials and sidecars to the credential pool. Local use refuses without that record. Host vault enrollment instead selects independent settings and metadata, leaving local credentials, journal and metadata untouched. */
133
137
  export declare function createNativeAccountRuntime(options: NativeAccountRuntimeOptions): NativeAccountRuntime;
134
138
  export declare function primeDelta(delta: PrimeUsageDelta): {
@@ -0,0 +1,28 @@
1
+ import { type NativeAccountPolicy } from './native-account-runtime.ts';
2
+ import { type NativeAccountSnapshot } from './native-account-view.ts';
3
+ /** Preserve authentication precedence, then explain the shared policy's exclusions. */
4
+ export declare function createNativeVaultNoRouteResponse(input: {
5
+ snapshot: NativeAccountSnapshot;
6
+ modelId?: string;
7
+ policies?: ReadonlyMap<string, NativeAccountPolicy>;
8
+ sticky?: boolean;
9
+ }): Response;
10
+ type QuotaPolicyBody = {
11
+ type: 'error';
12
+ error: {
13
+ type: 'rate_limit_error';
14
+ message: string;
15
+ };
16
+ };
17
+ /** OpenCode 2 selection refuses before sending; its chooser cannot return a Response. */
18
+ export declare class NativeVaultQuotaPolicyError extends Error {
19
+ readonly response: Response;
20
+ readonly body: QuotaPolicyBody;
21
+ readonly kind = "quota-policy";
22
+ readonly status = 429;
23
+ readonly headers: Headers;
24
+ readonly retryAfter: number;
25
+ private constructor();
26
+ static fromResponse(response: Response): Promise<NativeVaultQuotaPolicyError>;
27
+ }
28
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { type AccountOperationError, type AccountStorage, type OAuthQuotaSnapshot } from './accounts.ts';
2
+ import type { NativeQuotaPolicyLimit } from './native-account-runtime.ts';
2
3
  import { type NativeLockEvent } from './native-lock-observer.ts';
3
4
  import type { NativeVaultRosterDocument } from './native-roster-seed.ts';
4
5
  import { type NativeVaultRoutingSeed } from './native-vault-seed.ts';
@@ -60,6 +61,7 @@ export declare function createStickyNoRouteResponse(input: {
60
61
  mainRefreshError?: AccountOperationError;
61
62
  fallbackReauthLabels?: readonly string[];
62
63
  routeQuotas: readonly OAuthQuotaSnapshot[];
64
+ vaultPolicyLimits?: readonly NativeQuotaPolicyLimit[];
63
65
  modelId?: string;
64
66
  now?: number;
65
67
  }): Response;
@@ -86,6 +88,7 @@ export declare function stickyRouteCandidateWeight(input: {
86
88
  modelId?: string;
87
89
  storage: AccountStorage | null;
88
90
  now?: number;
91
+ onPolicyBlock?: (limit: NativeQuotaPolicyLimit) => void;
89
92
  }): number;
90
93
  export declare function stickyRouteKnownFableExhausted(candidate: StickyRouteCandidate, storage: AccountStorage | null, now?: number): boolean;
91
94
  export declare function decideStickyQuotaFailure(input: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",