@cortexkit/anthropic-auth-core 2.2.5 → 2.2.6

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/dist/index.js CHANGED
@@ -21380,6 +21380,77 @@ function createNativeLocalCredentialService(options) {
21380
21380
  return Object.freeze({ authorize: authorize2 });
21381
21381
  }
21382
21382
 
21383
+ // src/native-local-publication.ts
21384
+ var publishers = new Map;
21385
+ function acquireNativeLocalPublisher(key) {
21386
+ let owned = publishers.get(key);
21387
+ if (!owned) {
21388
+ owned = { writer: createPublisher(), leases: 0 };
21389
+ publishers.set(key, owned);
21390
+ }
21391
+ owned.leases++;
21392
+ let released = false;
21393
+ return {
21394
+ ...owned.writer,
21395
+ release() {
21396
+ if (released)
21397
+ return;
21398
+ released = true;
21399
+ if (--owned.leases === 0)
21400
+ publishers.delete(key);
21401
+ }
21402
+ };
21403
+ }
21404
+ function createPublisher() {
21405
+ let tail = Promise.resolve();
21406
+ const pending = new Map;
21407
+ const run = (write) => {
21408
+ const result = tail.then(write);
21409
+ tail = result.catch(() => {});
21410
+ return result;
21411
+ };
21412
+ return {
21413
+ run,
21414
+ enqueue(key, patch, write) {
21415
+ const queued = pending.get(key);
21416
+ if (queued) {
21417
+ const previous = queued.patch;
21418
+ if (previous.quota && patch.quota && patch.quota.source !== "headers" && quotaSnapshotCheckedAt(previous.quota) > quotaSnapshotCheckedAt(patch.quota))
21419
+ return Promise.resolve(false);
21420
+ queued.patch = { ...previous, ...patch };
21421
+ if (previous.quota && patch.quota)
21422
+ queued.patch.quota = fromNativeQuotaMap(nativeQuotaCodec.merge(toNativeQuotaMap(previous.quota), toNativeQuotaMap(patch.quota)));
21423
+ if (previous.lastUsed !== undefined || patch.lastUsed !== undefined)
21424
+ queued.patch.lastUsed = Math.max(previous.lastUsed ?? 0, patch.lastUsed ?? 0);
21425
+ for (const field of [
21426
+ "quotaErrorClearedAt",
21427
+ "quotaErrorGeneration"
21428
+ ])
21429
+ if (previous[field] !== undefined || patch[field] !== undefined)
21430
+ queued.patch[field] = Math.max(previous[field] ?? 0, patch[field] ?? 0);
21431
+ if (previous.lastQuotaRefreshError && patch.lastQuotaRefreshError && previous.lastQuotaRefreshError.checkedAt > patch.lastQuotaRefreshError.checkedAt)
21432
+ queued.patch.lastQuotaRefreshError = previous.lastQuotaRefreshError;
21433
+ return queued.result;
21434
+ }
21435
+ const batch = { patch, result: Promise.resolve(false) };
21436
+ pending.set(key, batch);
21437
+ batch.result = run(async () => {
21438
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
21439
+ pending.delete(key);
21440
+ return write(batch.patch);
21441
+ });
21442
+ return batch.result;
21443
+ },
21444
+ async drain() {
21445
+ let observed;
21446
+ do {
21447
+ observed = tail;
21448
+ await observed;
21449
+ } while (observed !== tail);
21450
+ }
21451
+ };
21452
+ }
21453
+
21383
21454
  // src/native-usage-publication.ts
21384
21455
  import { types as types3 } from "node:util";
21385
21456
  function usagePublicationRefusalIsFatal(error) {
@@ -21526,7 +21597,7 @@ class NativeQuotaObservations extends Map {
21526
21597
  let clearAt;
21527
21598
  let matched = false;
21528
21599
  for (const observation of this.values()) {
21529
- if (observation.entry.quota.source === "headers")
21600
+ if (observation.entry.quota.source === "headers" && observation.routeBeforeSave !== true)
21530
21601
  continue;
21531
21602
  if (observation.lineage !== lineage || observation.accountUuid !== accountUuid2 || getQuotaNextRefreshAt(observation.entry.quota, storage, observation.entry.checkedAt) <= now)
21532
21603
  continue;
@@ -21556,6 +21627,17 @@ class NativeQuotaObservations extends Map {
21556
21627
  }
21557
21628
  }));
21558
21629
  }
21630
+ hold(accountId, observation) {
21631
+ const current = this.get(accountId);
21632
+ if (current && current.lineage === observation.lineage && quotaSnapshotCheckedAt(current.entry.quota) > quotaSnapshotCheckedAt(observation.entry.quota))
21633
+ return false;
21634
+ this.set(accountId, observation);
21635
+ return true;
21636
+ }
21637
+ forget(accountId, observation) {
21638
+ if (this.get(accountId) === observation)
21639
+ this.delete(accountId);
21640
+ }
21559
21641
  drain() {
21560
21642
  return this.publications.drain();
21561
21643
  }
@@ -26265,11 +26347,12 @@ function acquireNativeAccountRuntime(options) {
26265
26347
  const quotaObservations = use().quotaObservations;
26266
26348
  const onLockEvent = use().onLockEvent;
26267
26349
  const drainRoutingPublications = use().drainRoutingPublications;
26350
+ const drainQuotaPublications = use().drainQuotaPublications;
26268
26351
  return {
26269
26352
  onLockEvent,
26270
26353
  vault,
26271
26354
  quotaObservations,
26272
- drainQuotaPublications: () => quotaObservations.drain(),
26355
+ drainQuotaPublications,
26273
26356
  drainRoutingPublications,
26274
26357
  getRelayConfig: () => use().getRelayConfig(),
26275
26358
  getSettingsPath: () => use().getSettingsPath(),
@@ -26297,7 +26380,10 @@ function acquireNativeAccountRuntime(options) {
26297
26380
  fetchProfileForDisplay: (id2, transport, signal) => use().fetchProfileForDisplay(id2, transport, signal),
26298
26381
  close: () => {
26299
26382
  vault.close();
26300
- return drainRoutingPublications();
26383
+ return Promise.all([
26384
+ drainQuotaPublications(),
26385
+ drainRoutingPublications()
26386
+ ]).then(() => {});
26301
26387
  }
26302
26388
  };
26303
26389
  }
@@ -26337,6 +26423,9 @@ function createNativeAccountRuntime(options) {
26337
26423
  }
26338
26424
  });
26339
26425
  const shutdown = new AbortController;
26426
+ let closing;
26427
+ const quotaPublications = acquireNativeLocalPublisher(paths.config);
26428
+ const metadataPublications = acquireNativeLocalPublisher(paths.runtime);
26340
26429
  const store = createNativePoolStore({
26341
26430
  paths,
26342
26431
  quota: nativeQuotaCodec,
@@ -26459,6 +26548,51 @@ function createNativeAccountRuntime(options) {
26459
26548
  throw error;
26460
26549
  }
26461
26550
  }
26551
+ async function localMetadataMatches(subject) {
26552
+ const binding2 = subject.binding;
26553
+ const row = await poolRow(binding2.rowId);
26554
+ if (row?.stamp !== "bound" || !row.credential || !nativeLocalPoolBindingMatches(binding2, paths, row))
26555
+ return false;
26556
+ if ("kind" in subject)
26557
+ return row.credential.type === "api" && fingerprintOf(row.credential) === subject.credentialFingerprint && row.credential.baseURL === subject.baseURL && row.credential.authHeader === subject.authHeader;
26558
+ return row.credential.type === "oauth" && fingerprintOf(row.credential) === subject.credentialFingerprint && nativeLocalCredentialValidationMatches(subject, binding2, row.credential);
26559
+ }
26560
+ async function localMetadataWrite(subject, change) {
26561
+ const binding2 = subject.binding;
26562
+ if (await authority() !== "local")
26563
+ return false;
26564
+ const matches2 = await localMetadataMatches(subject);
26565
+ await localAuthority();
26566
+ if (!matches2)
26567
+ return false;
26568
+ try {
26569
+ await updateNativeRuntime(paths.runtime, paths.storageId, (current) => {
26570
+ const old = current.accounts[binding2.rowId];
26571
+ const previous = old?.binding;
26572
+ const same = previous?.kind === "local" && previous.rowId === binding2.rowId && previous.identity === binding2.identity && previous.credentialEpoch === binding2.credentialEpoch;
26573
+ current.accounts[binding2.rowId] = change(same && old ? old : { binding: binding2 });
26574
+ return current;
26575
+ }, {
26576
+ onLockEvent: options.local?.onLockEvent,
26577
+ beforeRename: async () => {
26578
+ if (!await localMetadataMatches(subject))
26579
+ throw new NativeRuntimeError("publication-refused");
26580
+ await localAuthority();
26581
+ },
26582
+ beforePublish: () => localAuthority()
26583
+ });
26584
+ if (await authority() !== "local")
26585
+ throw new NativeRuntimeError("publication-refused");
26586
+ return true;
26587
+ } catch (error) {
26588
+ if (error instanceof NativeRuntimeError && (error.code === "runtime-conflict" || error.code === "publication-refused")) {
26589
+ if (await authority() !== "local")
26590
+ throw error;
26591
+ return false;
26592
+ }
26593
+ throw error;
26594
+ }
26595
+ }
26462
26596
  async function initializeCredentialEpoch(id2) {
26463
26597
  const row = await poolRow(id2);
26464
26598
  if (!row?.credential || row.credentialEpoch === undefined)
@@ -26594,7 +26728,13 @@ function createNativeAccountRuntime(options) {
26594
26728
  onLockEvent,
26595
26729
  vault,
26596
26730
  quotaObservations: vault.quotaObservations,
26597
- drainQuotaPublications: () => vault.quotaObservations.drain(),
26731
+ drainQuotaPublications: async () => {
26732
+ await vault.quotaObservations.drain();
26733
+ await Promise.all([
26734
+ quotaPublications.drain(),
26735
+ metadataPublications.drain()
26736
+ ]);
26737
+ },
26598
26738
  drainRoutingPublications: () => vaultRouter.drain(),
26599
26739
  async getRelayConfig() {
26600
26740
  const { mode, paths: paths2, store: store2 } = await selectedStorage();
@@ -27052,30 +27192,45 @@ function createNativeAccountRuntime(options) {
27052
27192
  };
27053
27193
  },
27054
27194
  async publishLocal(subject, rawPatch) {
27055
- const patch = captureNativeMetadataPatch(rawPatch);
27056
- if (await authority() !== "local")
27057
- return false;
27058
- const { quota: quota2, ...metadata2 } = patch;
27059
- const quotaOnly = quota2 !== undefined && Object.keys(metadata2).length === 0;
27060
- const row = await poolRow(subject.binding.rowId);
27061
- if (row?.stamp !== "bound" || row.credential?.type !== "oauth" || !nativeLocalPoolBindingMatches(subject.binding, paths, row) || !quotaOnly && !nativeLocalCredentialValidationMatches(subject, subject.binding, row.credential))
27062
- return false;
27063
- if (quota2) {
27064
- if (quota2.source !== "headers" && row.quota && quotaSnapshotCheckedAt(fromNativeQuotaMap(row.quota)) > quotaSnapshotCheckedAt(quota2))
27195
+ const captured = captureNativeMetadataPatch(rawPatch);
27196
+ const { quota: _quota, ...metadata2 } = captured;
27197
+ const mixed = captured.quota !== undefined && Object.keys(metadata2).length > 0;
27198
+ let admission;
27199
+ const admitted = () => admission ??= (async () => {
27200
+ if (await authority() !== "local")
27065
27201
  return false;
27066
- if (quota2.accountIdentity !== subject.binding.identity)
27067
- throw new NativeRuntimeError("publication-refused");
27068
- await store.recordQuota(subject.binding.rowId, {
27069
- credentialEpoch: subject.binding.credentialEpoch,
27070
- identity: subject.binding.identity
27071
- }, toNativeQuotaMap(quota2));
27072
- }
27073
- return localWrite(subject, (old) => applyNativeMetadataPatch(old, metadata2), quotaOnly ? "binding" : true);
27202
+ const matches2 = await localMetadataMatches(subject);
27203
+ await localAuthority();
27204
+ return matches2;
27205
+ })();
27206
+ const key = JSON.stringify(subject);
27207
+ const writes = [];
27208
+ if (captured.quota)
27209
+ writes.push(quotaPublications.enqueue(mixed ? `${key}:mixed` : key, { quota: captured.quota }, async ({ quota: quota2 }) => {
27210
+ if (mixed && !await admitted())
27211
+ return false;
27212
+ if (!quota2 || await authority() !== "local")
27213
+ return false;
27214
+ const row = await poolRow(subject.binding.rowId);
27215
+ if (row?.stamp !== "bound" || row.credential?.type !== "oauth" || !nativeLocalPoolBindingMatches(subject.binding, paths, row) || quota2.accountIdentity !== subject.binding.identity)
27216
+ return false;
27217
+ if (quota2.source !== "headers" && row.quota && quotaSnapshotCheckedAt(fromNativeQuotaMap(row.quota)) > quotaSnapshotCheckedAt(quota2))
27218
+ return false;
27219
+ await store.recordQuota(subject.binding.rowId, {
27220
+ credentialEpoch: subject.binding.credentialEpoch,
27221
+ identity: subject.binding.identity
27222
+ }, toNativeQuotaMap(quota2));
27223
+ return true;
27224
+ }));
27225
+ if (Object.keys(metadata2).length)
27226
+ writes.push(metadataPublications.enqueue(mixed ? `${key}:mixed` : key, metadata2, async (patch) => {
27227
+ if (mixed && !await admitted())
27228
+ return false;
27229
+ return localMetadataWrite(subject, (old) => applyNativeMetadataPatch(old, patch));
27230
+ }));
27231
+ return (await Promise.all(writes)).every(Boolean);
27074
27232
  },
27075
- publishApi: (subject, patch) => localWrite(subject, (old) => ({
27076
- ...old,
27077
- lastUsed: Math.max(old.lastUsed ?? 0, patch.lastUsed)
27078
- })),
27233
+ publishApi: (subject, patch) => metadataPublications.enqueue(JSON.stringify(subject), patch, (merged) => localMetadataWrite(subject, (old) => applyNativeMetadataPatch(old, merged))),
27079
27234
  async resetBackoff(routeId, fence, kind = "all") {
27080
27235
  if ("accessToken" in fence)
27081
27236
  return vault.resetBackoff(fence, kind);
@@ -27107,13 +27262,13 @@ function createNativeAccountRuntime(options) {
27107
27262
  if (await physicalId(routeId) !== fence.binding.rowId)
27108
27263
  return;
27109
27264
  let lineage;
27110
- const kept = await localWrite(fence, (old) => {
27265
+ const kept = await metadataPublications.run(() => localMetadataWrite(fence, (old) => {
27111
27266
  lineage = old.authLineageId ?? randomUUID15();
27112
27267
  return {
27113
27268
  ...old,
27114
27269
  authLineageId: lineage
27115
27270
  };
27116
- });
27271
+ }));
27117
27272
  return kept ? lineage : undefined;
27118
27273
  },
27119
27274
  async incrementPrimeUsage(routeId, fence, delta = {}) {
@@ -27124,7 +27279,7 @@ function createNativeAccountRuntime(options) {
27124
27279
  if (await physicalId(routeId) !== fence.binding.rowId)
27125
27280
  return false;
27126
27281
  const captured = primeDelta(delta);
27127
- return localWrite(fence, (old) => ({
27282
+ return metadataPublications.run(() => localMetadataWrite(fence, (old) => ({
27128
27283
  ...old,
27129
27284
  prime: {
27130
27285
  count: (old.prime?.count ?? 0) + 1,
@@ -27132,7 +27287,7 @@ function createNativeAccountRuntime(options) {
27132
27287
  outputTokens: (old.prime?.outputTokens ?? 0) + captured.outputTokens,
27133
27288
  since: old.prime?.since ?? now()
27134
27289
  }
27135
- }));
27290
+ })));
27136
27291
  },
27137
27292
  async fetchQuota(routeId, fetchImpl = fetch, signal, dispatch, purpose) {
27138
27293
  const snapshot = await runtime2.read();
@@ -27196,11 +27351,16 @@ function createNativeAccountRuntime(options) {
27196
27351
  });
27197
27352
  },
27198
27353
  close() {
27199
- if (!shutdown.signal.aborted) {
27354
+ if (closing)
27355
+ return closing;
27356
+ const draining = runtime2.drainQuotaPublications();
27357
+ vault.close();
27358
+ closing = Promise.all([draining, vaultRouter.drain()]).then(() => {}).finally(() => {
27200
27359
  shutdown.abort(new NativeCustodyError("closed"));
27201
- vault.close();
27202
- }
27203
- return vaultRouter.drain();
27360
+ quotaPublications.release();
27361
+ metadataPublications.release();
27362
+ });
27363
+ return closing;
27204
27364
  }
27205
27365
  };
27206
27366
  function readLocalProfile(id2, fetchImpl, signal) {
@@ -0,0 +1,8 @@
1
+ import type { NativeAccountMetadataPatch } from './native-account-view.ts';
2
+ /** Share one replaceable bookkeeping mailbox per destination in this process, never for credential writes. */
3
+ export declare function acquireNativeLocalPublisher(key: string): {
4
+ release(): void;
5
+ run: <T>(write: () => Promise<T>) => Promise<T>;
6
+ enqueue(key: string, patch: NativeAccountMetadataPatch, write: (patch: NativeAccountMetadataPatch) => Promise<boolean>): Promise<boolean>;
7
+ drain(): Promise<void>;
8
+ };
@@ -7,6 +7,14 @@ export type NativeQuotaObservation = {
7
7
  entry: QuotaEntry;
8
8
  generalExhausted: boolean;
9
9
  quotaErrorClearedAt?: number;
10
+ /**
11
+ * Set on a response-header reading from a host that routes on native
12
+ * snapshots (OpenCode 2). The snapshot overlay then applies it while its
13
+ * detached save is still pending, as it does for poll readings. Header
14
+ * readings without it stay host-owned: OpenCode 1 applies its own routing
15
+ * and display rules to them.
16
+ */
17
+ routeBeforeSave?: boolean;
10
18
  };
11
19
  export declare function nativePollQuotaGenerallyExhausted(quota: OAuthQuotaSnapshot): boolean;
12
20
  export declare function nativeLocalQuotaLineage(binding: NativeLocalPoolBinding & {
@@ -22,5 +30,14 @@ export declare class NativeQuotaObservations extends Map<string, NativeQuotaObse
22
30
  clearAt?: number;
23
31
  } | undefined;
24
32
  publish(accountId: string, observation: NativeQuotaObservation, write: () => Promise<boolean>): Promise<void>;
33
+ /**
34
+ * Registers a response-header reading before its detached save starts, so
35
+ * routing in this process sees it while the save waits on file locks. A
36
+ * reading older than the one already held for the same credential lineage
37
+ * is not registered, so an older reading never replaces a newer one.
38
+ */
39
+ hold(accountId: string, observation: NativeQuotaObservation): boolean;
40
+ /** Forgets a held reading whose save was refused, unless a later reading already replaced it. */
41
+ forget(accountId: string, observation: NativeQuotaObservation): void;
25
42
  drain(): Promise<void>;
26
43
  }
@@ -104,10 +104,10 @@ export interface NativeRuntimeWriteHooks {
104
104
  /** This writer alone uses `<leaf>.native-runtime.<UUID v4>.partial`. */
105
105
  export declare function isNativeRuntimeStagingName(path: string, name: string): boolean;
106
106
  /**
107
- * This file has its own update lock. Account publishers also hold the locks
108
- * protecting the pool and, for vault accounts, the roster. They verify those
109
- * locks immediately before replacement. A process paused beyond a lock expiry
110
- * can overlap a new owner even with normal renewal.
107
+ * This file has its own update lock. Advisory account publishers recheck the
108
+ * pool or roster binding without taking its lock immediately before replacement;
109
+ * readers reject metadata whose binding no longer matches. Credential writers
110
+ * retain their separate ownership and custody checks.
111
111
  */
112
112
  export declare function updateNativeRuntime(path: string, storageId: string, change: (current: NativeRuntimeState) => NativeRuntimeState | Promise<NativeRuntimeState>, hooks?: NativeRuntimeWriteHooks): Promise<NativeRuntimeState>;
113
113
  /**
@@ -15,8 +15,9 @@ export interface NativePoolStoreOptions extends Pick<OpenPoolStoreOptions, 'now'
15
15
  onLockEvent?: (event: NativeLockEvent) => void;
16
16
  }
17
17
  /**
18
- * These config/state locks also fence local account metadata against concurrent
19
- * credential changes. Legacy sidecar writers use different files and locks.
18
+ * These locks serialize pool transactions, including credential changes and
19
+ * quota publication. Runtime bookkeeping has its own lock and binding checks.
20
+ * Legacy sidecar writers use different files and locks.
20
21
  */
21
22
  export declare function nativePoolStoreLocks(paths: NativePoolStorePaths): readonly [{
22
23
  readonly path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",