@cortexkit/opencode-openai-auth 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-DqKq8Eej.d.ts → codex-request-BXDXToyt.d.ts} +158 -27
  3. package/dist/index-0yh85bwb.js +69 -0
  4. package/dist/{index-jye6vccc.js → index-4c8yw54k.js} +2 -2
  5. package/dist/{index-k4eacw7x.js → index-7c6sxtd6.js} +2 -2
  6. package/dist/index-7eb3dw37.js +90 -0
  7. package/dist/index-8re8rprk.js +2 -0
  8. package/dist/{index-bgq9d21h.js → index-csky2xpg.js} +1 -1
  9. package/dist/index-edbdscy7.js +65 -0
  10. package/dist/{index-45qgkb0m.js → index-f2fmb596.js} +1 -1
  11. package/dist/{index-envpbt0k.js → index-j8v2xwq6.js} +1 -1
  12. package/dist/{index-egrty6tz.js → index-k6cydkgt.js} +2 -2
  13. package/dist/{index-dryydk6b.js → index-m5qmvr6c.js} +2 -2
  14. package/dist/index-msx2m78k.js +8 -0
  15. package/dist/{index-yeyp8t0k.js → index-zpm95mt5.js} +1 -1
  16. package/dist/index.d.ts +15 -7
  17. package/dist/index.js +1 -1
  18. package/dist/{map-CCbSZIhT.d.ts → map-Buq-GW7l.d.ts} +1 -1
  19. package/dist/{protocol-CSIDDBDf.d.ts → model-YRg7Uowo.d.ts} +67 -25
  20. package/dist/{port-file-CZ9MIdy2.d.ts → port-file-CxEpgSHY.d.ts} +1 -1
  21. package/dist/protocol-CDV1Pc6h.d.ts +29 -0
  22. package/dist/rpc/port-file.d.ts +1 -1
  23. package/dist/rpc/port-file.js +1 -1
  24. package/dist/rpc/protocol.d.ts +1 -1
  25. package/dist/rpc/protocol.js +1 -1
  26. package/dist/rpc/rpc-client.d.ts +3 -3
  27. package/dist/rpc/rpc-client.js +1 -1
  28. package/dist/rpc/rpc-dir.js +1 -1
  29. package/dist/{sidebar-state-BCxFpH68.d.ts → sidebar-state-DjTWUbmf.d.ts} +15 -9
  30. package/dist/sidebar-state.d.ts +1 -1
  31. package/dist/sidebar-state.js +1 -1
  32. package/dist/{token-vykm9bxh.js → token-rz4t3ay6.js} +1 -1
  33. package/dist/token-util-tpy2c7h6.js +1 -0
  34. package/dist/tui-preferences.js +1 -1
  35. package/dist/tui.d.ts +20 -2
  36. package/dist/v2/server.d.ts +73 -1
  37. package/dist/v2/server.js +3 -3
  38. package/package.json +2 -2
  39. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +78 -42
  40. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +354 -0
  41. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  42. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +393 -0
  43. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  44. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  45. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  46. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  47. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  48. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  49. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  50. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  51. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  52. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  53. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  54. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  55. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  56. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  57. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  58. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  59. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  60. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  61. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  62. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  63. package/src/tui-compiled/raw/sidebar-state.js +41 -14
  64. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  65. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  66. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  67. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  68. package/src/tui-compiled/raw/tui.tsx +146 -40
  69. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +78 -42
  70. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +354 -0
  71. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  72. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +393 -0
  73. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  74. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  75. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  76. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  77. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  78. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  79. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  80. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  81. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  82. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  83. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  84. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  85. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  86. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  87. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  88. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  89. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  90. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  91. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  92. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  93. package/src/tui-compiled/runtime/sidebar-state.js +41 -14
  94. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  95. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  96. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  97. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  98. package/src/tui-compiled/runtime/tui.js +255 -163
  99. package/dist/index-2hch7e8n.js +0 -8
  100. package/dist/index-5dg931q0.js +0 -65
  101. package/dist/index-99esnzg0.js +0 -157
  102. package/dist/index-9reyazwh.js +0 -2
  103. package/dist/index-yf6arn5w.js +0 -2
  104. package/dist/token-util-gmn5yaqc.js +0 -1
@@ -0,0 +1,393 @@
1
+ import { createLogger } from "./cc9e0702a752cea7-index.js";
2
+ import { combineAbortSignals } from "./6371b24a0573d79e-any-signal.js";
3
+ import { ClaustrumScopedCustody, decideScopedRetryAfter401, } from "./0f77ebc3c914c9c6-custody.js";
4
+ import { readClaustrumEnrollmentToken } from "./b9588ad4725c1d10-enrollment.js";
5
+ import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
6
+ import { acquireInsulaQuotaSource } from "./d0f0b6be200311af-insula.js";
7
+ import { acceptVaultRoute, declineVaultRoute, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
8
+ /**
9
+ * One plugin host's view of its vault accounts. It turns vault credentials
10
+ * into rows for `/routing`, authorizes each physical send with the vault, and
11
+ * reports a 401 against the exact record version that send used. Metadata
12
+ * (the connection and discovery) is shared and coalesced; credential reads
13
+ * never are. It never enrolls: a missing token is an error here, and setup
14
+ * is where enrollment happens.
15
+ */
16
+ export class ClaustrumConsumer {
17
+ #options;
18
+ #shutdown = new AbortController();
19
+ #logger;
20
+ #custody;
21
+ #connecting;
22
+ #refreshing;
23
+ #roster;
24
+ #timer;
25
+ #started = false;
26
+ #insula;
27
+ constructor(options) {
28
+ this.#options = options;
29
+ this.#logger = options.logger ?? createLogger('claustrum');
30
+ }
31
+ #assertOpen(signal) {
32
+ this.#shutdown.signal.throwIfAborted();
33
+ signal?.throwIfAborted();
34
+ }
35
+ async #active() {
36
+ return (await this.#options.isCustodyActive?.()) ?? true;
37
+ }
38
+ #wait(pending, signal) {
39
+ const combined = combineAbortSignals([
40
+ this.#shutdown.signal,
41
+ ...(signal ? [signal] : []),
42
+ ]);
43
+ return new Promise((resolve, reject) => {
44
+ const cleanup = () => {
45
+ combined.signal.removeEventListener('abort', abort);
46
+ combined.dispose();
47
+ };
48
+ const abort = () => {
49
+ cleanup();
50
+ reject(combined.signal.reason);
51
+ };
52
+ combined.signal.addEventListener('abort', abort, { once: true });
53
+ // Observe the operation even after cancellation: shared metadata work may
54
+ // finish for other callers, and a late connection must still be closed.
55
+ pending.then((value) => {
56
+ cleanup();
57
+ if (combined.signal.aborted)
58
+ reject(combined.signal.reason);
59
+ else
60
+ resolve(value);
61
+ }, (error) => {
62
+ cleanup();
63
+ reject(error);
64
+ });
65
+ if (combined.signal.aborted)
66
+ abort();
67
+ });
68
+ }
69
+ async #getCustody() {
70
+ this.#assertOpen();
71
+ if (this.#custody)
72
+ return this.#custody;
73
+ if (!this.#connecting) {
74
+ this.#connecting = this.#options
75
+ .connect()
76
+ .then((client) => {
77
+ if (this.#shutdown.signal.aborted) {
78
+ client.close();
79
+ this.#assertOpen();
80
+ }
81
+ this.#custody = new ClaustrumScopedCustody({
82
+ client,
83
+ family: this.#options.family,
84
+ tokenPath: this.#options.tokenPath,
85
+ parseIdentity: this.#options.parseIdentity,
86
+ requireAssertion: this.#options.requireAssertion,
87
+ now: this.#options.now,
88
+ logger: this.#logger,
89
+ });
90
+ if (this.#options.insula && client.insula) {
91
+ this.#insula = acquireInsulaQuotaSource(client.insula, this.#options.tokenPath);
92
+ }
93
+ return this.#custody;
94
+ })
95
+ .finally(() => {
96
+ this.#connecting = undefined;
97
+ });
98
+ }
99
+ return this.#wait(this.#connecting);
100
+ }
101
+ /** The last committed roster this instance saw, without any I/O. */
102
+ snapshot() {
103
+ return this.#roster;
104
+ }
105
+ /** Rows for `/routing`, from the last committed roster. */
106
+ routingRows() {
107
+ return vaultRoutingRows(this.#roster);
108
+ }
109
+ refresh() {
110
+ this.#assertOpen();
111
+ if (this.#refreshing)
112
+ return this.#refreshing;
113
+ this.#refreshing = (async () => {
114
+ if (!(await this.#active())) {
115
+ this.#roster = undefined;
116
+ this.#custody?.close();
117
+ this.#custody = undefined;
118
+ this.#insula?.release();
119
+ this.#insula = undefined;
120
+ return undefined;
121
+ }
122
+ const custody = await this.#getCustody();
123
+ const roster = await refreshVaultRoster({
124
+ path: this.#options.rosterPath,
125
+ custody,
126
+ isActive: () => this.#active(),
127
+ signal: this.#shutdown.signal,
128
+ projection: () => ({
129
+ reservedRouteIds: new Set(this.#options.reservedRouteIds?.() ?? []),
130
+ routePrefix: this.#options.routePrefix,
131
+ mapAccount: this.#options.mapAccount,
132
+ now: (this.#options.now ?? Date.now)(),
133
+ }),
134
+ });
135
+ this.#assertOpen();
136
+ const changed = roster?.view !== this.#roster?.view;
137
+ this.#roster = roster;
138
+ // Notify on view changes only: subscribers rewrite shared state on each
139
+ // notification, and the poll runs every few seconds.
140
+ if (roster && changed)
141
+ this.#options.onRoster?.(roster);
142
+ return roster;
143
+ })().finally(() => {
144
+ this.#refreshing = undefined;
145
+ });
146
+ return this.#refreshing;
147
+ }
148
+ start() {
149
+ if (this.#started)
150
+ return;
151
+ this.#assertOpen();
152
+ this.#started = true;
153
+ const tick = async () => {
154
+ try {
155
+ await this.refresh();
156
+ await this.refreshInsulaQuota();
157
+ }
158
+ catch (error) {
159
+ if (!this.#shutdown.signal.aborted)
160
+ this.#options.onError?.(error);
161
+ }
162
+ if (this.#shutdown.signal.aborted)
163
+ return;
164
+ const delay = this.#options.pollIntervalMs ?? 5_000;
165
+ if (delay <= 0)
166
+ return;
167
+ this.#timer = (this.#options.setTimeoutImpl ?? setTimeout)(() => {
168
+ this.#timer = undefined;
169
+ void tick();
170
+ }, delay);
171
+ this.#timer.unref?.();
172
+ };
173
+ void tick();
174
+ }
175
+ /**
176
+ * Fetch on a background tick, at most once per minute across projects with
177
+ * the same daemon and enrollment token path. Admission must use the cache
178
+ * instead, so an optional quota daemon never delays a provider request.
179
+ */
180
+ async refreshInsulaQuota() {
181
+ if (!this.#options.insula ||
182
+ this.#shutdown.signal.aborted ||
183
+ !(await this.#active()))
184
+ return;
185
+ try {
186
+ await readClaustrumEnrollmentToken(this.#options.tokenPath);
187
+ await this.#getCustody();
188
+ await this.#insula?.source.refresh((this.#options.now ?? Date.now)());
189
+ }
190
+ catch {
191
+ // An optional source must never prevent the plugin's own quota poll.
192
+ }
193
+ }
194
+ /**
195
+ * Cache-only, for unknown-quota admission with a served receipt. Undefined
196
+ * means poll the provider normally. Only get_scoped's asserted account_id
197
+ * joins insula; parsed identities and display metadata cannot substitute.
198
+ * Background polling should use cachedInsulaQuotaForRoute instead.
199
+ */
200
+ async cachedInsulaQuota(receipt) {
201
+ if (!this.#options.insula ||
202
+ this.#shutdown.signal.aborted ||
203
+ !(await this.#active()))
204
+ return undefined;
205
+ try {
206
+ await readClaustrumEnrollmentToken(this.#options.tokenPath);
207
+ return this.#normalizeInsulaQuota(receipt.assertedAccountIdentity);
208
+ }
209
+ catch {
210
+ return undefined;
211
+ }
212
+ }
213
+ /**
214
+ * Background cache lookup without fetching a credential. Re-read the roster
215
+ * so a peer's account replacement invalidates the old route immediately.
216
+ * A route without a known provider account cannot join an insula entry.
217
+ */
218
+ async cachedInsulaQuotaForRoute(routeId) {
219
+ if (!this.#options.insula ||
220
+ this.#shutdown.signal.aborted ||
221
+ !(await this.#active()))
222
+ return undefined;
223
+ try {
224
+ await readClaustrumEnrollmentToken(this.#options.tokenPath);
225
+ const roster = await readVaultRoster(this.#options.rosterPath);
226
+ const row = roster?.rows.find((entry) => entry.routeId === routeId);
227
+ return this.#normalizeInsulaQuota(row?.accountIdentity);
228
+ }
229
+ catch {
230
+ return undefined;
231
+ }
232
+ }
233
+ #normalizeInsulaQuota(accountIdentity) {
234
+ if (!this.#options.insula || !accountIdentity)
235
+ return undefined;
236
+ const current = this.#insula?.source.current(this.#options.insula.provider, accountIdentity, (this.#options.now ?? Date.now)());
237
+ return current
238
+ ? this.#options.insula.normalize(current.entry, current.context)
239
+ : undefined;
240
+ }
241
+ /**
242
+ * Authorize one physical send on a vault route. The roster file is re-read
243
+ * first, so a decline committed by another process applies before the next
244
+ * poll, and a route whose credential or account changed is refused.
245
+ */
246
+ async authorize(routeId, signal) {
247
+ this.#assertOpen(signal);
248
+ const roster = this.#roster ?? (await this.#wait(this.refresh(), signal));
249
+ if (!roster || !(await this.#active()))
250
+ throw new ClaustrumConsumerError('not-active', 'Claustrum scoped custody is not active');
251
+ const current = await readVaultRoster(this.#options.rosterPath);
252
+ this.#assertOpen(signal);
253
+ const observed = roster.rows.find((row) => row.routeId === routeId);
254
+ const configured = current?.rows.find((row) => row.routeId === routeId);
255
+ if (configured && !configured.enabled)
256
+ throw new ClaustrumConsumerError('route-declined', 'Claustrum route is disabled');
257
+ if (!observed ||
258
+ !configured ||
259
+ configured.state !== 'active' ||
260
+ configured.credentialId !== observed.credentialId ||
261
+ configured.accountIdentity !== observed.accountIdentity)
262
+ throw new ClaustrumConsumerError('route-unavailable', 'Claustrum route is disabled, removed or changed');
263
+ const custody = await this.#wait(this.#getCustody(), signal);
264
+ const combined = combineAbortSignals([
265
+ this.#shutdown.signal,
266
+ ...(signal ? [signal] : []),
267
+ ]);
268
+ try {
269
+ return await custody.authorize({
270
+ credentialId: configured.credentialId,
271
+ credentialType: configured.credentialType,
272
+ ...(configured.accountIdentity !== undefined && {
273
+ accountIdentity: configured.accountIdentity,
274
+ }),
275
+ }, combined.signal);
276
+ }
277
+ finally {
278
+ combined.dispose();
279
+ }
280
+ }
281
+ async reportFailure(attempt, status, source) {
282
+ this.#assertOpen();
283
+ if (!this.#custody)
284
+ throw new ClaustrumConsumerError('no-receipt', 'Claustrum dispatch receipt has no active owner');
285
+ await this.#custody.reportFailure(attempt, status, source);
286
+ }
287
+ /**
288
+ * Send on a vault route. `dispatch` builds and sends the request with the
289
+ * receipt's token and may be called twice, so it must be able to rebuild
290
+ * its body. A 401 is retried once, and only when the vault now serves a new
291
+ * record version of the same credential and account; the final 401 is
292
+ * reported against the version that send actually used. Each call to
293
+ * `dispatch` gets its own receipt.
294
+ */
295
+ async send(routeId, dispatch, options) {
296
+ const signal = options.signal;
297
+ let served = await this.authorize(routeId, signal);
298
+ let response = await dispatch(served, signal);
299
+ if (response.status === 401 && !signal?.aborted) {
300
+ let current;
301
+ try {
302
+ current = await this.authorize(routeId, signal);
303
+ }
304
+ catch {
305
+ // Re-authorization failed: keep this 401 and report it against the
306
+ // receipt (served record version) this request was sent with.
307
+ }
308
+ if (decideScopedRetryAfter401(options.site, served, current, this.#logger)) {
309
+ await response.body?.cancel().catch(() => { });
310
+ served = current;
311
+ response = await dispatch(current, signal);
312
+ }
313
+ }
314
+ if (response.status === 401) {
315
+ await this.reportFailure(served, 401, options.reporterSource ?? 'direct').catch((error) => this.#options.onError?.(error));
316
+ // After the report the vault may mark the account as needing a new
317
+ // login; refresh now so routing drops it without waiting for the next poll. Deferred so a consumer closed in
318
+ // the meantime reports to onError instead of throwing from this send.
319
+ Promise.resolve()
320
+ .then(() => this.refresh())
321
+ .catch((error) => {
322
+ if (!this.#shutdown.signal.aborted)
323
+ this.#options.onError?.(error);
324
+ });
325
+ }
326
+ return response;
327
+ }
328
+ /** Decline a vault account: it stays listed and never routes until accepted. */
329
+ async decline(routeId) {
330
+ this.#assertOpen();
331
+ await declineVaultRoute(this.#options.rosterPath, routeId);
332
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
333
+ }
334
+ async accept(routeId) {
335
+ this.#assertOpen();
336
+ await acceptVaultRoute(this.#options.rosterPath, routeId);
337
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
338
+ }
339
+ /**
340
+ * Store a quota or profile observation for a vault route. The receipt the
341
+ * reading was taken with is required: the observation lands only while the
342
+ * route still holds the credential and account that receipt was served for
343
+ * (see `recordVaultQuota`), so a reading for a replaced account is dropped.
344
+ */
345
+ async recordQuota(routeId, observation, attempt) {
346
+ this.#assertOpen();
347
+ const kept = await recordVaultQuota(this.#options.rosterPath, {
348
+ routeId,
349
+ observation,
350
+ credentialId: attempt.credentialId,
351
+ accountIdentitySource: attempt.accountIdentitySource,
352
+ ...(attempt.accountIdentity !== undefined && {
353
+ accountIdentity: attempt.accountIdentity,
354
+ }),
355
+ ...(attempt.expectedAccountIdentity !== undefined && {
356
+ expectedAccountIdentity: attempt.expectedAccountIdentity,
357
+ }),
358
+ });
359
+ if (kept)
360
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
361
+ return kept;
362
+ }
363
+ /**
364
+ * Store a background observation that belongs to an account rather than to
365
+ * a served credential (an insula reading from `cachedInsulaQuotaForRoute`).
366
+ * It lands only while the route is still bound to that known account, so no
367
+ * credential fetch is needed and a replaced account's reading is dropped.
368
+ * The in-memory roster is reloaded from the file, without a vault call.
369
+ */
370
+ async recordAccountQuota(routeId, accountIdentity, observation) {
371
+ this.#assertOpen();
372
+ const kept = await recordVaultAccountQuota(this.#options.rosterPath, {
373
+ routeId,
374
+ accountIdentity,
375
+ observation,
376
+ });
377
+ if (kept)
378
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
379
+ return kept;
380
+ }
381
+ close() {
382
+ if (this.#shutdown.signal.aborted)
383
+ return;
384
+ this.#shutdown.abort(new ClaustrumConsumerError('closed', 'Claustrum consumer is closed'));
385
+ if (this.#timer)
386
+ (this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
387
+ this.#timer = undefined;
388
+ this.#custody?.close();
389
+ this.#insula?.release();
390
+ this.#insula = undefined;
391
+ this.#roster = undefined;
392
+ }
393
+ }
@@ -0,0 +1,154 @@
1
+ export function toResetIso(raw) {
2
+ if (raw == null || raw === "")
3
+ return;
4
+ const n = typeof raw === "number" ? raw : Number(raw);
5
+ if (Number.isFinite(n)) {
6
+ const ms = n < 100000000000 ? n * 1000 : n, d = new Date(ms);
7
+ return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
8
+ }
9
+ const d = new Date(raw);
10
+ return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
11
+ }
12
+ function positiveFinite(value) {
13
+ const parsed = Number(value);
14
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
15
+ }
16
+ function nonNegativeFinite(value) {
17
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
18
+ }
19
+ function windowFromHeader(h, prefix) {
20
+ const used = h.get(prefix);
21
+ if (used === null || used.trim() === "")
22
+ return;
23
+ const usedPercent = Number(used);
24
+ if (!Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100)
25
+ return;
26
+ const base = prefix.slice(0, -13), windowMinutes = positiveFinite(h.get(`${base}-window-minutes`)), resetsAt = toResetIso(h.get(`${base}-reset-at`) ?? void 0);
27
+ if (usedPercent === 0 && h.get(`${base}-window-minutes`) === "0" && !resetsAt)
28
+ return;
29
+ return {
30
+ usedPercent,
31
+ remainingPercent: 100 - usedPercent,
32
+ resetsAt,
33
+ checkedAt: Date.now(),
34
+ ...windowMinutes !== void 0 ? { windowMinutes } : {}
35
+ };
36
+ }
37
+ const QUOTA_USED_PERCENT_HEADERS = [
38
+ "x-codex-primary-used-percent",
39
+ "x-codex-secondary-used-percent"
40
+ ];
41
+ function usedPercentHeaderState(h, name) {
42
+ const raw = h.get(name);
43
+ if (raw === null)
44
+ return "absent";
45
+ if (raw.trim() === "")
46
+ return "invalid";
47
+ const value = Number(raw);
48
+ return Number.isFinite(value) && value >= 0 && value <= 100 ? "valid" : "invalid";
49
+ }
50
+ export function normalizeQuotaHeaders(h) {
51
+ if (QUOTA_USED_PERCENT_HEADERS.some((name) => usedPercentHeaderState(h, name) === "invalid"))
52
+ return {};
53
+ const snapshot = {}, primary = windowFromHeader(h, "x-codex-primary-used-percent");
54
+ if (primary)
55
+ snapshot.primary = primary;
56
+ const secondary = windowFromHeader(h, "x-codex-secondary-used-percent");
57
+ if (secondary)
58
+ snapshot.secondary = secondary;
59
+ return snapshot;
60
+ }
61
+ export function isCompleteQuotaHeaderFrame(h) {
62
+ const states = QUOTA_USED_PERCENT_HEADERS.map((name) => usedPercentHeaderState(h, name));
63
+ return states.includes("valid") && !states.includes("invalid");
64
+ }
65
+ function windowFromWs(w) {
66
+ if (!w)
67
+ return;
68
+ if (!Number.isFinite(w.used_percent) || w.used_percent < 0 || w.used_percent > 100)
69
+ return;
70
+ const windowMinutes = positiveFinite(w.window_minutes), resetsAt = toResetIso(w.reset_at);
71
+ if (w.used_percent === 0 && windowMinutes === void 0 && !resetsAt)
72
+ return;
73
+ return {
74
+ usedPercent: w.used_percent,
75
+ remainingPercent: 100 - w.used_percent,
76
+ resetsAt,
77
+ checkedAt: Date.now(),
78
+ ...windowMinutes !== void 0 ? { windowMinutes } : {}
79
+ };
80
+ }
81
+ export function normalizeWsFrame(event) {
82
+ const snapshot = {}, primary = windowFromWs(event.rate_limits?.primary);
83
+ if (primary)
84
+ snapshot.primary = primary;
85
+ const secondary = windowFromWs(event.rate_limits?.secondary);
86
+ if (secondary)
87
+ snapshot.secondary = secondary;
88
+ return snapshot;
89
+ }
90
+ function nonNegativeNumberish(value) {
91
+ if (value == null || value === "")
92
+ return;
93
+ const parsed = Number(value);
94
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
95
+ }
96
+ function windowFromWham(w) {
97
+ if (!w)
98
+ return;
99
+ if (!Number.isFinite(w.used_percent) || w.used_percent < 0 || w.used_percent > 100)
100
+ return;
101
+ const windowSeconds = positiveFinite(w.limit_window_seconds), resetsAt = toResetIso(w.reset_at);
102
+ if (w.used_percent === 0 && windowSeconds === void 0 && !resetsAt)
103
+ return;
104
+ return {
105
+ usedPercent: w.used_percent,
106
+ remainingPercent: 100 - w.used_percent,
107
+ resetsAt,
108
+ checkedAt: Date.now(),
109
+ ...windowSeconds !== void 0 ? { windowMinutes: windowSeconds / 60 } : {}
110
+ };
111
+ }
112
+ export function normalizeWham(json) {
113
+ const snapshot = {}, primary = windowFromWham(json.rate_limit?.primary_window);
114
+ if (primary)
115
+ snapshot.primary = primary;
116
+ const secondary = windowFromWham(json.rate_limit?.secondary_window);
117
+ if (secondary)
118
+ snapshot.secondary = secondary;
119
+ const resetCreditsAvailable = nonNegativeFinite(json.rate_limit_reset_credits?.available_count);
120
+ if (resetCreditsAvailable !== void 0)
121
+ snapshot.resetCreditsAvailable = resetCreditsAvailable;
122
+ const resetCreditsApplicable = nonNegativeFinite(json.rate_limit_reset_credits?.applicable_available_count);
123
+ if (resetCreditsApplicable !== void 0)
124
+ snapshot.resetCreditsApplicable = resetCreditsApplicable;
125
+ const individualLimit = json.spend_control?.individual_limit;
126
+ if (json.spend_control === null || individualLimit === null)
127
+ snapshot.spendControlCleared = !0;
128
+ if (individualLimit && typeof json.spend_control?.reached === "boolean") {
129
+ const limit = nonNegativeNumberish(individualLimit.limit), used = nonNegativeNumberish(individualLimit.used), remaining = nonNegativeNumberish(individualLimit.remaining), usedPercent = nonNegativeNumberish(individualLimit.used_percent), remainingPercent = nonNegativeNumberish(individualLimit.remaining_percent);
130
+ if (limit !== void 0 && used !== void 0 && remaining !== void 0 && usedPercent !== void 0 && remainingPercent !== void 0)
131
+ snapshot.spendControl = {
132
+ limit,
133
+ used,
134
+ remaining,
135
+ usedPercent,
136
+ remainingPercent,
137
+ resetsAt: toResetIso(individualLimit.reset_at),
138
+ unit: individualLimit.unit,
139
+ source: individualLimit.source,
140
+ reached: json.spend_control.reached
141
+ };
142
+ }
143
+ const credits = json.credits;
144
+ if (credits && typeof credits.has_credits === "boolean" && typeof credits.unlimited === "boolean" && typeof credits.overage_limit_reached === "boolean") {
145
+ const balance = nonNegativeNumberish(credits.balance);
146
+ snapshot.credits = {
147
+ hasCredits: credits.has_credits,
148
+ unlimited: credits.unlimited,
149
+ overageLimitReached: credits.overage_limit_reached,
150
+ ...balance !== void 0 ? { balance } : {}
151
+ };
152
+ }
153
+ return snapshot;
154
+ }