@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.
Files changed (85) hide show
  1. package/dist/bin.js +8 -8
  2. package/dist/{codex-request-B9D0e97Q.d.ts → codex-request-hw0XcldE.d.ts} +27 -27
  3. package/dist/index-1eq2s1cp.js +2 -0
  4. package/dist/{index-m3ctrabv.js → index-1tdnrt68.js} +1 -1
  5. package/dist/{index-8x1aj7q3.js → index-5230x6s8.js} +2 -2
  6. package/dist/{index-gn2b097f.js → index-ag04nyv7.js} +7 -7
  7. package/dist/index-brbqqer5.js +90 -0
  8. package/dist/{index-xwvze96g.js → index-d98f1xdp.js} +1 -1
  9. package/dist/index-jcyrm58f.js +65 -0
  10. package/dist/{index-2pgkrebq.js → index-rsvnabbe.js} +2 -2
  11. package/dist/{index-ftkhhr6d.js → index-s88n2bjn.js} +1 -1
  12. package/dist/{index-bc89akak.js → index-t5m0dhmq.js} +1 -1
  13. package/dist/index-vy729a0b.js +69 -0
  14. package/dist/index-wgwze391.js +2 -0
  15. package/dist/{index-tddr876f.js → index-yhw3hnsh.js} +2 -2
  16. package/dist/index.d.ts +11 -3
  17. package/dist/index.js +1 -1
  18. package/dist/{model-BdITTmeb.d.ts → model-DphbMQce.d.ts} +1 -1
  19. package/dist/{port-file-DbHIVC9a.d.ts → port-file-Cx-wwe5Q.d.ts} +1 -1
  20. package/dist/{protocol-MysYPAho.d.ts → protocol-CDqdjh74.d.ts} +1 -1
  21. package/dist/rpc/port-file.d.ts +1 -1
  22. package/dist/rpc/port-file.js +1 -1
  23. package/dist/rpc/protocol.d.ts +1 -1
  24. package/dist/rpc/protocol.js +1 -1
  25. package/dist/rpc/rpc-client.d.ts +3 -3
  26. package/dist/rpc/rpc-client.js +1 -1
  27. package/dist/rpc/rpc-dir.js +1 -1
  28. package/dist/{sidebar-state-CVSok7zv.d.ts → sidebar-state-pTd9gg9W.d.ts} +55 -7
  29. package/dist/sidebar-state.d.ts +2 -2
  30. package/dist/sidebar-state.js +1 -1
  31. package/dist/{token-z6qq7a1d.js → token-0z5g59p8.js} +1 -1
  32. package/dist/token-util-jf9g6stk.js +1 -0
  33. package/dist/tui-preferences.js +1 -1
  34. package/dist/tui.d.ts +30 -3
  35. package/dist/v2/server.d.ts +2 -2
  36. package/dist/v2/server.js +2 -2
  37. package/package.json +4 -4
  38. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +50 -5
  39. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +70 -1
  40. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +88 -61
  41. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
  42. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +100 -43
  43. package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +3 -1
  44. package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +7 -9
  45. package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +2 -0
  46. package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +17 -3
  47. package/src/tui-compiled/raw/shared/81dddd203f1b92a3-quota-writer.js +253 -0
  48. package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +31 -6
  49. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +116 -12
  50. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +15 -3
  51. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +64 -2
  52. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +2 -2
  53. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +5 -4
  54. package/src/tui-compiled/raw/shared/dc9290c2a7552ba6-watch.js +2 -1
  55. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +1 -0
  56. package/src/tui-compiled/raw/sidebar-state.js +32 -3
  57. package/src/tui-compiled/raw/tui/v2-dialog.js +2 -1
  58. package/src/tui-compiled/raw/tui.tsx +170 -26
  59. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +50 -5
  60. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +70 -1
  61. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +88 -61
  62. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
  63. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +100 -43
  64. package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +3 -1
  65. package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +7 -9
  66. package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +2 -0
  67. package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +17 -3
  68. package/src/tui-compiled/runtime/shared/81dddd203f1b92a3-quota-writer.js +253 -0
  69. package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +31 -6
  70. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +116 -12
  71. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +15 -3
  72. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +64 -2
  73. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +2 -2
  74. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +5 -4
  75. package/src/tui-compiled/runtime/shared/dc9290c2a7552ba6-watch.js +2 -1
  76. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +1 -0
  77. package/src/tui-compiled/runtime/sidebar-state.js +32 -3
  78. package/src/tui-compiled/runtime/tui/v2-dialog.js +2 -1
  79. package/src/tui-compiled/runtime/tui.js +350 -219
  80. package/dist/index-1mbgsxsr.js +0 -2
  81. package/dist/index-4v2fs42x.js +0 -65
  82. package/dist/index-ex6nx3mq.js +0 -2
  83. package/dist/index-psjyexj3.js +0 -69
  84. package/dist/index-tn8x5tz8.js +0 -90
  85. package/dist/token-util-cf0nyh7n.js +0 -1
@@ -1,8 +1,13 @@
1
- import { projectQuota } from "./d5a8f1061ee2f114-index.js";
1
+ import {
2
+ budgetExhaustedResetAt,
3
+ isQuotaCredits,
4
+ projectQuota
5
+ } from "./d5a8f1061ee2f114-index.js";
2
6
  import {
3
7
  admit,
4
8
  decideStickyBreak,
5
9
  orderForPlacement,
10
+ QUOTA_STALENESS_MS,
6
11
  routeOrdered,
7
12
  routeSticky
8
13
  } from "./3e7d45a77c6ad4d5-index.js";
@@ -65,6 +70,70 @@ export function waitForFirstQuotaReading(input, waitMs = FIRST_QUOTA_READING_WAI
65
70
  function isConfirmedExhaustion(refusal) {
66
71
  return refusal.reason === "exhausted" || refusal.reason === "budget-spent";
67
72
  }
73
+ export function creditsUsable(credits, now) {
74
+ if (!isQuotaCredits(credits))
75
+ return !1;
76
+ const record = credits;
77
+ if (!(now - record.checkedAt <= QUOTA_STALENESS_MS))
78
+ return !1;
79
+ if (record.overageLimitReached === !0)
80
+ return !1;
81
+ if (record.unlimited)
82
+ return !0;
83
+ return record.hasCredits && (record.balance === void 0 || record.balance > 0);
84
+ }
85
+ function creditsTiers(rows, input, admission) {
86
+ const passes = (id) => input.killswitch.get(id) !== !1, free = new Set(admission.admitted.filter((row) => row.lastPath !== !0 && passes(row.id)).map((row) => row.id)), refusals = new Map(admission.refused.map((refusal) => [refusal.id, refusal])), credits = rows.filter((row) => {
87
+ if (row.kind !== "oauth" || !passes(row.id))
88
+ return !1;
89
+ if (refusals.get(row.id)?.reason !== "exhausted")
90
+ return !1;
91
+ const projection = projectQuota(row.quota);
92
+ if (budgetExhaustedResetAt(projection, input.now))
93
+ return !1;
94
+ return creditsUsable(projection.credits, input.now);
95
+ }).map((row) => row.id);
96
+ return { free, credits };
97
+ }
98
+ function admitQuietly(input, rows = input.rows) {
99
+ return admit({
100
+ rows,
101
+ now: input.now,
102
+ rateLimitMarks: input.rateLimitMarks,
103
+ refreshBackoff: input.refreshBackoff,
104
+ requestPull: () => {}
105
+ });
106
+ }
107
+ export function creditsFallbackIds(input) {
108
+ return [...creditsTiers(input.rows, input, admitQuietly(input)).credits];
109
+ }
110
+
111
+ export class CreditsServing {
112
+ #ids = new Set;
113
+ served(input, id, log) {
114
+ const admission = admitQuietly(input), tiers = creditsTiers(input.rows, input, admission), credits = new Set(tiers.credits);
115
+ if (tiers.free.has(id)) {
116
+ this.#ids.clear();
117
+ return;
118
+ }
119
+ for (const marked of [...this.#ids])
120
+ if (!credits.has(marked))
121
+ this.#ids.delete(marked);
122
+ if (!credits.has(id) || this.#ids.has(id))
123
+ return;
124
+ this.#ids.add(id);
125
+ log?.info("serving requests on prepaid credits", {
126
+ accountId: id,
127
+ reason: "free quota used up"
128
+ });
129
+ }
130
+ has(id) {
131
+ return this.#ids.has(id);
132
+ }
133
+ ids() {
134
+ return new Set(this.#ids);
135
+ }
136
+ }
68
137
  export function blockFor(id, admission, killswitch) {
69
138
  if (id === void 0)
70
139
  return { reason: "no-credential" };
@@ -97,11 +166,13 @@ export function planOrdered(input) {
97
166
  refreshBackoff: input.refreshBackoff,
98
167
  now: input.now,
99
168
  requestPull: input.requestPull
100
- }), ids = orderForPlacement(input.rows.map((row) => row.id), placement, mainId), confirmed = new Set(route.admission.refused.filter((refusal) => isConfirmedExhaustion(refusal) && input.killswitch.get(refusal.id) !== !1).map((refusal) => refusal.id)), lastPath = ids.filter((id) => confirmed.has(id));
169
+ }), ids = orderForPlacement(input.rows.map((row) => row.id), placement, mainId), confirmed = new Set(route.admission.refused.filter((refusal) => isConfirmedExhaustion(refusal) && input.killswitch.get(refusal.id) !== !1).map((refusal) => refusal.id)), lastPath = ids.filter((id) => confirmed.has(id)), tiers = creditsTiers(input.rows, input, route.admission), free = route.order.filter((id) => tiers.free.has(id)), credits = ids.filter((id) => tiers.credits.includes(id));
101
170
  if (!replayable) {
102
- const hasMain = ids.includes(mainId), target = hasMain ? mainId : route.order[0] ?? lastPath[0];
171
+ const hasMain = ids.includes(mainId), target = hasMain ? mainId : free[0] ?? credits[0] ?? route.order[0] ?? lastPath[0];
103
172
  if (target !== void 0 && route.order.includes(target))
104
173
  return { kind: "send", order: [target], lastPath: !1 };
174
+ if (target !== void 0 && credits.includes(target))
175
+ return { kind: "send", order: [target], lastPath: !1 };
105
176
  if (target !== void 0 && confirmed.has(target))
106
177
  return { kind: "send", order: [target], lastPath: !0 };
107
178
  return {
@@ -109,6 +180,16 @@ export function planOrdered(input) {
109
180
  block: hasMain ? blockFor(mainId, route.admission, input.killswitch) : routeBlock(ids, route.admission, input.killswitch)
110
181
  };
111
182
  }
183
+ if (free.length > 0)
184
+ return { kind: "send", order: free, lastPath: !1 };
185
+ if (credits.length > 0) {
186
+ const rest = route.order.length > 0 ? route.order : lastPath;
187
+ return {
188
+ kind: "send",
189
+ order: [...credits, ...rest.filter((id) => !credits.includes(id))],
190
+ lastPath: !1
191
+ };
192
+ }
112
193
  if (route.order.length > 0)
113
194
  return { kind: "send", order: route.order, lastPath: !1 };
114
195
  if (lastPath.length > 0)
@@ -160,23 +241,46 @@ function routePoolSticky(input, options, extra) {
160
241
  });
161
242
  }
162
243
  export function selectStickyRow(input, options, exclude, pendingBytes) {
163
- const route = routePoolSticky(input, options, { exclude, pendingBytes });
164
- if (route.outcome !== "dispatch" || route.source === "pin")
165
- return;
166
- return {
244
+ const route = routePoolSticky(input, options, { exclude, pendingBytes }), rows = input.rows.filter((row) => !exclude.has(row.id)), tiers = creditsTiers(rows, input, route.admission), selected = route.outcome === "dispatch" && route.source !== "pin" ? {
167
245
  accountId: route.accountId,
168
246
  source: route.source,
169
247
  ...route.quotaCheckedAt === void 0 ? {} : { quotaCheckedAt: route.quotaCheckedAt }
248
+ } : void 0;
249
+ if (selected && tiers.free.has(selected.accountId))
250
+ return selected;
251
+ const credits = tiers.credits[0];
252
+ if (credits === void 0)
253
+ return selected;
254
+ const checkedAt = projectQuota(rows.find((row) => row.id === credits)?.quota).checkedAt;
255
+ return {
256
+ accountId: credits,
257
+ source: "mode-fallback",
258
+ ...checkedAt === void 0 ? {} : { quotaCheckedAt: checkedAt }
170
259
  };
171
260
  }
172
261
  export function routePinnedRow(input, options, id) {
173
- const killed = input.killswitch.get(id) === !1, route = routePoolSticky(input, options, { pin: id });
262
+ const killed = input.killswitch.get(id) === !1, move = {
263
+ kind: "move",
264
+ reason: killed ? "killswitch" : "exhausted"
265
+ }, route = routePoolSticky(input, options, { pin: id }), tiers = creditsTiers(input.rows, input, route.admission), onCredits = tiers.credits.includes(id), othersOnCredits = tiers.credits.some((other) => other !== id);
174
266
  if (route.outcome === "dispatch") {
175
- if (route.pin.action === "assign")
176
- return { kind: "move", reason: killed ? "killswitch" : "exhausted" };
177
- return route.accountId === id ? { kind: "serve" } : { kind: "detour" };
267
+ const toLastPath = !tiers.free.has(route.accountId);
268
+ if (route.pin.action === "assign") {
269
+ if (toLastPath && onCredits)
270
+ return { kind: "serve" };
271
+ return move;
272
+ }
273
+ if (route.accountId !== id)
274
+ return { kind: "detour" };
275
+ if (toLastPath && othersOnCredits)
276
+ return move;
277
+ return { kind: "serve" };
178
278
  }
179
- const refusal = route.admission.refused.find((entry) => entry.id === id);
279
+ const refusal = route.admission.refused.find((entry) => entry.id === id), confirmed = killed || refusal !== void 0 && isConfirmedExhaustion(refusal);
280
+ if (onCredits)
281
+ return { kind: "serve" };
282
+ if (othersOnCredits)
283
+ return confirmed ? move : { kind: "detour" };
180
284
  if (!killed && refusal && isConfirmedExhaustion(refusal))
181
285
  return { kind: "last-path" };
182
286
  return { kind: "none" };
@@ -42,6 +42,17 @@ function budgetFor(snapshot) {
42
42
  return { kind: "cleared" };
43
43
  return;
44
44
  }
45
+ function prepaidCreditsFor(value, checkedAt) {
46
+ if (!isRecord(value) || typeof value.hasCredits !== "boolean" || typeof value.unlimited !== "boolean")
47
+ return;
48
+ return {
49
+ hasCredits: value.hasCredits,
50
+ unlimited: value.unlimited,
51
+ ...finite(value.balance) && value.balance >= 0 ? { balance: value.balance } : {},
52
+ ...typeof value.overageLimitReached === "boolean" ? { overageLimitReached: value.overageLimitReached } : {},
53
+ checkedAt
54
+ };
55
+ }
45
56
  export function observationFromSnapshot(snapshot, checkedAt, complete) {
46
57
  if (!isRecord(snapshot) || !finite(checkedAt))
47
58
  return;
@@ -53,15 +64,16 @@ export function observationFromSnapshot(snapshot, checkedAt, complete) {
53
64
  else if (complete)
54
65
  coverage.push({ label });
55
66
  }
56
- const budget = budgetFor(snapshot), credits = snapshot.resetCreditsAvailable, counters = typeof credits === "number" && Number.isSafeInteger(credits) && credits >= 0 ? { resetCredits: { value: credits, checkedAt } } : void 0;
57
- if (readings.length === 0 && coverage.length === 0 && !budget && !counters)
67
+ const budget = budgetFor(snapshot), credits = snapshot.resetCreditsAvailable, counters = typeof credits === "number" && Number.isSafeInteger(credits) && credits >= 0 ? { resetCredits: { value: credits, checkedAt } } : void 0, prepaid = prepaidCreditsFor(snapshot.credits, checkedAt);
68
+ if (readings.length === 0 && coverage.length === 0 && !budget && !counters && !prepaid)
58
69
  return;
59
70
  return {
60
71
  checkedAt,
61
72
  ...readings.length > 0 ? { readings } : {},
62
73
  ...coverage.length > 0 ? { coverage } : {},
63
74
  ...budget ? { budget } : {},
64
- ...counters ? { counters } : {}
75
+ ...counters ? { counters } : {},
76
+ ...prepaid ? { credits: prepaid } : {}
65
77
  };
66
78
  }
67
79
  export function windowsFromQuotaMap(map) {
@@ -128,6 +128,9 @@ export function insulaUsageTransport(options) {
128
128
  }
129
129
  const MINUTE = 60_000;
130
130
  const MAX_AGE = 4 * MINUTE;
131
+ // consumer-contract.md:331 refetches a slot after 60 s. Two such intervals
132
+ // bound our willingness to wait, not a claim that insula completed its sweep.
133
+ const COLD_WAIT = 2 * MINUTE;
131
134
  const sources = new Map();
132
135
  // Keep only the minute budget after the last owner closes, so project churn
133
136
  // cannot reset the bound. Expired budgets are pruned on the next tick.
@@ -170,6 +173,9 @@ export class InsulaQuotaSource {
170
173
  transport;
171
174
  key;
172
175
  #entries = [];
176
+ #available = false;
177
+ #completeProviders = new Set();
178
+ #coldSince = new Map();
173
179
  /** Every usage.get, polled or watch-driven, shares this one slot. */
174
180
  #reads = new CoalescedRead(async () => {
175
181
  try {
@@ -178,13 +184,25 @@ export class InsulaQuotaSource {
178
184
  !Array.isArray(reply.result) ||
179
185
  reply.error !== undefined)
180
186
  throw new Error('unavailable insula usage');
181
- // Each response is a snapshot, not a delta. Missing accounts fall back;
182
- // neither pending nor the entry count asserts provider completeness.
187
+ // Each response is a snapshot, not a delta. Only completeProviders
188
+ // asserts completeness; neither pending nor the entry count does.
183
189
  this.#entries = reply.result.filter((entry) => record(entry) && typeof entry.provider === 'string');
190
+ this.#available =
191
+ this.#entries.length === reply.result.length &&
192
+ (reply.completeProviders === undefined ||
193
+ (Array.isArray(reply.completeProviders) &&
194
+ reply.completeProviders.every((value) => typeof value === 'string')));
195
+ this.#completeProviders = new Set(this.#available && Array.isArray(reply.completeProviders)
196
+ ? reply.completeProviders
197
+ : []);
198
+ for (const provider of this.#completeProviders)
199
+ this.#coldSince.delete(provider);
184
200
  }
185
201
  catch {
186
202
  // Refusal is absence, even if the previous reading was still young.
187
203
  this.#entries = [];
204
+ this.#available = false;
205
+ this.#completeProviders.clear();
188
206
  }
189
207
  }, () => { });
190
208
  #reader = new ChangeNoticeReader();
@@ -391,6 +409,50 @@ export class InsulaQuotaSource {
391
409
  }
392
410
  return undefined;
393
411
  }
412
+ /** Cache-only background lookup; current() retains its original miss behavior. */
413
+ lookup(provider, accountId, now) {
414
+ if (!this.#available)
415
+ return { miss: 'unreachable' };
416
+ const entry = this.#entries.find((value) => value.provider === provider &&
417
+ typeof value.account === 'string' &&
418
+ value.account.trim() === accountId);
419
+ // An unlabelled error describes the provider's credential path, not an
420
+ // unrelated labelled account. Never borrow another account's verdict.
421
+ const errorClass = (entry ??
422
+ this.#entries.find((value) => value.provider === provider &&
423
+ value.account === undefined &&
424
+ value.errorClass !== undefined))?.errorClass;
425
+ if (errorClass === 'upstream_failed')
426
+ return { miss: 'upstream-failed' };
427
+ if (errorClass === 'credential_absent' ||
428
+ errorClass === 'credential_unusable' ||
429
+ errorClass === 'credential_rejected')
430
+ return { miss: 'credential' };
431
+ if (errorClass !== undefined)
432
+ return { miss: 'unreachable' };
433
+ if (entry) {
434
+ const fetchedAt = typeof entry.fetchedAt === 'string'
435
+ ? Date.parse(entry.fetchedAt)
436
+ : Number.NaN;
437
+ if (!record(entry.usage) || !Number.isFinite(fetchedAt))
438
+ return { miss: 'unreachable' };
439
+ const reading = {
440
+ entry,
441
+ context: {
442
+ fetchedAt,
443
+ ...(typeof entry.source === 'string' && { source: entry.source }),
444
+ },
445
+ };
446
+ return now >= fetchedAt + MAX_AGE || entry.stale !== undefined
447
+ ? { miss: 'stale', reading }
448
+ : { reading };
449
+ }
450
+ if (this.#completeProviders.has(provider))
451
+ return { miss: 'absent' };
452
+ const since = this.#coldSince.get(provider) ?? now;
453
+ this.#coldSince.set(provider, since);
454
+ return { miss: now - since >= COLD_WAIT ? 'unreachable' : 'cold' };
455
+ }
394
456
  }
395
457
  export function acquireInsulaQuotaSource(transport, tokenPath) {
396
458
  const key = JSON.stringify([
@@ -1,5 +1,5 @@
1
1
  export { quotaCodec } from "./053cc17b5419261e-codec.js";
2
- export { formatQuota, isQuotaStale, NO_LIMITS_REPORTED, NO_QUOTA_READING, QUOTA_STALE_AFTER_MS, quotaTextParts, quotaWindowName, quotaWindowParts, } from "./0332701351b3eeec-format.js";
3
- export { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, decodeQuotaMap, emptyQuotaMap, isQuotaMap, } from "./905bffc0220f43d0-map.js";
2
+ export { formatQuota, isQuotaStale, NO_LIMITS_REPORTED, NO_QUOTA_READING, QUOTA_STALE_AFTER_MS, quotaCreditsText, quotaTextParts, quotaWindowName, quotaWindowParts, } from "./0332701351b3eeec-format.js";
3
+ export { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, decodeQuotaMap, emptyQuotaMap, isQuotaCredits, isQuotaMap, } from "./905bffc0220f43d0-map.js";
4
4
  export { isQuotaObservation, mergeQuotaObservation, QuotaCodecError, } from "./4f36d333d081d040-merge.js";
5
5
  export { budgetExhaustedResetAt, futureResetAt, projectQuota, readsExhausted, } from "./39c3cb05738fcf6d-projection.js";
@@ -154,13 +154,14 @@ function normalizeQuota(value) {
154
154
  };
155
155
  }
156
156
  const credits = value.credits;
157
- if (isRecord(credits) && typeof credits.hasCredits === "boolean" && typeof credits.unlimited === "boolean" && typeof credits.overageLimitReached === "boolean") {
158
- const balance = Number(credits.balance);
157
+ if (isRecord(credits) && typeof credits.hasCredits === "boolean" && typeof credits.unlimited === "boolean" && (credits.overageLimitReached === void 0 || typeof credits.overageLimitReached === "boolean")) {
158
+ const balance = Number(credits.balance), checkedAt = typeof credits.checkedAt === "number" ? credits.checkedAt : Number.NaN;
159
159
  quota.credits = {
160
160
  hasCredits: credits.hasCredits,
161
161
  unlimited: credits.unlimited,
162
- overageLimitReached: credits.overageLimitReached,
163
- ...Number.isFinite(balance) ? { balance } : {}
162
+ ...typeof credits.overageLimitReached === "boolean" ? { overageLimitReached: credits.overageLimitReached } : {},
163
+ ...Number.isFinite(balance) ? { balance } : {},
164
+ ...Number.isFinite(checkedAt) ? { checkedAt } : {}
164
165
  };
165
166
  }
166
167
  return Object.keys(quota).length ? quota : void 0;
@@ -109,8 +109,9 @@ export class ScopedRosterWatch {
109
109
  this.#reads = new CoalescedRead(async () => {
110
110
  const force = this.#force;
111
111
  this.#force = false;
112
+ // A read is only ever requested by a notice, which set #latestView.
112
113
  if (force || this.#latestView !== this.#options.view())
113
- await this.#options.discover();
114
+ await this.#options.discover(this.#latestView ?? '');
114
115
  }, (error) => {
115
116
  if (!this.#stopped)
116
117
  this.#options.onError?.(error);
@@ -14,6 +14,7 @@ export { ClaustrumConsumerError, } from "./ca0fccaba1d87af4-errors.js";
14
14
  export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, isCustodyPlaceholder, isCustodyPlaceholderValue, } from "./d9d2892a4c371301-host-slot.js";
15
15
  export { acquireInsulaQuotaSource, } from "./d0f0b6be200311af-insula.js";
16
16
  export { acceptAccount, declineAccount, isDeclined, } from "./7c140680fc81be92-interlock.js";
17
+ export { drainVaultQuota } from "./81dddd203f1b92a3-quota-writer.js";
17
18
  export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
18
19
  export { ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, WATCH_RETRY_MS, } from "./dc9290c2a7552ba6-watch.js";
19
20
  /**
@@ -2,13 +2,35 @@ import {
2
2
  formatQuota,
3
3
  isQuotaStale,
4
4
  projectQuota,
5
+ quotaCreditsText,
5
6
  quotaWindowName
6
7
  } from "./shared/d5a8f1061ee2f114-index.js";
7
8
  import { orderForPlacement } from "./shared/3e7d45a77c6ad4d5-index.js";
8
9
  import {
10
+ creditsUsable,
9
11
  orderedPlacement,
10
12
  vaultMainRouteId
11
13
  } from "./shared/abd062d633e29fd2-pool-routing.js";
14
+ export function accountCreditsText(quota) {
15
+ const credits = quota?.credits;
16
+ if (credits === null || typeof credits !== "object")
17
+ return;
18
+ const { hasCredits, unlimited, balance } = credits;
19
+ if (typeof hasCredits !== "boolean" || typeof unlimited !== "boolean")
20
+ return;
21
+ return quotaCreditsText({
22
+ hasCredits,
23
+ unlimited,
24
+ ...typeof balance === "number" ? { balance } : {}
25
+ });
26
+ }
27
+ export function accountServesOnCredits(quota, now = Date.now()) {
28
+ if (!exhaustedQuotaResetAt(quota, now))
29
+ return !1;
30
+ if (spendControlExhaustedResetAt(quota, now))
31
+ return !1;
32
+ return creditsUsable(quota?.credits, now);
33
+ }
12
34
  const QUOTA_WINDOW_KEYS = ["primary", "secondary"], LEGACY_WINDOW_MINUTES = {
13
35
  primary: 300,
14
36
  secondary: 10080
@@ -153,7 +175,7 @@ export function normalizeSidebarState(raw) {
153
175
  };
154
176
  } else
155
177
  main = { quota: null, killed: !1 };
156
- const fallbacks = normalizeAccountList(r.fallbacks), vaultAccounts = Array.isArray(r.vaultAccounts) ? normalizeAccountList(r.vaultAccounts) : void 0, activeId = typeof r.activeId === "string" ? r.activeId : void 0, route = typeof r.route === "string" ? r.route : DEFAULT_SIDEBAR_STATE.route, lastUpdated = typeof r.lastUpdated === "number" ? r.lastUpdated : 0, planType = typeof r.planType === "string" ? r.planType : void 0, credits = typeof r.credits === "number" ? r.credits : void 0, activeRouting = normalizeActiveRouting(r.activeRouting), stickyAssignments = normalizeStickyAssignments(r.stickyAssignments);
178
+ const fallbacks = normalizeAccountList(r.fallbacks), vaultAccounts = Array.isArray(r.vaultAccounts) ? normalizeAccountList(r.vaultAccounts) : void 0, activeId = typeof r.activeId === "string" ? r.activeId : void 0, route = typeof r.route === "string" ? r.route : DEFAULT_SIDEBAR_STATE.route, lastUpdated = typeof r.lastUpdated === "number" ? r.lastUpdated : 0, planType = typeof r.planType === "string" ? r.planType : void 0, activeRouting = normalizeActiveRouting(r.activeRouting), stickyAssignments = normalizeStickyAssignments(r.stickyAssignments);
157
179
  return {
158
180
  main,
159
181
  fallbacks,
@@ -163,7 +185,6 @@ export function normalizeSidebarState(raw) {
163
185
  ...activeRouting !== void 0 ? { activeRouting } : {},
164
186
  ...stickyAssignments !== void 0 ? { stickyAssignments } : {},
165
187
  ...planType !== void 0 ? { planType } : {},
166
- ...credits !== void 0 ? { credits } : {},
167
188
  ...r.accountPool === !0 ? { accountPool: !0 } : {},
168
189
  ...vaultAccounts !== void 0 ? { vaultAccounts } : {}
169
190
  };
@@ -491,12 +512,18 @@ function mergeSpendControl(fresher, older) {
491
512
  return;
492
513
  return older.spendControl;
493
514
  }
515
+ function fresherCredits(incoming, existing) {
516
+ if (!incoming || !existing)
517
+ return incoming ?? existing;
518
+ const at = (credits) => typeof credits.checkedAt === "number" && Number.isFinite(credits.checkedAt) ? credits.checkedAt : Number.NEGATIVE_INFINITY;
519
+ return at(existing) > at(incoming) ? existing : incoming;
520
+ }
494
521
  function mergeQuotaByWindow(incoming, existing) {
495
522
  if (!incoming)
496
523
  return existing;
497
524
  if (!existing)
498
525
  return incoming;
499
- const incomingAt = latestQuotaCheckedAt(incoming), existingAt = latestQuotaCheckedAt(existing), existingSnapshotIsFresher = existingAt !== void 0 && (incomingAt === void 0 || existingAt > incomingAt), primary = freshestWindow(incoming.primary, existing.primary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), secondary = freshestWindow(incoming.secondary, existing.secondary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), spendControl = existingSnapshotIsFresher ? mergeSpendControl(existing, incoming) : mergeSpendControl(incoming, existing), spendControlCleared = spendControl === void 0 && (incoming.spendControlCleared === !0 || existing.spendControlCleared === !0);
526
+ const incomingAt = latestQuotaCheckedAt(incoming), existingAt = latestQuotaCheckedAt(existing), existingSnapshotIsFresher = existingAt !== void 0 && (incomingAt === void 0 || existingAt > incomingAt), primary = freshestWindow(incoming.primary, existing.primary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), secondary = freshestWindow(incoming.secondary, existing.secondary, existingSnapshotIsFresher, incoming.checkedAt, existing.checkedAt), spendControl = existingSnapshotIsFresher ? mergeSpendControl(existing, incoming) : mergeSpendControl(incoming, existing), spendControlCleared = spendControl === void 0 && (incoming.spendControlCleared === !0 || existing.spendControlCleared === !0), credits = fresherCredits(incoming.credits, existing.credits);
500
527
  let checkedAt;
501
528
  for (const stamp of [
502
529
  finiteWindowCheckedAt(primary),
@@ -507,6 +534,7 @@ function mergeQuotaByWindow(incoming, existing) {
507
534
  const {
508
535
  spendControl: _incomingSpendControl,
509
536
  spendControlCleared: _incomingCleared,
537
+ credits: _incomingCredits,
510
538
  ...incomingRest
511
539
  } = incoming;
512
540
  return {
@@ -515,6 +543,7 @@ function mergeQuotaByWindow(incoming, existing) {
515
543
  secondary,
516
544
  ...spendControl !== void 0 ? { spendControl } : {},
517
545
  ...spendControlCleared ? { spendControlCleared: !0 } : {},
546
+ ...credits !== void 0 ? { credits } : {},
518
547
  checkedAt: checkedAt ?? incoming.checkedAt
519
548
  };
520
549
  }
@@ -22,7 +22,8 @@ export async function openControlDialog(dialog, initial, apply, active = () => !
22
22
  const choices = [
23
23
  ...section.items.filter((item) => item.actions.length > 0).map((item) => ({
24
24
  title: item.label,
25
- description: inlineDescription(item.status, item.detail),
25
+ description: inlineDescription(item.detail),
26
+ ...item.status ? { footer: item.status } : {},
26
27
  value: { itemId: item.id, actions: item.actions }
27
28
  })),
28
29
  ...section.actions.map((action) => ({