@cortexkit/anthropic-auth-core 1.23.0 → 2.0.1

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.
Files changed (136) hide show
  1. package/README.md +18 -1
  2. package/dist/accounts.d.ts +4 -163
  3. package/dist/cachekeep.d.ts +9 -0
  4. package/dist/claude-code.d.ts +16 -0
  5. package/dist/claustrum-enrollment.d.ts +2 -0
  6. package/dist/claustrum.d.ts +0 -209
  7. package/dist/commands/account.d.ts +2 -3
  8. package/dist/index.d.ts +45 -4
  9. package/dist/index.js +27569 -38
  10. package/dist/internal-types/LICENSE +21 -0
  11. package/dist/internal-types/commands/builtins.d.ts +71 -0
  12. package/dist/internal-types/commands/index.d.ts +10 -0
  13. package/dist/internal-types/commands/menu.d.ts +61 -0
  14. package/dist/internal-types/commands/model.d.ts +238 -0
  15. package/dist/internal-types/commands/pi.d.ts +23 -0
  16. package/dist/internal-types/commands/seam.d.ts +72 -0
  17. package/dist/internal-types/fs/refresh-file-lock.d.ts +33 -0
  18. package/dist/internal-types/logger/capture-sink.d.ts +14 -0
  19. package/dist/internal-types/logger/engine.d.ts +98 -0
  20. package/dist/internal-types/logger/index.d.ts +6 -0
  21. package/dist/internal-types/logger/redact.d.ts +11 -0
  22. package/dist/internal-types/pi-slot/index.d.ts +3 -0
  23. package/dist/internal-types/pi-slot/slot.d.ts +51 -0
  24. package/dist/internal-types/quota/codec.d.ts +11 -0
  25. package/dist/internal-types/quota/format.d.ts +46 -0
  26. package/dist/internal-types/quota/index.d.ts +10 -0
  27. package/dist/internal-types/quota/map.d.ts +63 -0
  28. package/dist/internal-types/quota/merge.d.ts +48 -0
  29. package/dist/internal-types/quota/projection.d.ts +64 -0
  30. package/dist/internal-types/store/attribution.d.ts +28 -0
  31. package/dist/internal-types/store/errors.d.ts +55 -0
  32. package/dist/internal-types/store/hooks.d.ts +12 -0
  33. package/dist/internal-types/store/identity.d.ts +39 -0
  34. package/dist/internal-types/store/index.d.ts +21 -0
  35. package/dist/internal-types/store/mutate.d.ts +189 -0
  36. package/dist/internal-types/store/pool.d.ts +197 -0
  37. package/dist/internal-types/store/provider-state.d.ts +131 -0
  38. package/dist/internal-types/store/publication.d.ts +47 -0
  39. package/dist/internal-types/store/pull.d.ts +36 -0
  40. package/dist/internal-types/store/refresh-lock.d.ts +67 -0
  41. package/dist/internal-types/store/refresh.d.ts +72 -0
  42. package/dist/internal-types/store/rows.d.ts +313 -0
  43. package/dist/internal-types/store/runtime.d.ts +36 -0
  44. package/dist/internal-types/store/schema.d.ts +443 -0
  45. package/dist/internal-types/store/settings.d.ts +63 -0
  46. package/dist/internal-types/store/torn.d.ts +78 -0
  47. package/dist/migration-source.d.ts +33 -0
  48. package/dist/models.d.ts +41 -0
  49. package/dist/native-account-runtime.d.ts +121 -0
  50. package/dist/native-account-view.d.ts +126 -0
  51. package/dist/native-command-effects.d.ts +59 -0
  52. package/dist/native-credential-validation.d.ts +42 -0
  53. package/dist/native-custody-activation.d.ts +45 -0
  54. package/dist/native-custody.d.ts +114 -0
  55. package/dist/native-enrollment-authority.d.ts +2 -0
  56. package/dist/native-host-auth-read.d.ts +3 -0
  57. package/dist/native-host-auth-write.d.ts +51 -0
  58. package/dist/native-host-auth.d.ts +12 -0
  59. package/dist/native-local-credential-service.d.ts +76 -0
  60. package/dist/native-local-runtime-readers.d.ts +22 -0
  61. package/dist/native-lock-observer.d.ts +12 -0
  62. package/dist/native-menu-executor.d.ts +243 -0
  63. package/dist/native-menu-model.d.ts +142 -0
  64. package/dist/native-migration-proof.d.ts +28 -0
  65. package/dist/native-migration-source.d.ts +47 -0
  66. package/dist/native-migration.d.ts +66 -0
  67. package/dist/native-quota-codec.d.ts +32 -0
  68. package/dist/native-refresh-coordinator.d.ts +240 -0
  69. package/dist/native-roster-seed.d.ts +115 -0
  70. package/dist/native-runtime-registry.d.ts +19 -0
  71. package/dist/native-runtime.d.ts +204 -0
  72. package/dist/native-settings.d.ts +6 -0
  73. package/dist/native-transient-account.d.ts +38 -0
  74. package/dist/native-ui.d.ts +45 -0
  75. package/dist/native-usage-publication.d.ts +12 -0
  76. package/dist/native-vault-admission.d.ts +8 -0
  77. package/dist/native-vault-connection.d.ts +33 -0
  78. package/dist/native-vault-paths.d.ts +13 -0
  79. package/dist/native-vault-runtime.d.ts +102 -0
  80. package/dist/native-vault-seed.d.ts +16 -0
  81. package/dist/pool-authority.d.ts +159 -0
  82. package/dist/pool-binding.d.ts +32 -0
  83. package/dist/pool-paths.d.ts +21 -0
  84. package/dist/pool-store.d.ts +32 -0
  85. package/dist/provider-error.d.ts +139 -0
  86. package/dist/quota-manager.d.ts +18 -7
  87. package/dist/relay.d.ts +14 -1
  88. package/dist/request-history.d.ts +34 -0
  89. package/dist/routing.d.ts +1 -2
  90. package/dist/sse.d.ts +81 -0
  91. package/dist/sticky-routing.d.ts +28 -0
  92. package/package.json +10 -4
  93. package/dist/accounts.js +0 -3881
  94. package/dist/auth.js +0 -185
  95. package/dist/cache1h.js +0 -97
  96. package/dist/cachekeep-registry.js +0 -124
  97. package/dist/cachekeep.js +0 -604
  98. package/dist/cch.js +0 -172
  99. package/dist/claude-code.js +0 -403
  100. package/dist/claustrum-enrollment-registry.d.ts +0 -17
  101. package/dist/claustrum-enrollment-registry.js +0 -194
  102. package/dist/claustrum-enrollment.js +0 -485
  103. package/dist/claustrum-scoped-roster.d.ts +0 -23
  104. package/dist/claustrum-scoped-roster.js +0 -194
  105. package/dist/claustrum-scoped-runtime.d.ts +0 -28
  106. package/dist/claustrum-scoped-runtime.js +0 -230
  107. package/dist/claustrum-scoped.d.ts +0 -39
  108. package/dist/claustrum-scoped.js +0 -193
  109. package/dist/claustrum.js +0 -1471
  110. package/dist/commands/account.js +0 -368
  111. package/dist/constants.js +0 -83
  112. package/dist/custom-headers.js +0 -102
  113. package/dist/dump.js +0 -616
  114. package/dist/fast.js +0 -66
  115. package/dist/json.js +0 -10
  116. package/dist/killswitch.js +0 -132
  117. package/dist/logger.js +0 -288
  118. package/dist/logging.js +0 -59
  119. package/dist/mid-conversation-output-config.js +0 -89
  120. package/dist/model-remap.js +0 -72
  121. package/dist/models.js +0 -177
  122. package/dist/network-errors.js +0 -27
  123. package/dist/oauth-profile.js +0 -60
  124. package/dist/pkce.js +0 -17
  125. package/dist/prime.js +0 -841
  126. package/dist/provider.js +0 -0
  127. package/dist/quota-header-feed.js +0 -331
  128. package/dist/quota-headers.js +0 -121
  129. package/dist/quota-manager.js +0 -856
  130. package/dist/quotas.js +0 -141
  131. package/dist/relay.js +0 -1303
  132. package/dist/routing.js +0 -94
  133. package/dist/start.js +0 -29
  134. package/dist/sticky-routing.js +0 -541
  135. package/dist/thinking-binding.js +0 -54
  136. package/dist/token-fingerprint.js +0 -8
@@ -1,194 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { acquireRefreshFileLock, getClaustrumMode, isOAuthAccount, loadAccounts, mutateAccountsPersistent, } from './accounts.js';
3
- function preferredLabel(credentialId) {
4
- const parts = credentialId.split(':');
5
- return parts.length >= 3 && parts[2]?.trim()
6
- ? parts.slice(2).join(':').trim()
7
- : undefined;
8
- }
9
- function routeId(account) {
10
- const label = preferredLabel(account.credentialId);
11
- if (label)
12
- return label;
13
- return `scoped-${createHash('sha256').update(account.accountId).digest('hex')}`;
14
- }
15
- function representativeRows(storage, rows) {
16
- const credentials = new Set();
17
- const byIdentity = new Map();
18
- for (const row of rows) {
19
- if (!row.credentialId.trim() ||
20
- !row.accountId.trim() ||
21
- credentials.has(row.credentialId)) {
22
- throw new Error('Claustrum returned an ambiguous account inventory');
23
- }
24
- credentials.add(row.credentialId);
25
- const group = byIdentity.get(row.accountId) ?? [];
26
- group.push(row);
27
- byIdentity.set(row.accountId, group);
28
- }
29
- return [...byIdentity.values()]
30
- .map((group) => {
31
- const existing = storage.accounts.find((account) => isOAuthAccount(account) &&
32
- account.anthropicAccountUuid === group[0]?.accountId);
33
- // Multiple login records for one provider account are not extra quota.
34
- // Keep its established binding where possible, except that the conventional
35
- // default owns the primary slot rather than appearing a second time below.
36
- const ordered = group.toSorted((left, right) => left.credentialId.localeCompare(right.credentialId));
37
- const active = ordered.filter((entry) => entry.state === 'active');
38
- const choices = active.length ? active : ordered;
39
- const row = choices.find((entry) => entry.credentialId === 'oauth:anthropic') ??
40
- choices.find((entry) => entry.credentialId === existing?.claustrumScopedCredentialId) ??
41
- choices[0];
42
- if (!row)
43
- throw new Error('Claustrum returned an empty account identity group');
44
- return row;
45
- })
46
- .sort((left, right) => left.credentialId.localeCompare(right.credentialId));
47
- }
48
- /** Pure identity projection; callers must serialize discovery before persisting it. */
49
- export function projectClaustrumScopedRoster(storage, rows, view, now = Date.now()) {
50
- if (new Set(storage.accounts.map((account) => account.id)).size !==
51
- storage.accounts.length) {
52
- throw new Error('Account configuration contains duplicate route identities');
53
- }
54
- const selected = representativeRows(storage, rows);
55
- const primaryIdentity = rows.find((row) => row.credentialId === 'oauth:anthropic')?.accountId;
56
- const primary = selected.find((row) => row.accountId === primaryIdentity);
57
- const disabled = new Set(storage.claustrum?.disabledAccountIdentities ?? []);
58
- for (const account of storage.accounts) {
59
- if (!isOAuthAccount(account) || !account.anthropicAccountUuid)
60
- continue;
61
- if (account.enabled === false)
62
- disabled.add(account.anthropicAccountUuid);
63
- else
64
- disabled.delete(account.anthropicAccountUuid);
65
- }
66
- const unused = new Set(storage.accounts.filter(isOAuthAccount).map((account) => account.id));
67
- const reserved = new Set(storage.accounts.map((account) => account.id));
68
- const next = selected
69
- .filter((row) => row !== primary)
70
- .map((row) => {
71
- const existing = storage.accounts.find((account) => isOAuthAccount(account) &&
72
- unused.has(account.id) &&
73
- account.anthropicAccountUuid === row.accountId);
74
- let id = existing?.id ?? routeId(row);
75
- // Existing API routes are unrelated and cannot be replaced by discovery.
76
- while ((id !== existing?.id && reserved.has(id)) || id === 'main')
77
- id = `scoped-${id}`;
78
- reserved.add(id);
79
- if (existing)
80
- unused.delete(existing.id);
81
- return {
82
- id,
83
- label: existing?.label ??
84
- preferredLabel(row.credentialId) ??
85
- row.email ??
86
- row.credentialId,
87
- type: 'oauth',
88
- enabled: existing
89
- ? existing.enabled !== false
90
- : !disabled.has(row.accountId),
91
- addedAt: existing?.addedAt ?? now,
92
- anthropicAccountUuid: row.accountId,
93
- claustrumScopedCredentialId: row.credentialId,
94
- claustrumScopedState: row.state,
95
- authLineageId: `scoped:${createHash('sha256').update(row.accountId).digest('hex')}`,
96
- refresh: '',
97
- // Provider identity, not rotating bearer material, owns these observations.
98
- quota: existing?.quota,
99
- profile: existing?.profile,
100
- prime: existing?.prime,
101
- lastUsed: existing?.lastUsed,
102
- lastQuotaRefreshError: existing?.lastQuotaRefreshError,
103
- };
104
- });
105
- const byId = new Map(next.map((account) => [account.id, account]));
106
- const ordered = storage.accounts.flatMap((account) => {
107
- if (!isOAuthAccount(account))
108
- return [account];
109
- const replacement = byId.get(account.id);
110
- if (!replacement)
111
- return [];
112
- byId.delete(account.id);
113
- return [replacement];
114
- });
115
- ordered.push(...byId.values());
116
- const projected = {
117
- ...storage,
118
- claustrum: {
119
- ...storage.claustrum,
120
- scopedRoster: true,
121
- primaryAccount: primary
122
- ? {
123
- credentialId: primary.credentialId,
124
- accountId: primary.accountId,
125
- state: primary.state,
126
- }
127
- : undefined,
128
- disabledAccountIdentities: [...disabled].sort(),
129
- },
130
- accounts: ordered,
131
- };
132
- return { primary, accounts: next, storage: projected, view };
133
- }
134
- /**
135
- * The lease covers LIST and commit, not just the file write: opaque view hashes
136
- * cannot tell a delayed old response from a newer one. A lost lease is checked
137
- * while holding the config lock, before any authoritative membership write.
138
- * Neither local mode nor a failed list can publish an empty inventory.
139
- */
140
- export async function refreshClaustrumScopedRoster(options) {
141
- options.signal?.throwIfAborted();
142
- if (getClaustrumMode(await loadAccounts(options.path)) !== 'claustrum')
143
- return undefined;
144
- const lease = await acquireRefreshFileLock({
145
- name: 'scoped-roster',
146
- path: options.path,
147
- ttlMs: 30_000,
148
- renew: true,
149
- });
150
- if (!lease)
151
- throw new Error('Claustrum account discovery is already in progress');
152
- try {
153
- if (getClaustrumMode(await loadAccounts(options.path)) !== 'claustrum')
154
- return undefined;
155
- const inventory = await options.custody.discover(options.signal);
156
- options.signal?.throwIfAborted();
157
- const result = await mutateAccountsPersistent(options.path, (current) => {
158
- if (getClaustrumMode(current) !== 'claustrum')
159
- return { storage: current, result: undefined, save: false };
160
- const roster = projectClaustrumScopedRoster(current, inventory.accounts, inventory.view, options.now?.());
161
- const retained = new Set(roster.storage.accounts.map((account) => account.id));
162
- const removedAccountIds = current.accounts
163
- .filter((account) => isOAuthAccount(account) && !retained.has(account.id))
164
- .map((account) => account.id);
165
- return {
166
- storage: roster.storage,
167
- result: roster,
168
- options: { removedAccountIds, preserveExistingAccountOrder: false },
169
- save: JSON.stringify(current) !== JSON.stringify(roster.storage),
170
- };
171
- }, {
172
- assertAuthority: async () => {
173
- options.signal?.throwIfAborted();
174
- await lease.assertOwned();
175
- },
176
- });
177
- if (!result)
178
- return undefined;
179
- const persisted = await loadAccounts(options.path);
180
- options.signal?.throwIfAborted();
181
- await lease.assertOwned();
182
- if (!persisted || getClaustrumMode(persisted) !== 'claustrum')
183
- return undefined;
184
- return {
185
- ...result,
186
- storage: persisted,
187
- accounts: persisted.accounts.filter((account) => isOAuthAccount(account) &&
188
- Boolean(account.claustrumScopedCredentialId)),
189
- };
190
- }
191
- finally {
192
- await lease.release();
193
- }
194
- }
@@ -1,28 +0,0 @@
1
- import { type ClaustrumClientOptions } from '@cortexkit/claustrum-client';
2
- import { type ClaustrumScopedAttempt, type ClaustrumScopedClient } from './claustrum-scoped.js';
3
- import { type ClaustrumScopedRoster } from './claustrum-scoped-roster.js';
4
- export { ClaustrumCredentialError as ClaustrumScopedCredentialError } from '@cortexkit/claustrum-client';
5
- export declare function connectClaustrumScopedClient(options?: ClaustrumClientOptions): Promise<ClaustrumScopedClient>;
6
- /** Host-owned lifecycle for metadata discovery. Bearer material is never cached. */
7
- export interface ClaustrumScopedRuntimeOptions {
8
- storagePath: string;
9
- tokenPath: string;
10
- connect: () => Promise<ClaustrumScopedClient>;
11
- onRoster?: (roster: ClaustrumScopedRoster) => void;
12
- onError?: () => void;
13
- setTimeoutImpl?: typeof setTimeout;
14
- clearTimeoutImpl?: typeof clearTimeout;
15
- pollIntervalMs?: number;
16
- }
17
- export declare class ClaustrumScopedRuntime {
18
- #private;
19
- constructor(options: ClaustrumScopedRuntimeOptions);
20
- snapshot(): ClaustrumScopedRoster | undefined;
21
- refresh(): Promise<ClaustrumScopedRoster | undefined>;
22
- start(): void;
23
- authorize(routeId: string, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
24
- reportFailure(attempt: ClaustrumScopedAttempt, status: number, source: 'direct' | 'relay_status_field' | 'relay_message_parse'): Promise<void>;
25
- fetchQuota(routeId: string, fetchImpl?: typeof fetch): Promise<import("./accounts.js").OAuthQuotaSnapshot>;
26
- fetchProfile(routeId: string, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<import("./accounts.js").OAuthAccountProfile>;
27
- close(): void;
28
- }
@@ -1,230 +0,0 @@
1
- import { ClaustrumClient, } from '@cortexkit/claustrum-client';
2
- import { fetchOAuthQuotaSnapshot, getClaustrumMode, isOAuthAccount, loadAccounts, } from './accounts.js';
3
- import { resolveClaustrumConnectionPath } from './claustrum.js';
4
- import { ClaustrumScopedCustody, } from './claustrum-scoped.js';
5
- import { refreshClaustrumScopedRoster, } from './claustrum-scoped-roster.js';
6
- import { fetchOAuthAccountProfile } from './oauth-profile.js';
7
- export { ClaustrumCredentialError as ClaustrumScopedCredentialError } from '@cortexkit/claustrum-client';
8
- export function connectClaustrumScopedClient(options = {}) {
9
- return ClaustrumClient.connect({
10
- ...options,
11
- connectionFile: options.connectionFile ?? resolveClaustrumConnectionPath(),
12
- });
13
- }
14
- export class ClaustrumScopedRuntime {
15
- #options;
16
- #shutdown = new AbortController();
17
- #custody;
18
- #connecting;
19
- #refreshing;
20
- #roster;
21
- #timer;
22
- #started = false;
23
- constructor(options) {
24
- this.#options = options;
25
- }
26
- #assertOpen(signal) {
27
- this.#shutdown.signal.throwIfAborted();
28
- signal?.throwIfAborted();
29
- }
30
- #wait(pending, signal) {
31
- const combined = AbortSignal.any([
32
- this.#shutdown.signal,
33
- ...(signal ? [signal] : []),
34
- ]);
35
- return new Promise((resolve, reject) => {
36
- const cleanup = () => combined.removeEventListener('abort', abort);
37
- const abort = () => {
38
- cleanup();
39
- reject(combined.reason);
40
- };
41
- combined.addEventListener('abort', abort, { once: true });
42
- // Observe the operation even after cancellation: shared metadata work may
43
- // finish for other callers, and a late connector must still be closed.
44
- pending.then((value) => {
45
- cleanup();
46
- if (combined.aborted)
47
- reject(combined.reason);
48
- else
49
- resolve(value);
50
- }, (error) => {
51
- cleanup();
52
- reject(error);
53
- });
54
- if (combined.aborted)
55
- abort();
56
- });
57
- }
58
- async #getCustody() {
59
- this.#assertOpen();
60
- if (this.#custody)
61
- return this.#custody;
62
- if (!this.#connecting) {
63
- this.#connecting = this.#options
64
- .connect()
65
- .then((client) => {
66
- if (this.#shutdown.signal.aborted) {
67
- client.close();
68
- this.#assertOpen();
69
- }
70
- this.#custody = new ClaustrumScopedCustody({
71
- client,
72
- tokenPath: this.#options.tokenPath,
73
- });
74
- return this.#custody;
75
- })
76
- .finally(() => {
77
- this.#connecting = undefined;
78
- });
79
- }
80
- return this.#wait(this.#connecting);
81
- }
82
- snapshot() {
83
- return this.#roster;
84
- }
85
- refresh() {
86
- this.#assertOpen();
87
- if (this.#refreshing)
88
- return this.#refreshing;
89
- this.#refreshing = (async () => {
90
- if (getClaustrumMode(await loadAccounts(this.#options.storagePath)) !==
91
- 'claustrum') {
92
- this.#roster = undefined;
93
- this.#custody?.close();
94
- this.#custody = undefined;
95
- return undefined;
96
- }
97
- const custody = await this.#getCustody();
98
- const roster = await refreshClaustrumScopedRoster({
99
- path: this.#options.storagePath,
100
- custody,
101
- signal: this.#shutdown.signal,
102
- });
103
- this.#assertOpen();
104
- this.#roster = roster;
105
- if (roster)
106
- this.#options.onRoster?.(roster);
107
- return roster;
108
- })().finally(() => {
109
- this.#refreshing = undefined;
110
- });
111
- return this.#refreshing;
112
- }
113
- start() {
114
- if (this.#started)
115
- return;
116
- this.#assertOpen();
117
- this.#started = true;
118
- const tick = async () => {
119
- try {
120
- await this.refresh();
121
- }
122
- catch {
123
- if (!this.#shutdown.signal.aborted)
124
- this.#options.onError?.();
125
- }
126
- if (this.#shutdown.signal.aborted)
127
- return;
128
- const delay = this.#options.pollIntervalMs ?? 5_000;
129
- if (delay <= 0)
130
- return;
131
- this.#timer = (this.#options.setTimeoutImpl ?? setTimeout)(() => {
132
- this.#timer = undefined;
133
- void tick();
134
- }, delay);
135
- this.#timer.unref?.();
136
- };
137
- void tick();
138
- }
139
- async authorize(routeId, signal) {
140
- this.#assertOpen(signal);
141
- const roster = this.#roster ?? (await this.#wait(this.refresh(), signal));
142
- const current = await loadAccounts(this.#options.storagePath);
143
- this.#assertOpen(signal);
144
- if (!roster || getClaustrumMode(current) !== 'claustrum')
145
- throw new Error('Claustrum scoped custody is not active');
146
- let credentialId;
147
- let accountId;
148
- if (routeId === 'main') {
149
- const primary = current?.claustrum?.primaryAccount;
150
- if (roster.primary?.state !== 'active' ||
151
- primary?.state !== 'active' ||
152
- primary.accountId !== roster.primary.accountId ||
153
- primary.credentialId !== roster.primary.credentialId)
154
- throw new Error('Claustrum primary account is unavailable or changed');
155
- credentialId = roster.primary.credentialId;
156
- accountId = roster.primary.accountId;
157
- if (current?.claustrum?.disabledAccountIdentities?.includes(accountId)) {
158
- throw new Error('Claustrum primary account is disabled');
159
- }
160
- }
161
- else {
162
- const observed = roster.accounts.find((account) => account.id === routeId);
163
- const configured = current?.accounts.find((account) => account.id === routeId);
164
- if (!observed ||
165
- !configured ||
166
- !isOAuthAccount(configured) ||
167
- configured.enabled === false ||
168
- configured.claustrumScopedState !== 'active' ||
169
- configured.claustrumScopedCredentialId !==
170
- observed.claustrumScopedCredentialId ||
171
- configured.anthropicAccountUuid !== observed.anthropicAccountUuid) {
172
- throw new Error('Claustrum route is disabled, removed or changed');
173
- }
174
- credentialId = observed.claustrumScopedCredentialId;
175
- accountId = observed.anthropicAccountUuid;
176
- }
177
- if (!credentialId || !accountId)
178
- throw new Error('Claustrum route has no verified identity');
179
- return (await this.#wait(this.#getCustody(), signal)).authorize({ credentialId, accountId }, AbortSignal.any([this.#shutdown.signal, ...(signal ? [signal] : [])]));
180
- }
181
- async reportFailure(attempt, status, source) {
182
- this.#assertOpen();
183
- if (!this.#custody)
184
- throw new Error('Claustrum dispatch receipt has no active owner');
185
- await this.#custody.reportFailure(attempt, status, source);
186
- }
187
- async fetchQuota(routeId, fetchImpl = fetch) {
188
- const attempt = await this.authorize(routeId);
189
- return fetchOAuthQuotaSnapshot({
190
- accessToken: attempt.accessToken,
191
- fetchImpl: this.#fetchForAttempt(attempt, fetchImpl),
192
- });
193
- }
194
- async fetchProfile(routeId, fetchImpl = fetch, signal) {
195
- const attempt = await this.authorize(routeId, signal);
196
- return fetchOAuthAccountProfile({
197
- accessToken: attempt.accessToken,
198
- accountIdentity: attempt.accountId,
199
- providerAccountUuid: attempt.accountId,
200
- signal,
201
- fetchImpl: this.#fetchForAttempt(attempt, fetchImpl),
202
- });
203
- }
204
- #fetchForAttempt(attempt, fetchImpl) {
205
- return Object.assign(async (input, init) => {
206
- this.#assertOpen();
207
- const response = await fetchImpl(input, {
208
- ...init,
209
- signal: AbortSignal.any([
210
- this.#shutdown.signal,
211
- ...(init?.signal ? [init.signal] : []),
212
- ]),
213
- });
214
- if (response.status === 401) {
215
- await this.reportFailure(attempt, response.status, 'direct').catch(() => this.#options.onError?.());
216
- }
217
- return response;
218
- }, { preconnect: fetchImpl.preconnect });
219
- }
220
- close() {
221
- if (this.#shutdown.signal.aborted)
222
- return;
223
- this.#shutdown.abort(new Error('Claustrum scoped runtime is closed'));
224
- if (this.#timer)
225
- (this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
226
- this.#timer = undefined;
227
- this.#custody?.close();
228
- this.#roster = undefined;
229
- }
230
- }
@@ -1,39 +0,0 @@
1
- import { type ClaustrumClient, type ClaustrumReporterSource, type EnrollmentTokenFile } from '@cortexkit/claustrum-client';
2
- export type ClaustrumScopedClient = Pick<ClaustrumClient, 'listScoped' | 'getScoped' | 'reportAuthFailureScoped' | 'close'>;
3
- export interface ClaustrumScopedIdentity {
4
- readonly credentialId: string;
5
- readonly accountId: string;
6
- }
7
- export interface ClaustrumScopedAccount extends ClaustrumScopedIdentity {
8
- readonly state: string;
9
- readonly email?: string;
10
- readonly orgName?: string;
11
- }
12
- export interface ClaustrumScopedAttempt extends ClaustrumScopedIdentity {
13
- /** Non-enumerable, memory-only bearer. Authorize again for every dispatch/retry. */
14
- readonly accessToken: string;
15
- readonly recordVersion: number;
16
- readonly expiresAtMs: number;
17
- }
18
- /**
19
- * Shared OpenCode/Pi scoped read plane. There is intentionally no credential cache,
20
- * handle fallback, or get single-flight: each outgoing attempt must be authorized
21
- * by the daemon, including attempts made after a successful earlier request.
22
- * Enrollment tokens are re-read per operation so operator reissue works on disk.
23
- */
24
- export declare class ClaustrumScopedCustody {
25
- #private;
26
- constructor(options: {
27
- client: ClaustrumScopedClient;
28
- tokenPath?: string;
29
- readToken?: () => Promise<EnrollmentTokenFile>;
30
- now?: () => number;
31
- });
32
- discover(signal?: AbortSignal): Promise<{
33
- view: string;
34
- accounts: readonly ClaustrumScopedAccount[];
35
- }>;
36
- authorize(identity: ClaustrumScopedIdentity, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
37
- reportFailure(attempt: ClaustrumScopedAttempt, status: number, reporterSource: ClaustrumReporterSource): Promise<void>;
38
- close(): void;
39
- }
@@ -1,193 +0,0 @@
1
- import { ClaustrumCredentialError, } from '@cortexkit/claustrum-client';
2
- import { CUSTODY_TOMBSTONE_PREFIX } from './claustrum.js';
3
- import { readClaustrumEnrollmentToken } from './claustrum-enrollment.js';
4
- import { parseJsonRedacted } from './json.js';
5
- const SERVING_MARGIN_MS = 300_000;
6
- function accessTokenFromMaterial(material) {
7
- let access = material.trim();
8
- if (access.startsWith('{')) {
9
- let parsed;
10
- try {
11
- parsed = parseJsonRedacted(access);
12
- }
13
- catch {
14
- throw new Error('Claustrum returned invalid OAuth material');
15
- }
16
- if (!parsed || typeof parsed !== 'object') {
17
- throw new Error('Claustrum returned invalid OAuth material');
18
- }
19
- const record = parsed;
20
- const value = record.access_token ?? record.access;
21
- access = typeof value === 'string' ? value : '';
22
- }
23
- if (!/^[\x21-\x7e]+$/.test(access) ||
24
- access.startsWith(CUSTODY_TOMBSTONE_PREFIX)) {
25
- throw new Error('Claustrum returned invalid OAuth material');
26
- }
27
- return access;
28
- }
29
- /**
30
- * Shared OpenCode/Pi scoped read plane. There is intentionally no credential cache,
31
- * handle fallback, or get single-flight: each outgoing attempt must be authorized
32
- * by the daemon, including attempts made after a successful earlier request.
33
- * Enrollment tokens are re-read per operation so operator reissue works on disk.
34
- */
35
- export class ClaustrumScopedCustody {
36
- #client;
37
- #readToken;
38
- #now;
39
- #provenance = new WeakMap();
40
- #reports = new WeakMap();
41
- #closed = false;
42
- constructor(options) {
43
- const tokenPath = options.tokenPath;
44
- if (options.readToken) {
45
- this.#readToken = options.readToken;
46
- }
47
- else if (tokenPath) {
48
- this.#readToken = () => readClaustrumEnrollmentToken(tokenPath);
49
- }
50
- else {
51
- throw new Error('Claustrum enrollment token path is required');
52
- }
53
- this.#client = options.client;
54
- this.#now = options.now ?? Date.now;
55
- }
56
- #check(signal) {
57
- if (this.#closed)
58
- throw new Error('Claustrum scoped custody is closed');
59
- signal?.throwIfAborted();
60
- }
61
- async #token(signal) {
62
- this.#check(signal);
63
- const value = await this.#readToken();
64
- this.#check(signal);
65
- if (!/^[0-9a-f]{64}$/.test(value.token) ||
66
- !Number.isSafeInteger(value.token_generation) ||
67
- value.token_generation < 1) {
68
- throw new Error('Invalid Claustrum enrollment token');
69
- }
70
- return value.token;
71
- }
72
- async #call(operation, signal) {
73
- this.#check(signal);
74
- let result;
75
- let removeAbortListener;
76
- try {
77
- const pending = operation();
78
- result = signal
79
- ? await Promise.race([
80
- pending,
81
- new Promise((_resolve, reject) => {
82
- const abort = () => reject(signal.reason);
83
- signal.addEventListener('abort', abort, { once: true });
84
- removeAbortListener = () => signal.removeEventListener('abort', abort);
85
- if (signal.aborted)
86
- abort();
87
- }),
88
- ])
89
- : await pending;
90
- }
91
- catch (error) {
92
- this.#check(signal);
93
- // Preserve producer-owned classifications, never arbitrary transport text
94
- // which may echo request params containing the enrollment bearer.
95
- if (error instanceof ClaustrumCredentialError)
96
- throw error;
97
- throw new Error('Claustrum scoped operation unavailable');
98
- }
99
- finally {
100
- removeAbortListener?.();
101
- }
102
- this.#check(signal);
103
- return result;
104
- }
105
- async discover(signal) {
106
- const token = await this.#token(signal);
107
- const inventory = await this.#call(() => this.#client.listScoped(token), signal);
108
- const seen = new Set();
109
- const accounts = [];
110
- for (const row of inventory.rows) {
111
- if (row.refreshAdapter !== 'anthropic' ||
112
- row.credentialType !== 'oauth' ||
113
- !row.operations.includes('read'))
114
- continue;
115
- if (!row.id || !row.accountId?.trim() || seen.has(row.id)) {
116
- throw new Error('Claustrum inventory has missing or ambiguous account identity');
117
- }
118
- seen.add(row.id);
119
- accounts.push(Object.freeze({
120
- credentialId: row.id,
121
- accountId: row.accountId,
122
- state: row.state,
123
- ...(row.email !== undefined && { email: row.email }),
124
- ...(row.orgName !== undefined && { orgName: row.orgName }),
125
- }));
126
- }
127
- return { view: inventory.view, accounts: Object.freeze(accounts) };
128
- }
129
- async authorize(identity, signal) {
130
- if (!identity.credentialId || !identity.accountId)
131
- throw new Error('Claustrum dispatch requires account identity');
132
- // Capture caller fields before yielding so mutation cannot change the fence.
133
- const { credentialId, accountId } = identity;
134
- const token = await this.#token(signal);
135
- const served = await this.#call(() => this.#client.getScoped({
136
- credentialId,
137
- enrollmentToken: token,
138
- minTtlMs: SERVING_MARGIN_MS,
139
- }), signal);
140
- if (served.credentialId !== credentialId ||
141
- served.accountId !== accountId) {
142
- throw new Error('Claustrum served credential identity changed');
143
- }
144
- if (!Number.isSafeInteger(served.recordVersion) ||
145
- served.recordVersion < 0 ||
146
- served.expiresAtMs === null ||
147
- !Number.isFinite(served.expiresAtMs) ||
148
- served.expiresAtMs - this.#now() < SERVING_MARGIN_MS) {
149
- throw new Error('Claustrum served credential has insufficient validity');
150
- }
151
- const accessToken = accessTokenFromMaterial(served.material);
152
- const attempt = Object.freeze(Object.defineProperty({
153
- credentialId,
154
- accountId,
155
- recordVersion: served.recordVersion,
156
- expiresAtMs: served.expiresAtMs,
157
- }, 'accessToken', { value: accessToken, enumerable: false }));
158
- this.#provenance.set(attempt, token);
159
- return attempt;
160
- }
161
- async reportFailure(attempt, status, reporterSource) {
162
- if (status !== 401)
163
- return;
164
- this.#check();
165
- const token = this.#provenance.get(attempt);
166
- if (!token)
167
- throw new Error('Claustrum failure report requires an original dispatch receipt');
168
- const pending = this.#reports.get(attempt);
169
- if (pending)
170
- return pending;
171
- const report = this.#call(() => this.#client.reportAuthFailureScoped({
172
- credentialId: attempt.credentialId,
173
- enrollmentToken: token,
174
- providerStatus: 401,
175
- recordVersion: attempt.recordVersion,
176
- reporterSource,
177
- }));
178
- this.#reports.set(attempt, report);
179
- try {
180
- await report;
181
- }
182
- catch (error) {
183
- this.#reports.delete(attempt);
184
- throw error;
185
- }
186
- }
187
- close() {
188
- if (this.#closed)
189
- return;
190
- this.#closed = true;
191
- this.#client.close();
192
- }
193
- }