@cortexkit/anthropic-auth-core 2.2.4 → 2.2.5

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
@@ -2730,7 +2730,7 @@ async function assertHostsStopped(fence, env) {
2730
2730
 
2731
2731
  // ../../node_modules/@cortexkit/common-auth/dist/setup/enroll.js
2732
2732
  var ENROLLMENT_INCOMPLETE_MESSAGE = "Claustrum enrollment did not complete. Inspect and approve with ck, then rerun setup.";
2733
- var UNSUPPORTED_VAULT_MESSAGE = "This Claustrum vault does not support connecting plugins yet, so setup cannot connect to it. Run setup without --claustrum to use local accounts.";
2733
+ var UNSUPPORTED_VAULT_MESSAGE = 'This Claustrum vault is too old to connect plugins. Run ck upgrade to move to Claustrum v0.2.0 or newer (read "Upgrading from v0.1.3" in its release notes first), or run setup without --claustrum to use local accounts.';
2734
2734
  var ENROLLMENT_WAIT_MS = 300000;
2735
2735
  var APPROVAL_POLL_MS = 1000;
2736
2736
  var PROPOSE_BACKOFF_MS = [1000, 2000, 4000, 8000, 16000, 30000];
@@ -18408,6 +18408,52 @@ function acceptVaultRoute(path2, routeId) {
18408
18408
  };
18409
18409
  });
18410
18410
  }
18411
+ function invalidOrder(message) {
18412
+ return new ClaustrumConsumerError("invalid-order", message);
18413
+ }
18414
+ function vaultOrderProblem(rows, routeIds) {
18415
+ if (!Array.isArray(routeIds))
18416
+ return "route ids must be an array of route ids";
18417
+ const rosterIds = new Set(rows.map((row) => row.routeId));
18418
+ const given = new Set;
18419
+ for (const id2 of routeIds) {
18420
+ if (typeof id2 !== "string")
18421
+ return "route ids must be an array of route ids";
18422
+ if (given.has(id2))
18423
+ return `route id ${id2} appears more than once`;
18424
+ if (!rosterIds.has(id2))
18425
+ return `route id ${id2} is not in the roster`;
18426
+ given.add(id2);
18427
+ }
18428
+ const missing2 = [...rosterIds].filter((id2) => !given.has(id2)).length;
18429
+ if (missing2 > 0)
18430
+ return `the order leaves out ${missing2} roster route id${missing2 === 1 ? "" : "s"}`;
18431
+ return;
18432
+ }
18433
+ function reorderVaultRoster(path2, routeIds, options = {}) {
18434
+ return mutateVaultRoster(path2, (current) => {
18435
+ const rows = current?.rows ?? [];
18436
+ const problem = vaultOrderProblem(rows, routeIds);
18437
+ if (problem)
18438
+ throw invalidOrder(problem);
18439
+ const byId = new Map;
18440
+ for (const row of rows) {
18441
+ const group = byId.get(row.routeId);
18442
+ if (group)
18443
+ group.push(row);
18444
+ else
18445
+ byId.set(row.routeId, [row]);
18446
+ }
18447
+ const ids = [...routeIds];
18448
+ const next = ids.flatMap((id2) => byId.get(id2) ?? []);
18449
+ if (!current || next.every((row, index) => row === rows[index]))
18450
+ return { result: { ids, outcome: "unchanged" } };
18451
+ return {
18452
+ next: { ...current, rows: next },
18453
+ result: { ids, outcome: "reordered" }
18454
+ };
18455
+ }, { ...options.signal && { signal: options.signal } });
18456
+ }
18411
18457
  function receiptFence(row, receipt) {
18412
18458
  if (!memberIds(row).includes(receipt.credentialId))
18413
18459
  return "refuse";
@@ -19087,7 +19133,11 @@ class ClaustrumConsumer {
19087
19133
  let current;
19088
19134
  try {
19089
19135
  current = await this.authorize(routeId, signal);
19090
- } catch {}
19136
+ signal?.throwIfAborted();
19137
+ } catch (error) {
19138
+ if (signal?.aborted)
19139
+ throw error;
19140
+ }
19091
19141
  if (decideScopedRetryAfter401(options.site, served, current, this.#logger)) {
19092
19142
  await response.body?.cancel().catch(() => {});
19093
19143
  served = current;
@@ -19114,6 +19164,12 @@ class ClaustrumConsumer {
19114
19164
  await acceptVaultRoute(this.#options.rosterPath, routeId);
19115
19165
  this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
19116
19166
  }
19167
+ async reorder(routeIds) {
19168
+ this.#assertOpen();
19169
+ const result = await reorderVaultRoster(this.#options.rosterPath, routeIds);
19170
+ this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
19171
+ return result;
19172
+ }
19117
19173
  async recordQuota(routeId, observation, attempt) {
19118
19174
  this.#assertOpen();
19119
19175
  return recordVaultQuota(this.#options.rosterPath, {
@@ -19187,6 +19243,7 @@ function nativeCode(value) {
19187
19243
  case "host-slot-login":
19188
19244
  case "host-slot-placeholder":
19189
19245
  case "placeholder-refresh":
19246
+ case "invalid-order":
19190
19247
  return value;
19191
19248
  default:
19192
19249
  return "unavailable";
@@ -21364,6 +21421,7 @@ var codes = new Set([
21364
21421
  "host-slot-login",
21365
21422
  "host-slot-placeholder",
21366
21423
  "placeholder-refresh",
21424
+ "invalid-order",
21367
21425
  "publication-refused",
21368
21426
  "unsafe-runtime",
21369
21427
  "invalid-runtime",
@@ -24505,7 +24563,7 @@ class StickySessionRouter {
24505
24563
  }) > 0) : undefined;
24506
24564
  const selected = preferred ?? this.selectCandidate({ ...input, candidates, state });
24507
24565
  if (!selected) {
24508
- if (current) {
24566
+ if (current && !input.preserveOnNoCandidate) {
24509
24567
  delete state.assignments[key];
24510
24568
  save("delete");
24511
24569
  }
@@ -4,7 +4,7 @@ import type { RoutingRow } from '../routing/index.js';
4
4
  import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser, type KeptReceiptState, type VaultGrantReach } from './custody.js';
5
5
  import { type ClaustrumLogger } from './errors.js';
6
6
  import { type InsulaQuotaOptions, type InsulaQuotaResult } from './insula.js';
7
- import { type AccountMapper, type QuotaReceipt, type VaultRosterFile } from './roster.js';
7
+ import { type AccountMapper, type QuotaReceipt, type ReorderVaultRosterResult, type VaultRosterFile } from './roster.js';
8
8
  import { type WatchMode } from './watch.js';
9
9
  export interface ClaustrumConsumerOptions<Q = QuotaObservation> {
10
10
  /**
@@ -157,6 +157,12 @@ export declare class ClaustrumConsumer<Q = QuotaObservation> {
157
157
  /** Decline a vault account: it stays listed and never routes until accepted. */
158
158
  decline(routeId: string): Promise<void>;
159
159
  accept(routeId: string): Promise<void>;
160
+ /**
161
+ * Set the order vault accounts are tried in (`reorderVaultRoster`), then
162
+ * re-read the roster, so the next routing decision in this process uses
163
+ * the new order without waiting for a discovery. Never contacts the vault.
164
+ */
165
+ reorder(routeIds: readonly string[]): Promise<ReorderVaultRosterResult>;
160
166
  /**
161
167
  * Store a quota or profile observation for a vault route. The receipt the
162
168
  * reading was taken with is required: the observation lands only while the
@@ -4,7 +4,7 @@
4
4
  * `code`, a `class` and an `action`), so callers can tell the vault's verdict
5
5
  * apart from a check made on this side.
6
6
  */
7
- export type ClaustrumConsumerFailureKind = 'closed' | 'not-enrolled' | 'invalid-token' | 'unavailable' | 'identity-changed' | 'identity-unasserted' | 'insufficient-validity' | 'invalid-material' | 'not-active' | 'route-unavailable' | 'route-declined' | 'no-receipt' | 'unsafe-file' | 'invalid-state' | 'wrong-consumer' | 'roster-busy' | 'host-slot-login' | 'host-slot-placeholder' | 'placeholder-refresh';
7
+ export type ClaustrumConsumerFailureKind = 'closed' | 'not-enrolled' | 'invalid-token' | 'unavailable' | 'identity-changed' | 'identity-unasserted' | 'insufficient-validity' | 'invalid-material' | 'not-active' | 'route-unavailable' | 'route-declined' | 'no-receipt' | 'unsafe-file' | 'invalid-state' | 'wrong-consumer' | 'roster-busy' | 'host-slot-login' | 'host-slot-placeholder' | 'placeholder-refresh' | 'invalid-order';
8
8
  export declare class ClaustrumConsumerError extends Error {
9
9
  readonly kind: ClaustrumConsumerFailureKind;
10
10
  constructor(kind: ClaustrumConsumerFailureKind, message: string);
@@ -10,7 +10,7 @@ export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHO
10
10
  export { acquireInsulaQuotaSource, type InsulaQuotaContext, type InsulaQuotaLookup, type InsulaQuotaMiss, type InsulaQuotaOptions, type InsulaQuotaReading, type InsulaQuotaResult, type InsulaQuotaSource, type InsulaUsageEntry, type InsulaUsageTransport, type InsulaUsageWatch, type InsulaUsageWindow, type InsulaWatchOptions, } from './insula.js';
11
11
  export { acceptAccount, type DeclinedAccount, declineAccount, isDeclined, } from './interlock.js';
12
12
  export { drainVaultQuota } from './quota-writer.js';
13
- export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, vaultRoutingRows, } from './roster.js';
13
+ export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, type ReorderVaultRosterOptions, type ReorderVaultRosterResult, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, vaultRoutingRows, } from './roster.js';
14
14
  export { type ChangeNotice, ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, type ScopedRosterWatchOptions, type ScopedWatchEnd, type ScopedWatchHandle, type ScopedWatchNoticeShape, type ScopedWatchSource, WATCH_RETRY_MS, type WatchMode, } from './watch.js';
15
15
  /**
16
16
  * Connect the client that lists and fetches this consumer's vault credentials
@@ -119,6 +119,37 @@ export declare function mutateVaultRoster<T>(path: string, change: (current: Vau
119
119
  export declare function declineVaultRoute(path: string, routeId: string): Promise<undefined>;
120
120
  /** Lift the user's decline for a vault account and every alias of it. */
121
121
  export declare function acceptVaultRoute(path: string, routeId: string): Promise<undefined>;
122
+ /** Options of `reorderVaultRoster`. */
123
+ export interface ReorderVaultRosterOptions {
124
+ /** Aborts the wait for the roster's write lock. */
125
+ signal?: AbortSignal;
126
+ }
127
+ export interface ReorderVaultRosterResult {
128
+ /** The route id order now on disk. */
129
+ ids: string[];
130
+ /** `unchanged` when the roster was already in this order; nothing was written. */
131
+ outcome: 'reordered' | 'unchanged';
132
+ }
133
+ /**
134
+ * Set the order vault accounts are tried in. Routing reads that order from
135
+ * the roster's row order (`vaultRoutingRows`), and a later discovery keeps
136
+ * each account at its position, so the order lasts until the next reorder.
137
+ * The order is this host's routing preference: the vault has no order of its
138
+ * own, and this never contacts it.
139
+ *
140
+ * `routeIds` must name every roster route id exactly once; anything else
141
+ * (an unknown, missing or repeated id) refuses with `invalid-order` and
142
+ * writes nothing. It runs under the roster's write lock, like every other
143
+ * roster write, so a quota reading or decline committed meanwhile is kept.
144
+ * Each row is kept as the same object, so only the positions change: every
145
+ * row's fields and the rest of the file stay as they were. An order equal to
146
+ * the current one writes nothing. A host with no roster file yet has no
147
+ * routes, so only the empty order is accepted.
148
+ *
149
+ * A `ClaustrumConsumer` in the same process does not see the new order until
150
+ * it re-reads the file: call `consumer.reorder` instead to update both.
151
+ */
152
+ export declare function reorderVaultRoster(path: string, routeIds: readonly string[], options?: ReorderVaultRosterOptions): Promise<ReorderVaultRosterResult>;
122
153
  /**
123
154
  * The receipt fields a quota or profile observation must carry to say which
124
155
  * send (credential and account) it came from. Pass the
@@ -48,6 +48,43 @@ export interface RoutingSectionOptions {
48
48
  */
49
49
  orderedVariants?: MenuChoice[];
50
50
  formerMainId?: string;
51
+ /**
52
+ * The vault accounts' routing order, for a plugin that can run in vault
53
+ * mode. Called on each build; return the order while the plugin routes
54
+ * through vault accounts and undefined otherwise. While it returns one, the
55
+ * section shows and sets the vault accounts' order (by the name the menu
56
+ * shows for each account) instead of the pool roster's. Wire it to
57
+ * `ClaustrumConsumer.snapshot()` and `ClaustrumConsumer.reorder`, or to
58
+ * `readVaultRoster` and `reorderVaultRoster` for a plugin without a
59
+ * consumer.
60
+ */
61
+ vaultOrder?: () => VaultRoutingOrder | undefined | Promise<VaultRoutingOrder | undefined>;
62
+ }
63
+ /**
64
+ * The order vault accounts are tried in, as the Routing section reads and
65
+ * sets it in vault mode. Shaped after `/claustrum`'s roster, without
66
+ * importing it, so the menu does not need the vault client.
67
+ */
68
+ export interface VaultRoutingOrder {
69
+ /**
70
+ * Every vault roster row, in roster (routing) order: its route id and the
71
+ * name the plugin shows for that account (the roster row's `label`, or its
72
+ * email). Route ids are long and opaque, so the section shows and accepts
73
+ * labels; a route id is still accepted wherever a label is.
74
+ */
75
+ rows: readonly {
76
+ id: string;
77
+ label: string;
78
+ }[];
79
+ /**
80
+ * Sets the order. `ids` are route ids, every one exactly once. Wire it to
81
+ * `ClaustrumConsumer.reorder` or `reorderVaultRoster`, which refuse any
82
+ * other list without writing.
83
+ */
84
+ reorder(ids: readonly string[]): Promise<{
85
+ ids: readonly string[];
86
+ outcome: 'reordered' | 'unchanged';
87
+ }>;
51
88
  }
52
89
  export interface LimitsSectionOptions {
53
90
  /**
@@ -1,4 +1,4 @@
1
- export type { AccountsSectionOptions, LimitsSectionOptions, LoginOutcome, QuotaSectionOptions, RoutingSectionOptions, } from './builtins.js';
1
+ export type { AccountsSectionOptions, LimitsSectionOptions, LoginOutcome, QuotaSectionOptions, RoutingSectionOptions, VaultRoutingOrder, } from './builtins.js';
2
2
  export { MENU_DISABLED_REASON } from './builtins.js';
3
3
  export type { CommandMenu, CommandMenuOptions, StoreSectionSlot, } from './menu.js';
4
4
  export { createCommandMenu, parseApplyRequest } from './menu.js';
@@ -1,7 +1,7 @@
1
1
  import { type MenuTerminal } from '../auth-menu/terminal.js';
2
2
  import type { SetupHost, SetupIO, SetupPlugin } from './types.js';
3
3
  /** Fixed text: no version, host or account detail, so it is safe to print anywhere. */
4
- export declare const UNSUPPORTED_VAULT_MESSAGE = "This Claustrum vault does not support connecting plugins yet, so setup cannot connect to it. Run setup without --claustrum to use local accounts.";
4
+ export declare const UNSUPPORTED_VAULT_MESSAGE = "This Claustrum vault is too old to connect plugins. Run ck upgrade to move to Claustrum v0.2.0 or newer (read \"Upgrading from v0.1.3\" in its release notes first), or run setup without --claustrum to use local accounts.";
5
5
  /**
6
6
  * The longest setup waits, in total, for the vault to create the enrollment
7
7
  * request and for an operator to approve it. It matches the wait setup always
@@ -203,7 +203,7 @@ export interface NativeCustodyInventory {
203
203
  }[];
204
204
  };
205
205
  }
206
- export type NativeCustodyErrorCode = 'closed' | 'not-enrolled' | 'invalid-token' | 'unavailable' | 'identity-changed' | 'identity-unasserted' | 'insufficient-validity' | 'invalid-material' | 'not-active' | 'route-unavailable' | 'route-declined' | 'no-receipt' | 'unsafe-file' | 'invalid-state' | 'wrong-consumer' | 'roster-busy' | 'host-slot-login' | 'host-slot-placeholder' | 'placeholder-refresh';
206
+ export type NativeCustodyErrorCode = 'closed' | 'not-enrolled' | 'invalid-token' | 'unavailable' | 'identity-changed' | 'identity-unasserted' | 'insufficient-validity' | 'invalid-material' | 'not-active' | 'route-unavailable' | 'route-declined' | 'no-receipt' | 'unsafe-file' | 'invalid-state' | 'wrong-consumer' | 'roster-busy' | 'host-slot-login' | 'host-slot-placeholder' | 'placeholder-refresh' | 'invalid-order';
207
207
  export declare class NativeCustodyError extends Error {
208
208
  readonly code: NativeCustodyErrorCode;
209
209
  constructor(code: NativeCustodyErrorCode);
@@ -148,6 +148,8 @@ export declare class StickySessionRouter {
148
148
  inputBytes: number;
149
149
  preferredAccountId?: string;
150
150
  excludeAccountIds?: ReadonlySet<string>;
151
+ /** Leave the current pin intact when selection cannot offer an account. */
152
+ preserveOnNoCandidate?: boolean;
151
153
  }): Promise<StickyRouteResolution | null>;
152
154
  clear(sessionId: string): Promise<void>;
153
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "prepublishOnly": "bun run build"
31
31
  },
32
32
  "devDependencies": {
33
- "@cortexkit/common-auth": "0.24.0"
33
+ "@cortexkit/common-auth": "0.25.0"
34
34
  },
35
35
  "dependencies": {
36
36
  "@cortexkit/claustrum-client": "0.12.0",