@cortexkit/opencode-openai-auth 1.3.0 → 1.5.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-BjGT9yWg.d.ts} +36 -28
- package/dist/index-2p3gcjsa.js +69 -0
- package/dist/{index-m3ctrabv.js → index-2rw81dtm.js} +1 -1
- package/dist/index-2yszc75z.js +2 -0
- package/dist/{index-gn2b097f.js → index-3y453wg4.js} +7 -7
- package/dist/{index-bc89akak.js → index-4w0nsq5s.js} +1 -1
- package/dist/index-6fdmqk6p.js +90 -0
- package/dist/{index-xwvze96g.js → index-7vkn2e46.js} +1 -1
- package/dist/{index-2pgkrebq.js → index-djnfq652.js} +2 -2
- package/dist/{index-tddr876f.js → index-e416r885.js} +2 -2
- package/dist/index-fztcacjr.js +65 -0
- package/dist/index-gmmakhbt.js +2 -0
- package/dist/{index-ftkhhr6d.js → index-krcqvyqf.js} +1 -1
- package/dist/index-mhfja8yr.js +2 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1 -1
- package/dist/{model-BdITTmeb.d.ts → model-BMq5SCUB.d.ts} +1 -1
- package/dist/{port-file-DbHIVC9a.d.ts → port-file-CjMFUWcX.d.ts} +1 -1
- package/dist/{protocol-MysYPAho.d.ts → protocol-BC3ndToM.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-DfYyqiOH.d.ts} +55 -7
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-ed4az5gg.js +1 -0
- package/dist/{token-z6qq7a1d.js → token-ycw7dyev.js} +1 -1
- 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 +5 -5
- 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 +104 -62
- package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
- package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +172 -43
- package/src/tui-compiled/raw/shared/375d9cca2def037d-private-files.js +49 -3
- 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/754d6017f8e419cf-builtins.js +95 -13
- 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/b9588ad4725c1d10-enrollment.js +27 -0
- 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 +2 -1
- 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 +104 -62
- package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +45 -1
- package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +172 -43
- package/src/tui-compiled/runtime/shared/375d9cca2def037d-private-files.js +49 -3
- 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/754d6017f8e419cf-builtins.js +95 -13
- 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/b9588ad4725c1d10-enrollment.js +27 -0
- 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 +2 -1
- 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-8x1aj7q3.js +0 -2
- 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
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
} from "@opencode-ai/plugin/tui"
|
|
17
17
|
import {
|
|
18
18
|
createEffect,
|
|
19
|
+
createMemo,
|
|
19
20
|
createSignal,
|
|
20
21
|
For,
|
|
21
22
|
Index,
|
|
@@ -28,6 +29,8 @@ import { createRpcClient } from "./rpc/rpc-client.js"
|
|
|
28
29
|
import { resolveRpcDir } from "./rpc/rpc-dir.js"
|
|
29
30
|
import {
|
|
30
31
|
type AccountQuota,
|
|
32
|
+
accountCreditsText,
|
|
33
|
+
accountServesOnCredits,
|
|
31
34
|
type CollapsedAccountLine,
|
|
32
35
|
computeQuotaPacing,
|
|
33
36
|
DEFAULT_SIDEBAR_STATE,
|
|
@@ -254,6 +257,45 @@ function StatRow(props: {
|
|
|
254
257
|
)
|
|
255
258
|
}
|
|
256
259
|
|
|
260
|
+
// One of an account's own rows under its quota bars (budget amounts, prepaid
|
|
261
|
+
// credits, reset credits): muted label left, value right. Like the quota rows
|
|
262
|
+
// it stays on one line: it measures its own width and, when both do not fit,
|
|
263
|
+
// cuts only the label (with …), keeping one blank cell before the value.
|
|
264
|
+
function AccountMetaRow(props: {
|
|
265
|
+
theme: ThemeCurrent
|
|
266
|
+
label: string
|
|
267
|
+
value: string
|
|
268
|
+
}) {
|
|
269
|
+
const [width, setWidth] = createSignal(0)
|
|
270
|
+
let row: { width: number } | undefined
|
|
271
|
+
const label = () => {
|
|
272
|
+
const columns = width()
|
|
273
|
+
if (columns <= 0) return props.label
|
|
274
|
+
return fitCollapsedLabel(
|
|
275
|
+
props.label,
|
|
276
|
+
columns - displayWidth(props.value) - 1,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
return (
|
|
280
|
+
<box
|
|
281
|
+
ref={(element: { width: number }) => {
|
|
282
|
+
row = element
|
|
283
|
+
}}
|
|
284
|
+
onSizeChange={() => setWidth(row?.width ?? 0)}
|
|
285
|
+
width='100%'
|
|
286
|
+
flexDirection='row'
|
|
287
|
+
justifyContent='space-between'
|
|
288
|
+
>
|
|
289
|
+
<text fg={props.theme.textMuted} wrapMode='none' flexShrink={1}>
|
|
290
|
+
{label()}
|
|
291
|
+
</text>
|
|
292
|
+
<text fg={props.theme.text} wrapMode='none' flexShrink={0}>
|
|
293
|
+
<b>{props.value}</b>
|
|
294
|
+
</text>
|
|
295
|
+
</box>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
257
299
|
// One account in the collapsed view: label left; narrow quota text and the
|
|
258
300
|
// status dot right (red ⊘ when killed). The session's account keeps the
|
|
259
301
|
// usage tone; other accounts, and any account whose reading is stale, are
|
|
@@ -475,9 +517,6 @@ export function getQuotaMetadataRows(
|
|
|
475
517
|
): Array<{ label: string; value: string }> {
|
|
476
518
|
const rows: Array<{ label: string; value: string }> = []
|
|
477
519
|
if (state.planType) rows.push({ label: 'Plan', value: state.planType })
|
|
478
|
-
if (state.credits !== undefined) {
|
|
479
|
-
rows.push({ label: 'Credits', value: String(state.credits) })
|
|
480
|
-
}
|
|
481
520
|
return rows
|
|
482
521
|
}
|
|
483
522
|
|
|
@@ -499,9 +538,18 @@ export function formatSpendControlAmounts(
|
|
|
499
538
|
return `${formatGroupedAmount(spendControl.used)} / ${formatGroupedAmount(spendControl.limit)} ${unit}`
|
|
500
539
|
}
|
|
501
540
|
|
|
541
|
+
/**
|
|
542
|
+
* The account's own rows under its quota bars: the workspace budget's amounts
|
|
543
|
+
* (`credits`), the prepaid credits (`prepaid`, worded by `accountCreditsText`)
|
|
544
|
+
* and the reset-credit count (`resets`), each only when known. While the
|
|
545
|
+
* account is serving the session on its prepaid credits (its free quota is
|
|
546
|
+
* used up), the prepaid row says so in place of its `prepaid` label.
|
|
547
|
+
*/
|
|
502
548
|
export function getAccountMetadataRows(
|
|
503
549
|
resetCredits: number | undefined,
|
|
504
550
|
spendControl?: SpendControlReading,
|
|
551
|
+
prepaidCredits?: string,
|
|
552
|
+
servingOnCredits = false,
|
|
505
553
|
): Array<{ label: string; value: string }> {
|
|
506
554
|
const rows: Array<{ label: string; value: string }> = []
|
|
507
555
|
if (spendControl) {
|
|
@@ -510,16 +558,64 @@ export function getAccountMetadataRows(
|
|
|
510
558
|
value: formatSpendControlAmounts(spendControl),
|
|
511
559
|
})
|
|
512
560
|
}
|
|
561
|
+
if (prepaidCredits !== undefined) {
|
|
562
|
+
rows.push({
|
|
563
|
+
label: servingOnCredits ? 'serving on credits' : 'prepaid',
|
|
564
|
+
value: prepaidCredits,
|
|
565
|
+
})
|
|
566
|
+
}
|
|
513
567
|
if (resetCredits !== undefined) {
|
|
514
568
|
rows.push({ label: 'resets', value: String(resetCredits) })
|
|
515
569
|
}
|
|
516
570
|
return rows
|
|
517
571
|
}
|
|
518
572
|
|
|
573
|
+
/** The fewest cells a shortened quota bar keeps; below this it is dropped. */
|
|
574
|
+
const MIN_QUOTA_BAR_CELLS = 3
|
|
575
|
+
|
|
576
|
+
export interface QuotaRowFit {
|
|
577
|
+
/** Cells of usage bar to draw; 0 draws no bar. */
|
|
578
|
+
barCells: number
|
|
579
|
+
showReset: boolean
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* How an expanded quota row fits on one line `columns` cells wide. The row is
|
|
584
|
+
* the padded label, the bar, the " N% left" figure and, at the right edge, the
|
|
585
|
+
* reset time. When it does not fit, the reset time goes first, then the bar
|
|
586
|
+
* shrinks (to no fewer than MIN_QUOTA_BAR_CELLS cells, else it is dropped).
|
|
587
|
+
* The label and the figure are never cut. `columns` of 0 means the row has not
|
|
588
|
+
* been measured yet, and everything is drawn.
|
|
589
|
+
*/
|
|
590
|
+
export function fitQuotaRow(input: {
|
|
591
|
+
columns: number
|
|
592
|
+
label: string
|
|
593
|
+
barWidth: number
|
|
594
|
+
figure: string
|
|
595
|
+
resetText?: string
|
|
596
|
+
}): QuotaRowFit {
|
|
597
|
+
const { columns, barWidth } = input
|
|
598
|
+
const reset = input.resetText ? displayWidth(input.resetText) : 0
|
|
599
|
+
if (columns <= 0) return { barCells: barWidth, showReset: reset > 0 }
|
|
600
|
+
const fixed = displayWidth(input.label) + displayWidth(input.figure)
|
|
601
|
+
// The reset sits at the right edge with at least one blank cell before it,
|
|
602
|
+
// so it never runs into the figure.
|
|
603
|
+
if (reset > 0 && fixed + barWidth + 1 + reset <= columns)
|
|
604
|
+
return { barCells: barWidth, showReset: true }
|
|
605
|
+
const room = columns - fixed
|
|
606
|
+
if (room >= barWidth) return { barCells: barWidth, showReset: false }
|
|
607
|
+
return {
|
|
608
|
+
barCells: room >= MIN_QUOTA_BAR_CELLS ? room : 0,
|
|
609
|
+
showReset: false,
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
519
613
|
// Quota window row: muted label left, tone-colored usage bar and shared quota text.
|
|
520
614
|
// When pacing data is present, the bar
|
|
521
615
|
// gains a pace segment and an off-pace window adds a muted subline with the
|
|
522
616
|
// reserve/deficit delta and the projected runout.
|
|
617
|
+
// The row measures its own width and never wraps: fitQuotaRow decides what is
|
|
618
|
+
// left out when it is too narrow, and the pacing subline is cut with ….
|
|
523
619
|
function QuotaRow(props: {
|
|
524
620
|
theme: ThemeCurrent
|
|
525
621
|
appearance: AppearancePrefs
|
|
@@ -531,6 +627,29 @@ function QuotaRow(props: {
|
|
|
531
627
|
pacing: QuotaPacing | null
|
|
532
628
|
}) {
|
|
533
629
|
const used = () => props.window?.usedPercent ?? 0
|
|
630
|
+
const [columns, setColumns] = createSignal(0)
|
|
631
|
+
let row: { width: number } | undefined
|
|
632
|
+
const label = () => props.label.padEnd(props.labelWidth)
|
|
633
|
+
const figure = () => ` ${props.text.slice(props.label.length + 1)}`
|
|
634
|
+
const fit = () =>
|
|
635
|
+
fitQuotaRow({
|
|
636
|
+
columns: columns(),
|
|
637
|
+
label: label(),
|
|
638
|
+
barWidth: props.appearance.barWidth,
|
|
639
|
+
figure: figure(),
|
|
640
|
+
resetText: props.resetText,
|
|
641
|
+
})
|
|
642
|
+
// Memos, so a new measured width that leaves the fit unchanged (as the
|
|
643
|
+
// first measurement usually does) does not rebuild the bar's text nodes:
|
|
644
|
+
// rebuilt nodes are blank until the next frame is drawn.
|
|
645
|
+
const barCells = createMemo(() => fit().barCells)
|
|
646
|
+
const showReset = createMemo(() => fit().showReset)
|
|
647
|
+
const fittedPaceLine = () => {
|
|
648
|
+
const line = paceLine()
|
|
649
|
+
if (line === null || columns() <= 0) return line
|
|
650
|
+
// The subline is indented by the label column; cut it to the rest.
|
|
651
|
+
return fitCollapsedLabel(line, columns() - props.labelWidth)
|
|
652
|
+
}
|
|
534
653
|
const paceLine = () => {
|
|
535
654
|
const pacing = props.pacing
|
|
536
655
|
if (!pacing || pacing.state === 'on-pace') return null
|
|
@@ -553,40 +672,64 @@ function QuotaRow(props: {
|
|
|
553
672
|
}
|
|
554
673
|
>
|
|
555
674
|
{/* Bars show usage; the adjacent figure shows quota left, as in the dialog. */}
|
|
556
|
-
<box
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
675
|
+
<box
|
|
676
|
+
ref={(element: { width: number }) => {
|
|
677
|
+
row = element
|
|
678
|
+
}}
|
|
679
|
+
onSizeChange={() => setColumns(row?.width ?? 0)}
|
|
680
|
+
width='100%'
|
|
681
|
+
flexDirection='row'
|
|
682
|
+
justifyContent='space-between'
|
|
683
|
+
>
|
|
684
|
+
<box flexDirection='row' flexShrink={0}>
|
|
685
|
+
<text fg={props.theme.textMuted} wrapMode='none'>
|
|
686
|
+
{label()}
|
|
560
687
|
</text>
|
|
561
|
-
<For
|
|
688
|
+
<For
|
|
689
|
+
each={quotaBarSegments(
|
|
690
|
+
used(),
|
|
691
|
+
{ ...props.appearance, barWidth: barCells() },
|
|
692
|
+
props.pacing,
|
|
693
|
+
)}
|
|
694
|
+
>
|
|
562
695
|
{(segment) => (
|
|
563
|
-
<text fg={toneColor(props.theme, segment.tone)}>
|
|
696
|
+
<text fg={toneColor(props.theme, segment.tone)} wrapMode='none'>
|
|
564
697
|
{segment.text}
|
|
565
698
|
</text>
|
|
566
699
|
)}
|
|
567
700
|
</For>
|
|
568
701
|
<text
|
|
569
702
|
fg={toneColor(props.theme, usageTone(used(), props.appearance))}
|
|
703
|
+
wrapMode='none'
|
|
570
704
|
>
|
|
571
|
-
{
|
|
705
|
+
{figure()}
|
|
572
706
|
</text>
|
|
573
707
|
</box>
|
|
574
|
-
<Show when={
|
|
575
|
-
<text fg={props.theme.textMuted}
|
|
708
|
+
<Show when={showReset()}>
|
|
709
|
+
<text fg={props.theme.textMuted} wrapMode='none' flexShrink={0}>
|
|
710
|
+
{props.resetText}
|
|
711
|
+
</text>
|
|
576
712
|
</Show>
|
|
577
713
|
</box>
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
props.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
714
|
+
{/* Keyed, so a cut to a new width draws a new text node: on Bun 1.3
|
|
715
|
+
an existing node kept drawing the text it was created with. */}
|
|
716
|
+
<Show when={fittedPaceLine()} keyed>
|
|
717
|
+
{(line) => (
|
|
718
|
+
<box width='100%' flexDirection='row'>
|
|
719
|
+
<text fg={props.theme.textMuted}>
|
|
720
|
+
{''.padEnd(props.labelWidth)}
|
|
721
|
+
</text>
|
|
722
|
+
<text
|
|
723
|
+
fg={toneColor(
|
|
724
|
+
props.theme,
|
|
725
|
+
props.pacing?.state === 'deficit' ? 'warn' : 'muted',
|
|
726
|
+
)}
|
|
727
|
+
wrapMode='none'
|
|
728
|
+
>
|
|
729
|
+
{line}
|
|
730
|
+
</text>
|
|
731
|
+
</box>
|
|
732
|
+
)}
|
|
590
733
|
</Show>
|
|
591
734
|
</Show>
|
|
592
735
|
)
|
|
@@ -672,14 +815,15 @@ function AccountBlock(props: {
|
|
|
672
815
|
each={getAccountMetadataRows(
|
|
673
816
|
props.resetCredits,
|
|
674
817
|
props.quota?.spendControl,
|
|
818
|
+
accountCreditsText(props.quota),
|
|
819
|
+
props.active && accountServesOnCredits(props.quota, Date.now()),
|
|
675
820
|
)}
|
|
676
821
|
>
|
|
677
822
|
{(row) => (
|
|
678
|
-
<
|
|
823
|
+
<AccountMetaRow
|
|
679
824
|
theme={props.theme}
|
|
680
825
|
label={row.label}
|
|
681
826
|
value={row.value}
|
|
682
|
-
tone='text'
|
|
683
827
|
/>
|
|
684
828
|
)}
|
|
685
829
|
</For>
|
|
@@ -106,6 +106,25 @@ export function quotaWindowParts(quota, options) {
|
|
|
106
106
|
const parts = windows.map(({ part }) => part);
|
|
107
107
|
return age === undefined ? { windows: parts } : { windows: parts, age };
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The narrow label of each scoped window part, as `narrowTextParts` names
|
|
111
|
+
* them: model short names are worked out over every model-scoped window in
|
|
112
|
+
* the reading, so two models never share a short name.
|
|
113
|
+
*/
|
|
114
|
+
function withShortNames(windows) {
|
|
115
|
+
const abbreviations = scopeAbbreviations(windows
|
|
116
|
+
.filter(({ part, scope }) => part.kind === 'window' && scope !== ALL_SCOPE)
|
|
117
|
+
.map(({ scope }) => scope));
|
|
118
|
+
for (const window of windows) {
|
|
119
|
+
window.part.short =
|
|
120
|
+
window.part.kind === 'budget'
|
|
121
|
+
? 'cr'
|
|
122
|
+
: window.scope === ALL_SCOPE
|
|
123
|
+
? window.part.name
|
|
124
|
+
: (abbreviations.get(window.scope) ?? window.scope);
|
|
125
|
+
}
|
|
126
|
+
return windows;
|
|
127
|
+
}
|
|
109
128
|
/**
|
|
110
129
|
* `quotaWindowParts` with each window's scope kept beside it. The narrow form
|
|
111
130
|
* needs the scope to name and pick model-scoped windows; the public parts do
|
|
@@ -128,6 +147,7 @@ function scopedWindowParts(quota, options) {
|
|
|
128
147
|
part: {
|
|
129
148
|
kind: 'window',
|
|
130
149
|
name: quotaWindowName(limit),
|
|
150
|
+
short: '',
|
|
131
151
|
figure: percentLeft(limit.remainingPercent ?? 0),
|
|
132
152
|
remainingPercent,
|
|
133
153
|
checkedAt: limit.checkedAt,
|
|
@@ -149,6 +169,7 @@ function scopedWindowParts(quota, options) {
|
|
|
149
169
|
part: {
|
|
150
170
|
kind: 'budget',
|
|
151
171
|
name: 'credits',
|
|
172
|
+
short: '',
|
|
152
173
|
figure,
|
|
153
174
|
...(remainingPercent === undefined
|
|
154
175
|
? {}
|
|
@@ -158,6 +179,7 @@ function scopedWindowParts(quota, options) {
|
|
|
158
179
|
},
|
|
159
180
|
});
|
|
160
181
|
}
|
|
182
|
+
withShortNames(windows);
|
|
161
183
|
if (windows.length > 0) {
|
|
162
184
|
const age = now - Math.min(...windows.map(({ part }) => part.checkedAt));
|
|
163
185
|
if (age > (options.staleAfterMs ?? QUOTA_STALE_AFTER_MS)) {
|
|
@@ -298,20 +320,21 @@ function narrowTextParts(quota, options) {
|
|
|
298
320
|
return grouped;
|
|
299
321
|
const general = windows.filter(({ part, scope }) => part.kind === 'window' && scope === ALL_SCOPE);
|
|
300
322
|
const scoped = windows.filter(({ part, scope }) => part.kind === 'window' && scope !== ALL_SCOPE);
|
|
301
|
-
const abbreviations = scopeAbbreviations(scoped.map(({ scope }) => scope));
|
|
302
323
|
// The items competing for the one slot after the account-wide windows, in
|
|
303
324
|
// tie-break order: scoped windows in display order, then the budget. A
|
|
304
325
|
// spent budget counts as 0% left; one without a percentage cannot compete.
|
|
326
|
+
// Their labels are the parts' short names, so the narrow line and a layout
|
|
327
|
+
// reading `short` always agree.
|
|
305
328
|
const budget = windows.find(({ part }) => part.kind === 'budget');
|
|
306
329
|
const budgetLeft = budget?.spent ? 0 : budget?.part.remainingPercent;
|
|
307
330
|
const extras = [
|
|
308
|
-
...scoped.map(({
|
|
309
|
-
label:
|
|
331
|
+
...scoped.map(({ part }) => ({
|
|
332
|
+
label: part.short,
|
|
310
333
|
remaining: part.remainingPercent ?? 0,
|
|
311
334
|
})),
|
|
312
|
-
...(budgetLeft === undefined
|
|
335
|
+
...(budgetLeft === undefined || budget === undefined
|
|
313
336
|
? []
|
|
314
|
-
: [{ label:
|
|
337
|
+
: [{ label: budget.part.short, remaining: budgetLeft }]),
|
|
315
338
|
];
|
|
316
339
|
// Strictly lower wins, so on a tie the earlier item keeps the slot.
|
|
317
340
|
const tightest = extras.reduce((best, item) => best === undefined || item.remaining < best.remaining ? item : best, undefined);
|
|
@@ -357,3 +380,25 @@ export function formatQuota(quota, options) {
|
|
|
357
380
|
? NO_LIMITS_REPORTED
|
|
358
381
|
: NO_QUOTA_READING;
|
|
359
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Prepaid credits as one short value, worded as the official Codex client's
|
|
385
|
+
* status view words them: `Unlimited` for unlimited credits, `<n> credits`
|
|
386
|
+
* for a finite positive balance (rounded to a whole number), `Available` when
|
|
387
|
+
* the account has credits but no usable balance was reported, and undefined
|
|
388
|
+
* when the account has no credits (or `credits` is undefined), so a caller
|
|
389
|
+
* shows no credits row at all. Unlimited wins even when `hasCredits` is false.
|
|
390
|
+
* No other formatter adds this text; a caller that wants a credits row asks
|
|
391
|
+
* for it.
|
|
392
|
+
*/
|
|
393
|
+
export function quotaCreditsText(credits) {
|
|
394
|
+
if (credits === undefined)
|
|
395
|
+
return undefined;
|
|
396
|
+
if (credits.unlimited)
|
|
397
|
+
return 'Unlimited';
|
|
398
|
+
if (!credits.hasCredits)
|
|
399
|
+
return undefined;
|
|
400
|
+
const { balance } = credits;
|
|
401
|
+
return typeof balance === 'number' && Number.isFinite(balance) && balance > 0
|
|
402
|
+
? `${Math.round(balance)} credits`
|
|
403
|
+
: 'Available';
|
|
404
|
+
}
|
|
@@ -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
|
}
|