@cortexkit/opencode-openai-auth 1.3.0 → 1.4.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.
- package/dist/bin.js +8 -8
- package/dist/{codex-request-B9D0e97Q.d.ts → codex-request-hw0XcldE.d.ts} +27 -27
- package/dist/index-1eq2s1cp.js +2 -0
- package/dist/{index-m3ctrabv.js → index-1tdnrt68.js} +1 -1
- package/dist/{index-8x1aj7q3.js → index-5230x6s8.js} +2 -2
- package/dist/{index-gn2b097f.js → index-ag04nyv7.js} +7 -7
- package/dist/index-brbqqer5.js +90 -0
- package/dist/{index-xwvze96g.js → index-d98f1xdp.js} +1 -1
- package/dist/index-jcyrm58f.js +65 -0
- package/dist/{index-2pgkrebq.js → index-rsvnabbe.js} +2 -2
- package/dist/{index-ftkhhr6d.js → index-s88n2bjn.js} +1 -1
- package/dist/{index-bc89akak.js → index-t5m0dhmq.js} +1 -1
- package/dist/index-vy729a0b.js +69 -0
- package/dist/index-wgwze391.js +2 -0
- package/dist/{index-tddr876f.js → index-yhw3hnsh.js} +2 -2
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1 -1
- package/dist/{model-BdITTmeb.d.ts → model-DphbMQce.d.ts} +1 -1
- package/dist/{port-file-DbHIVC9a.d.ts → port-file-Cx-wwe5Q.d.ts} +1 -1
- package/dist/{protocol-MysYPAho.d.ts → protocol-CDqdjh74.d.ts} +1 -1
- package/dist/rpc/port-file.d.ts +1 -1
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +1 -1
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +3 -3
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/{sidebar-state-CVSok7zv.d.ts → sidebar-state-pTd9gg9W.d.ts} +55 -7
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/{token-z6qq7a1d.js → token-0z5g59p8.js} +1 -1
- package/dist/token-util-jf9g6stk.js +1 -0
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +30 -3
- package/dist/v2/server.d.ts +2 -2
- package/dist/v2/server.js +2 -2
- package/package.json +4 -4
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +50 -5
- package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +70 -1
- package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +88 -61
- package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
- package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +100 -43
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +3 -1
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +7 -9
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +2 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +17 -3
- package/src/tui-compiled/raw/shared/81dddd203f1b92a3-quota-writer.js +253 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +31 -6
- package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +116 -12
- package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +15 -3
- package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +64 -2
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +2 -2
- package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +5 -4
- package/src/tui-compiled/raw/shared/dc9290c2a7552ba6-watch.js +2 -1
- package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +1 -0
- package/src/tui-compiled/raw/sidebar-state.js +32 -3
- package/src/tui-compiled/raw/tui/v2-dialog.js +2 -1
- package/src/tui-compiled/raw/tui.tsx +170 -26
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +50 -5
- package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +70 -1
- package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +88 -61
- package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
- package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +100 -43
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +3 -1
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +7 -9
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +2 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +17 -3
- package/src/tui-compiled/runtime/shared/81dddd203f1b92a3-quota-writer.js +253 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +31 -6
- package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +116 -12
- package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +15 -3
- package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +64 -2
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +2 -2
- package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +5 -4
- package/src/tui-compiled/runtime/shared/dc9290c2a7552ba6-watch.js +2 -1
- package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +1 -0
- package/src/tui-compiled/runtime/sidebar-state.js +32 -3
- package/src/tui-compiled/runtime/tui/v2-dialog.js +2 -1
- package/src/tui-compiled/runtime/tui.js +350 -219
- package/dist/index-1mbgsxsr.js +0 -2
- package/dist/index-4v2fs42x.js +0 -65
- package/dist/index-ex6nx3mq.js +0 -2
- package/dist/index-psjyexj3.js +0 -69
- package/dist/index-tn8x5tz8.js +0 -90
- package/dist/token-util-cf0nyh7n.js +0 -1
|
@@ -4,6 +4,7 @@ import { ClaustrumUnreachableError, classifyVaultFailure, } from "./94f53cfd572b
|
|
|
4
4
|
import { readClaustrumEnrollmentToken } from "./b9588ad4725c1d10-enrollment.js";
|
|
5
5
|
import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
|
|
6
6
|
import { CUSTODY_PLACEHOLDER_PREFIX } from "./d9d2892a4c371301-host-slot.js";
|
|
7
|
+
import { ScopedRosterWatch, } from "./dc9290c2a7552ba6-watch.js";
|
|
7
8
|
/** Only a new version of the same account may replace an in-flight 401. */
|
|
8
9
|
export function isScopedCredentialRotation(served, current) {
|
|
9
10
|
return (current !== undefined &&
|
|
@@ -132,6 +133,9 @@ export class ClaustrumScopedCustody {
|
|
|
132
133
|
#lastProbeAt = -Infinity;
|
|
133
134
|
#probeDelay = 2_000;
|
|
134
135
|
#closed = false;
|
|
136
|
+
/** The view of the last successful `discover()`, for `watchRoster`. */
|
|
137
|
+
#discoveredView;
|
|
138
|
+
#watches = new Set();
|
|
135
139
|
constructor(options) {
|
|
136
140
|
const tokenPath = options.tokenPath;
|
|
137
141
|
if (options.readToken) {
|
|
@@ -197,7 +201,9 @@ export class ClaustrumScopedCustody {
|
|
|
197
201
|
this.#probing = true;
|
|
198
202
|
// Both triggers share list_scoped discovery, including its receipt-drop rules.
|
|
199
203
|
// Sends never await discovery or let idle backoff delay the next send probe.
|
|
200
|
-
|
|
204
|
+
// A probe commits no roster, so it is not the host's discovery and does
|
|
205
|
+
// not move the view a roster watch compares notices against.
|
|
206
|
+
void this.#list()
|
|
201
207
|
.catch(() => {
|
|
202
208
|
if (idle && this.#outage)
|
|
203
209
|
this.#probeDelay = Math.min(this.#probeDelay * 2, 30_000);
|
|
@@ -271,6 +277,11 @@ export class ClaustrumScopedCustody {
|
|
|
271
277
|
* skipped records: they simply are not this consumer's.
|
|
272
278
|
*/
|
|
273
279
|
async discover(signal) {
|
|
280
|
+
const inventory = await this.#list(signal);
|
|
281
|
+
this.#discoveredView = inventory.view;
|
|
282
|
+
return inventory;
|
|
283
|
+
}
|
|
284
|
+
async #list(signal) {
|
|
274
285
|
let inventory;
|
|
275
286
|
let token;
|
|
276
287
|
try {
|
|
@@ -567,6 +578,61 @@ export class ClaustrumScopedCustody {
|
|
|
567
578
|
throw error;
|
|
568
579
|
}
|
|
569
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Watch this consumer's visible vault accounts instead of listing them on a
|
|
583
|
+
* short timer. Opens one `watchScoped` on this custody's client and calls
|
|
584
|
+
* `onView` when the host must run its discovery (see
|
|
585
|
+
* `ClaustrumRosterWatchOptions`). A claustrum-client without `watchScoped`
|
|
586
|
+
* (before 0.12.0) or a vault that answers `unknown_method` reports
|
|
587
|
+
* `fallback` and opens nothing. A watch that closes before any notice was
|
|
588
|
+
* never accepted: the client reads once to learn why, and the watch reports
|
|
589
|
+
* `fallback` and tries one new watch after `WATCH_RETRY_MS`, never in a
|
|
590
|
+
* loop. A watch that closes after notices is re-opened after
|
|
591
|
+
* `RESUBSCRIBE_BACKOFF_MS`, doubling per close in a row. A lost transport
|
|
592
|
+
* starts this custody's outage, exactly as an unreachable call does, and
|
|
593
|
+
* the first notice of a watch ends it. Timers are unreferenced; `close()`
|
|
594
|
+
* on the returned watch or on this custody stops it.
|
|
595
|
+
*
|
|
596
|
+
* The watch keeps no history, so the host still lists on a slow timer while
|
|
597
|
+
* the mode is `watching`: every `SAFETY_POLL_MS` (five minutes). In any
|
|
598
|
+
* other mode the host keeps its usual list timer.
|
|
599
|
+
*/
|
|
600
|
+
watchRoster(options) {
|
|
601
|
+
this.#check();
|
|
602
|
+
const watch = new ScopedRosterWatch({
|
|
603
|
+
client: this.#client,
|
|
604
|
+
enrollmentToken: () => this.#token(),
|
|
605
|
+
view: options.view ?? (() => this.#discoveredView),
|
|
606
|
+
discover: async (view) => {
|
|
607
|
+
await options.onView(view);
|
|
608
|
+
},
|
|
609
|
+
onRefused: (error) => {
|
|
610
|
+
// Apply a refused watch as discover() applies a refused account
|
|
611
|
+
// listing: every kept receipt is dropped.
|
|
612
|
+
this.listFailed(error);
|
|
613
|
+
options.onRefused?.(error);
|
|
614
|
+
},
|
|
615
|
+
onTransportLost: () => this.transportLost(),
|
|
616
|
+
onReachable: () => this.reachable(),
|
|
617
|
+
onError: options.onError,
|
|
618
|
+
onMode: options.onMode,
|
|
619
|
+
logger: this.#logger,
|
|
620
|
+
now: this.#now,
|
|
621
|
+
setTimeoutImpl: this.#setTimeout,
|
|
622
|
+
clearTimeoutImpl: this.#clearTimeout,
|
|
623
|
+
});
|
|
624
|
+
this.#watches.add(watch);
|
|
625
|
+
watch.start();
|
|
626
|
+
return {
|
|
627
|
+
get mode() {
|
|
628
|
+
return watch.mode;
|
|
629
|
+
},
|
|
630
|
+
close: () => {
|
|
631
|
+
this.#watches.delete(watch);
|
|
632
|
+
watch.stop();
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
570
636
|
close() {
|
|
571
637
|
if (this.#closed)
|
|
572
638
|
return;
|
|
@@ -574,6 +640,9 @@ export class ClaustrumScopedCustody {
|
|
|
574
640
|
if (this.#probeTimer)
|
|
575
641
|
this.#clearTimeout(this.#probeTimer);
|
|
576
642
|
this.#probeTimer = undefined;
|
|
643
|
+
for (const watch of this.#watches)
|
|
644
|
+
watch.stop();
|
|
645
|
+
this.#watches.clear();
|
|
577
646
|
this.#kept.clear();
|
|
578
647
|
this.#client.close();
|
|
579
648
|
}
|
|
@@ -3,9 +3,10 @@ import { combineAbortSignals } from "./6371b24a0573d79e-any-signal.js";
|
|
|
3
3
|
import { ClaustrumScopedCustody, decideScopedRetryAfter401, } from "./0f77ebc3c914c9c6-custody.js";
|
|
4
4
|
import { readClaustrumEnrollmentToken } from "./b9588ad4725c1d10-enrollment.js";
|
|
5
5
|
import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
|
|
6
|
-
import { acquireInsulaQuotaSource } from "./d0f0b6be200311af-insula.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { acquireInsulaQuotaSource, } from "./d0f0b6be200311af-insula.js";
|
|
7
|
+
import { pendingVaultQuota, retainVaultQuotaWriter } from "./81dddd203f1b92a3-quota-writer.js";
|
|
8
|
+
import { acceptVaultRoute, applyVaultQuota, declineVaultRoute, forgetPendingVaultDiscovery, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
9
|
+
import { SAFETY_POLL_MS } from "./dc9290c2a7552ba6-watch.js";
|
|
9
10
|
/**
|
|
10
11
|
* One plugin host's view of its vault accounts. It turns vault credentials
|
|
11
12
|
* into rows for `/routing`, authorizes each physical send with the vault, and
|
|
@@ -19,8 +20,6 @@ export class ClaustrumConsumer {
|
|
|
19
20
|
#shutdown = new AbortController();
|
|
20
21
|
#logger;
|
|
21
22
|
#custody;
|
|
22
|
-
/** The custody's client, kept so `start()` can open its vault watch. */
|
|
23
|
-
#client;
|
|
24
23
|
#connecting;
|
|
25
24
|
#refreshing;
|
|
26
25
|
#roster;
|
|
@@ -34,11 +33,16 @@ export class ClaustrumConsumer {
|
|
|
34
33
|
#insula;
|
|
35
34
|
#vaultWatch;
|
|
36
35
|
#releaseInsulaWatch;
|
|
36
|
+
#insulaSkips = new Map();
|
|
37
37
|
/** When this instance last completed a discovery, by `now`. */
|
|
38
38
|
#discoveredAt;
|
|
39
|
+
#releaseQuotaWriter;
|
|
39
40
|
constructor(options) {
|
|
40
41
|
this.#options = options;
|
|
41
42
|
this.#logger = options.logger ?? createLogger('claustrum');
|
|
43
|
+
this.#releaseQuotaWriter = retainVaultQuotaWriter(options.rosterPath, (publication) => {
|
|
44
|
+
this.#roster = applyVaultQuota(this.#roster, publication).roster;
|
|
45
|
+
});
|
|
42
46
|
}
|
|
43
47
|
#assertOpen(signal) {
|
|
44
48
|
this.#shutdown.signal.throwIfAborted();
|
|
@@ -90,7 +94,6 @@ export class ClaustrumConsumer {
|
|
|
90
94
|
client.close();
|
|
91
95
|
this.#assertOpen();
|
|
92
96
|
}
|
|
93
|
-
this.#client = client;
|
|
94
97
|
this.#custody = new ClaustrumScopedCustody({
|
|
95
98
|
client,
|
|
96
99
|
family: this.#options.family,
|
|
@@ -143,9 +146,8 @@ export class ClaustrumConsumer {
|
|
|
143
146
|
}
|
|
144
147
|
/** Close the watches with the connection they belong to. */
|
|
145
148
|
#stopWatch() {
|
|
146
|
-
this.#vaultWatch?.
|
|
149
|
+
this.#vaultWatch?.close();
|
|
147
150
|
this.#vaultWatch = undefined;
|
|
148
|
-
this.#client = undefined;
|
|
149
151
|
this.#releaseInsulaWatch?.();
|
|
150
152
|
this.#releaseInsulaWatch = undefined;
|
|
151
153
|
}
|
|
@@ -179,11 +181,9 @@ export class ClaustrumConsumer {
|
|
|
179
181
|
let discovered;
|
|
180
182
|
const roster = await refreshVaultRoster({
|
|
181
183
|
path: this.#options.rosterPath,
|
|
182
|
-
custody
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return discovered;
|
|
186
|
-
},
|
|
184
|
+
custody,
|
|
185
|
+
onInventory: (inventory) => {
|
|
186
|
+
discovered = inventory;
|
|
187
187
|
},
|
|
188
188
|
isActive: () => this.#active(),
|
|
189
189
|
signal: this.#shutdown.signal,
|
|
@@ -196,7 +196,7 @@ export class ClaustrumConsumer {
|
|
|
196
196
|
});
|
|
197
197
|
this.#assertOpen();
|
|
198
198
|
const changed = roster?.view !== this.#roster?.view;
|
|
199
|
-
this.#roster = roster;
|
|
199
|
+
this.#roster = pendingVaultQuota(this.#options.rosterPath, roster);
|
|
200
200
|
custody.retain(roster?.rows ?? []);
|
|
201
201
|
// Each discovery replaces the previous value, including with unknown. A
|
|
202
202
|
// refresh that served a peer's roster discovered nothing and leaves it;
|
|
@@ -271,42 +271,30 @@ export class ClaustrumConsumer {
|
|
|
271
271
|
* reached in vault mode: a host in local mode never connects.
|
|
272
272
|
*/
|
|
273
273
|
#openWatch() {
|
|
274
|
-
const
|
|
275
|
-
if (this.#shutdown.signal.aborted || !
|
|
274
|
+
const custody = this.#custody;
|
|
275
|
+
if (this.#shutdown.signal.aborted || !custody)
|
|
276
276
|
return;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
277
|
+
// The same entry point a host without a consumer uses, so both apply
|
|
278
|
+
// notices, refusals and outages alike.
|
|
279
|
+
this.#vaultWatch ??= custody.watchRoster({
|
|
280
|
+
view: () => this.#roster?.view,
|
|
281
|
+
// The existing discovery, so a watched change commits the roster and
|
|
282
|
+
// re-applies it to kept receipts exactly as a poll does. A refresh
|
|
283
|
+
// already in flight may have listed before the change was announced,
|
|
284
|
+
// so wait for it and list again.
|
|
285
|
+
onView: async () => {
|
|
286
|
+
while (this.#refreshing)
|
|
287
|
+
await this.#refreshing.catch(() => { });
|
|
288
|
+
await this.refresh();
|
|
289
|
+
},
|
|
290
|
+
// Custody has dropped kept receipts, as a poll meeting the same
|
|
291
|
+
// refusal from list_scoped would.
|
|
292
|
+
onRefused: (error) => this.#options.onError?.(error),
|
|
293
|
+
onError: (error) => {
|
|
294
|
+
if (!this.#shutdown.signal.aborted)
|
|
294
295
|
this.#options.onError?.(error);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
onReachable: () => this.#custody?.reachable(),
|
|
298
|
-
onError: (error) => {
|
|
299
|
-
if (!this.#shutdown.signal.aborted)
|
|
300
|
-
this.#options.onError?.(error);
|
|
301
|
-
},
|
|
302
|
-
logger: this.#logger,
|
|
303
|
-
now: this.#options.now,
|
|
304
|
-
setTimeoutImpl: this.#options.setTimeoutImpl,
|
|
305
|
-
clearTimeoutImpl: this.#options.clearTimeoutImpl,
|
|
306
|
-
});
|
|
307
|
-
this.#vaultWatch = watch;
|
|
308
|
-
watch.start();
|
|
309
|
-
}
|
|
296
|
+
},
|
|
297
|
+
});
|
|
310
298
|
if (this.#options.insula && this.#insula && !this.#releaseInsulaWatch)
|
|
311
299
|
this.#releaseInsulaWatch = this.#insula.source.watch({
|
|
312
300
|
provider: this.#options.insula.provider,
|
|
@@ -364,7 +352,7 @@ export class ClaustrumConsumer {
|
|
|
364
352
|
return undefined;
|
|
365
353
|
try {
|
|
366
354
|
await readClaustrumEnrollmentToken(this.#options.tokenPath);
|
|
367
|
-
const roster = await readVaultRoster(this.#options.rosterPath);
|
|
355
|
+
const roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
368
356
|
const row = roster?.rows.find((entry) => entry.routeId === routeId);
|
|
369
357
|
return this.#normalizeInsulaQuota(row?.accountIdentity);
|
|
370
358
|
}
|
|
@@ -372,6 +360,44 @@ export class ClaustrumConsumer {
|
|
|
372
360
|
return undefined;
|
|
373
361
|
}
|
|
374
362
|
}
|
|
363
|
+
/** Cache-only background lookup, preserving the reason a route has no reading. */
|
|
364
|
+
async lookupInsulaQuotaForRoute(routeId) {
|
|
365
|
+
const options = this.#options.insula;
|
|
366
|
+
if (!options || this.#shutdown.signal.aborted || !(await this.#active()))
|
|
367
|
+
return { miss: 'unreachable' };
|
|
368
|
+
try {
|
|
369
|
+
await readClaustrumEnrollmentToken(this.#options.tokenPath);
|
|
370
|
+
const roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
371
|
+
const account = roster?.rows.find((row) => row.routeId === routeId)?.accountIdentity;
|
|
372
|
+
if (!account || !this.#insula)
|
|
373
|
+
return { miss: 'unreachable' };
|
|
374
|
+
const result = this.#insula.source.lookup(options.provider, account, (this.#options.now ?? Date.now)());
|
|
375
|
+
const reading = result.reading &&
|
|
376
|
+
options.normalize(result.reading.entry, result.reading.context);
|
|
377
|
+
if (result.miss)
|
|
378
|
+
return { miss: result.miss, ...(reading !== undefined && { reading }) };
|
|
379
|
+
// Refusing an echo or unsupported window leaves the plugin's poll in charge.
|
|
380
|
+
return reading === undefined ? { miss: 'unreachable' } : { reading };
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return { miss: 'unreachable' };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/** Skip the fallback poll while insula is cold or its upstream fetch failed. */
|
|
387
|
+
async pollInsulaQuotaForRoute(routeId, fallback) {
|
|
388
|
+
const result = await this.lookupInsulaQuotaForRoute(routeId);
|
|
389
|
+
if (result.miss === 'cold' || result.miss === 'upstream-failed') {
|
|
390
|
+
if (this.#insulaSkips.get(routeId) !== result.miss) {
|
|
391
|
+
this.#insulaSkips.set(routeId, result.miss);
|
|
392
|
+
this.#logger.warn('skipped insula provider poll', {
|
|
393
|
+
reason: result.miss,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
this.#insulaSkips.delete(routeId);
|
|
399
|
+
return result.miss ? fallback() : result.reading;
|
|
400
|
+
}
|
|
375
401
|
#normalizeInsulaQuota(accountIdentity) {
|
|
376
402
|
if (!this.#options.insula || !accountIdentity)
|
|
377
403
|
return undefined;
|
|
@@ -396,7 +422,7 @@ export class ClaustrumConsumer {
|
|
|
396
422
|
this.#insula = undefined;
|
|
397
423
|
throw new ClaustrumConsumerError('not-active', 'Claustrum scoped custody is not active');
|
|
398
424
|
}
|
|
399
|
-
const current = await readVaultRoster(this.#options.rosterPath);
|
|
425
|
+
const current = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
400
426
|
this.#assertOpen(signal);
|
|
401
427
|
const observed = roster.rows.find((row) => row.routeId === routeId);
|
|
402
428
|
const configured = current?.rows.find((row) => row.routeId === routeId);
|
|
@@ -488,13 +514,13 @@ export class ClaustrumConsumer {
|
|
|
488
514
|
async decline(routeId) {
|
|
489
515
|
this.#assertOpen();
|
|
490
516
|
await declineVaultRoute(this.#options.rosterPath, routeId);
|
|
491
|
-
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
517
|
+
this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
492
518
|
this.#custody?.retain(this.#roster?.rows ?? []);
|
|
493
519
|
}
|
|
494
520
|
async accept(routeId) {
|
|
495
521
|
this.#assertOpen();
|
|
496
522
|
await acceptVaultRoute(this.#options.rosterPath, routeId);
|
|
497
|
-
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
523
|
+
this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
498
524
|
}
|
|
499
525
|
/**
|
|
500
526
|
* Store a quota or profile observation for a vault route. The receipt the
|
|
@@ -504,7 +530,7 @@ export class ClaustrumConsumer {
|
|
|
504
530
|
*/
|
|
505
531
|
async recordQuota(routeId, observation, attempt) {
|
|
506
532
|
this.#assertOpen();
|
|
507
|
-
|
|
533
|
+
return recordVaultQuota(this.#options.rosterPath, {
|
|
508
534
|
routeId,
|
|
509
535
|
observation,
|
|
510
536
|
credentialId: attempt.credentialId,
|
|
@@ -516,27 +542,21 @@ export class ClaustrumConsumer {
|
|
|
516
542
|
expectedAccountIdentity: attempt.expectedAccountIdentity,
|
|
517
543
|
}),
|
|
518
544
|
});
|
|
519
|
-
if (kept)
|
|
520
|
-
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
521
|
-
return kept;
|
|
522
545
|
}
|
|
523
546
|
/**
|
|
524
547
|
* Store a background observation that belongs to an account rather than to
|
|
525
548
|
* a served credential (an insula reading from `cachedInsulaQuotaForRoute`).
|
|
526
549
|
* It lands only while the route is still bound to that known account, so no
|
|
527
550
|
* credential fetch is needed and a replaced account's reading is dropped.
|
|
528
|
-
*
|
|
551
|
+
* Memory updates immediately; durable publication is detached and coalesced.
|
|
529
552
|
*/
|
|
530
553
|
async recordAccountQuota(routeId, accountIdentity, observation) {
|
|
531
554
|
this.#assertOpen();
|
|
532
|
-
|
|
555
|
+
return recordVaultAccountQuota(this.#options.rosterPath, {
|
|
533
556
|
routeId,
|
|
534
557
|
accountIdentity,
|
|
535
558
|
observation,
|
|
536
559
|
});
|
|
537
|
-
if (kept)
|
|
538
|
-
this.#roster = await readVaultRoster(this.#options.rosterPath);
|
|
539
|
-
return kept;
|
|
540
560
|
}
|
|
541
561
|
close() {
|
|
542
562
|
if (this.#shutdown.signal.aborted)
|
|
@@ -551,5 +571,12 @@ export class ClaustrumConsumer {
|
|
|
551
571
|
this.#insula = undefined;
|
|
552
572
|
this.#roster = undefined;
|
|
553
573
|
this.#grantReach = undefined;
|
|
574
|
+
void this.#releaseQuotaWriter();
|
|
575
|
+
forgetPendingVaultDiscovery(this.#options.rosterPath);
|
|
576
|
+
}
|
|
577
|
+
/** Close immediately; the last consumer also waits for the writer's bounded drain. */
|
|
578
|
+
async dispose() {
|
|
579
|
+
this.close();
|
|
580
|
+
await this.#releaseQuotaWriter();
|
|
554
581
|
}
|
|
555
582
|
}
|
|
@@ -56,8 +56,33 @@ export function normalizeQuotaHeaders(h) {
|
|
|
56
56
|
const secondary = windowFromHeader(h, "x-codex-secondary-used-percent");
|
|
57
57
|
if (secondary)
|
|
58
58
|
snapshot.secondary = secondary;
|
|
59
|
+
const credits = creditsFromHeaders(h);
|
|
60
|
+
if (credits)
|
|
61
|
+
snapshot.credits = credits;
|
|
59
62
|
return snapshot;
|
|
60
63
|
}
|
|
64
|
+
function headerBool(h, name) {
|
|
65
|
+
const raw = h.get(name)?.trim();
|
|
66
|
+
if (raw === void 0)
|
|
67
|
+
return;
|
|
68
|
+
if (raw.toLowerCase() === "true" || raw === "1")
|
|
69
|
+
return !0;
|
|
70
|
+
if (raw.toLowerCase() === "false" || raw === "0")
|
|
71
|
+
return !1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
function creditsFromHeaders(h) {
|
|
75
|
+
const hasCredits = headerBool(h, "x-codex-credits-has-credits"), unlimited = headerBool(h, "x-codex-credits-unlimited");
|
|
76
|
+
if (hasCredits === void 0 || unlimited === void 0)
|
|
77
|
+
return;
|
|
78
|
+
const balance = nonNegativeNumberish(h.get("x-codex-credits-balance")?.trim());
|
|
79
|
+
return {
|
|
80
|
+
hasCredits,
|
|
81
|
+
unlimited,
|
|
82
|
+
...balance !== void 0 ? { balance } : {},
|
|
83
|
+
checkedAt: Date.now()
|
|
84
|
+
};
|
|
85
|
+
}
|
|
61
86
|
export function isCompleteQuotaHeaderFrame(h) {
|
|
62
87
|
const states = QUOTA_USED_PERCENT_HEADERS.map((name) => usedPercentHeaderState(h, name));
|
|
63
88
|
return states.includes("valid") && !states.includes("invalid");
|
|
@@ -85,8 +110,26 @@ export function normalizeWsFrame(event) {
|
|
|
85
110
|
const secondary = windowFromWs(event.rate_limits?.secondary);
|
|
86
111
|
if (secondary)
|
|
87
112
|
snapshot.secondary = secondary;
|
|
113
|
+
const credits = creditsFromWs(event.credits);
|
|
114
|
+
if (credits)
|
|
115
|
+
snapshot.credits = credits;
|
|
88
116
|
return snapshot;
|
|
89
117
|
}
|
|
118
|
+
function creditsFromWs(value) {
|
|
119
|
+
if (value === null || typeof value !== "object")
|
|
120
|
+
return;
|
|
121
|
+
const credits = value;
|
|
122
|
+
if (typeof credits.has_credits !== "boolean" || typeof credits.unlimited !== "boolean")
|
|
123
|
+
return;
|
|
124
|
+
const balance = nonNegativeNumberish(typeof credits.balance === "string" ? credits.balance.trim() : credits.balance);
|
|
125
|
+
return {
|
|
126
|
+
hasCredits: credits.has_credits,
|
|
127
|
+
unlimited: credits.unlimited,
|
|
128
|
+
...balance !== void 0 ? { balance } : {},
|
|
129
|
+
...typeof credits.overage_limit_reached === "boolean" ? { overageLimitReached: credits.overage_limit_reached } : {},
|
|
130
|
+
checkedAt: Date.now()
|
|
131
|
+
};
|
|
132
|
+
}
|
|
90
133
|
function nonNegativeNumberish(value) {
|
|
91
134
|
if (value == null || value === "")
|
|
92
135
|
return;
|
|
@@ -147,7 +190,8 @@ export function normalizeWham(json) {
|
|
|
147
190
|
hasCredits: credits.has_credits,
|
|
148
191
|
unlimited: credits.unlimited,
|
|
149
192
|
overageLimitReached: credits.overage_limit_reached,
|
|
150
|
-
...balance !== void 0 ? { balance } : {}
|
|
193
|
+
...balance !== void 0 ? { balance } : {},
|
|
194
|
+
checkedAt: Date.now()
|
|
151
195
|
};
|
|
152
196
|
}
|
|
153
197
|
return snapshot;
|