@cortexkit/opencode-anthropic-auth 2.0.1 → 2.0.3
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/README.md +35 -845
- package/dist/cli-failure.d.ts +6 -5
- package/dist/cli.js +57 -103
- package/dist/custody-mode.d.ts +1 -0
- package/dist/{index-52r2e1h9.js → index-5hyennzm.js} +1 -1
- package/dist/{index-p4wvcwhh.js → index-fncp6r0j.js} +1 -1
- package/dist/index-jmjevtm1.js +2 -0
- package/dist/index-kd89hcr3.js +2 -0
- package/dist/index-mdpyfkv3.js +464 -0
- package/dist/index-nfcp0m1t.js +42 -0
- package/dist/index-qttfa42y.js +2 -0
- package/dist/{index-3xqv3rw3.js → index-rftced5v.js} +1 -1
- package/dist/{index-zdgceztz.js → index-vjehtqnb.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/quota-preflight.d.ts +1 -0
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/setup/command.d.ts +5 -14
- package/dist/setup/native-activation.d.ts +14 -6
- package/dist/setup/native-enrollment.d.ts +15 -0
- package/dist/setup/native-local.d.ts +31 -1
- package/dist/setup/process-fence.d.ts +0 -6
- package/dist/setup/types.d.ts +9 -0
- package/dist/sidebar-state.d.ts +20 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +17 -2
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +0 -2
- package/dist/v2/activation.d.ts +2 -0
- package/dist/v2/adapter.d.ts +8 -2
- package/dist/v2/cachekeep.d.ts +19 -0
- package/dist/v2/menu.d.ts +2 -1
- package/dist/v2/server.js +4 -4
- package/dist/v2/setup.d.ts +2 -0
- package/dist/v2/source.d.ts +1 -0
- package/dist/v2/tui.js +1 -1
- package/package.json +5 -5
- package/src/sidebar-state.ts +165 -29
- package/src/tui-compiled/sidebar-state.ts +165 -29
- package/src/tui-compiled/tui.tsx +363 -239
- package/src/tui.tsx +149 -48
- package/dist/index-04syas8a.js +0 -2
- package/dist/index-b3mxpp4w.js +0 -2
- package/dist/index-ynq181e8.js +0 -42
- package/dist/index-yxtdke11.js +0 -449
- package/dist/setup/activation.d.ts +0 -7
- package/dist/setup/command.test.d.ts +0 -1
- package/dist/setup/detect.d.ts +0 -8
- package/dist/setup/host-executable.d.ts +0 -3
- package/dist/setup/jsonc.d.ts +0 -9
- package/dist/setup/opencode-config.d.ts +0 -18
- package/dist/setup/pi.d.ts +0 -18
package/src/sidebar-state.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface QuotaWindow {
|
|
|
2
2
|
usedPercent: number
|
|
3
3
|
remainingPercent: number
|
|
4
4
|
resetsAt?: string
|
|
5
|
+
checkedAt?: number
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export interface ScopedQuotaWindow extends QuotaWindow {
|
|
@@ -12,6 +13,7 @@ export interface ScopedQuotaWindow extends QuotaWindow {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface AccountQuota {
|
|
16
|
+
checkedAt?: number
|
|
15
17
|
five_hour?: QuotaWindow
|
|
16
18
|
seven_day?: QuotaWindow
|
|
17
19
|
scoped?: ScopedQuotaWindow[]
|
|
@@ -31,10 +33,12 @@ export interface SidebarAccountState {
|
|
|
31
33
|
label: string | undefined
|
|
32
34
|
quota: AccountQuota | null
|
|
33
35
|
enabled: boolean
|
|
36
|
+
stateLine?: string
|
|
37
|
+
quotaDated?: boolean
|
|
34
38
|
// True when the account's refresh token is permanently dead (400
|
|
35
39
|
// invalid_grant) and it needs a re-login — distinct from a transient backoff.
|
|
36
40
|
needsReauth: boolean
|
|
37
|
-
// True when the vault
|
|
41
|
+
// True when the vault credential needs a new sign-in.
|
|
38
42
|
vaultReauth?: boolean
|
|
39
43
|
// A gate alone does not prove the sidecar can serve a request.
|
|
40
44
|
vaultServed?: boolean
|
|
@@ -77,7 +81,10 @@ export interface PrimeSidebarAccountState {
|
|
|
77
81
|
export interface SidebarState {
|
|
78
82
|
main: {
|
|
79
83
|
quota: AccountQuota | null
|
|
84
|
+
enabled?: boolean
|
|
80
85
|
tierLabel?: string
|
|
86
|
+
stateLine?: string
|
|
87
|
+
quotaDated?: boolean
|
|
81
88
|
quotaBackedOff?: boolean
|
|
82
89
|
quotaBackoffUntil?: number
|
|
83
90
|
refreshBackedOff?: boolean
|
|
@@ -120,7 +127,11 @@ import {
|
|
|
120
127
|
import { tmpdir } from 'node:os'
|
|
121
128
|
import { dirname, join } from 'node:path'
|
|
122
129
|
import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core'
|
|
123
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
logger,
|
|
132
|
+
quotaDisplayProjection,
|
|
133
|
+
quotaWindowParts,
|
|
134
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
124
135
|
|
|
125
136
|
const STATE_FILE_ENV = 'OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE'
|
|
126
137
|
const DEFAULT_STATE_DIR = join(tmpdir(), 'opencode-anthropic-auth')
|
|
@@ -229,12 +240,14 @@ function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
|
|
|
229
240
|
usedPercent,
|
|
230
241
|
remainingPercent,
|
|
231
242
|
resetsAt: typeof value.resetsAt === 'string' ? value.resetsAt : undefined,
|
|
243
|
+
...(isFiniteNumber(value.checkedAt) && { checkedAt: value.checkedAt }),
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
function normalizeAccountQuota(value: unknown): AccountQuota | null {
|
|
236
248
|
if (!isRecord(value)) return null
|
|
237
249
|
const quota: AccountQuota = {}
|
|
250
|
+
if (isFiniteNumber(value.checkedAt)) quota.checkedAt = value.checkedAt
|
|
238
251
|
const fiveHour = normalizeQuotaWindow(value.five_hour)
|
|
239
252
|
const sevenDay = normalizeQuotaWindow(value.seven_day)
|
|
240
253
|
if (fiveHour) quota.five_hour = fiveHour
|
|
@@ -327,6 +340,9 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
327
340
|
if (isRecord(raw.main)) {
|
|
328
341
|
const m = raw.main
|
|
329
342
|
main.quota = normalizeAccountQuota(m.quota)
|
|
343
|
+
if (typeof m.enabled === 'boolean') main.enabled = m.enabled
|
|
344
|
+
if (typeof m.stateLine === 'string') main.stateLine = m.stateLine
|
|
345
|
+
if (m.quotaDated === true) main.quotaDated = true
|
|
330
346
|
if (typeof m.tierLabel === 'string' && m.tierLabel.trim()) {
|
|
331
347
|
main.tierLabel = m.tierLabel.trim()
|
|
332
348
|
}
|
|
@@ -349,6 +365,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
349
365
|
label: typeof entry.label === 'string' ? entry.label : undefined,
|
|
350
366
|
quota: normalizeAccountQuota(entry.quota),
|
|
351
367
|
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
|
|
368
|
+
...(typeof entry.stateLine === 'string' && {
|
|
369
|
+
stateLine: entry.stateLine,
|
|
370
|
+
}),
|
|
371
|
+
...(entry.quotaDated === true && { quotaDated: true }),
|
|
352
372
|
needsReauth:
|
|
353
373
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
354
374
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
@@ -792,13 +812,13 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
792
812
|
id: string
|
|
793
813
|
name: string
|
|
794
814
|
quota: AccountQuota | null
|
|
815
|
+
stateLine?: string
|
|
816
|
+
quotaDated?: boolean
|
|
795
817
|
} {
|
|
796
818
|
const activeId = state.activeId
|
|
797
819
|
if (activeId && activeId !== 'main') {
|
|
798
|
-
//
|
|
799
|
-
// accounts
|
|
800
|
-
// enabled. Kept so this pure helper stays correct for any caller and is
|
|
801
|
-
// exercised directly by the unit tests.
|
|
820
|
+
// A disabled fallback cannot remain the active route, even though vault
|
|
821
|
+
// accounts stay visible in the expanded inventory while turned off.
|
|
802
822
|
const fallback = state.fallbacks?.find(
|
|
803
823
|
(account) => account.enabled && account.id === activeId,
|
|
804
824
|
)
|
|
@@ -807,15 +827,22 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
807
827
|
id: fallback.id,
|
|
808
828
|
name: fallback.label ?? fallback.id,
|
|
809
829
|
quota: fallback.quota,
|
|
830
|
+
...(fallback.stateLine && {
|
|
831
|
+
stateLine: fallback.stateLine,
|
|
832
|
+
quotaDated: fallback.quotaDated,
|
|
833
|
+
}),
|
|
810
834
|
}
|
|
811
835
|
}
|
|
812
836
|
}
|
|
813
|
-
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
837
|
+
return {
|
|
838
|
+
id: 'main',
|
|
839
|
+
name: 'main',
|
|
840
|
+
quota: state.main?.quota ?? null,
|
|
841
|
+
...(state.main?.stateLine && {
|
|
842
|
+
stateLine: state.main.stateLine,
|
|
843
|
+
quotaDated: state.main.quotaDated,
|
|
844
|
+
}),
|
|
845
|
+
}
|
|
819
846
|
}
|
|
820
847
|
|
|
821
848
|
/**
|
|
@@ -903,49 +930,158 @@ export function getFableRecoverySummary(
|
|
|
903
930
|
return `Opus 4.8 · ${recovery.remaining} left`
|
|
904
931
|
}
|
|
905
932
|
|
|
906
|
-
|
|
933
|
+
/** Keep full shared names when they fit; truncate only to preserve row columns. */
|
|
934
|
+
export function formatQuotaRowLabel(name: string, width = 10): string {
|
|
935
|
+
if (width <= 0) return ''
|
|
936
|
+
let label = name
|
|
937
|
+
if (Bun.stringWidth(label) > width) {
|
|
938
|
+
label = ''
|
|
939
|
+
for (const character of name) {
|
|
940
|
+
if (Bun.stringWidth(label + character) > width - 1) break
|
|
941
|
+
label += character
|
|
942
|
+
}
|
|
943
|
+
label += '…'
|
|
944
|
+
}
|
|
945
|
+
return label + ' '.repeat(Math.max(0, width - Bun.stringWidth(label)))
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// common-auth 0.18.0 has no narrow form, so keep its one-line quota wording here.
|
|
949
|
+
// Once supported, replace this body with formatQuota(quotaDisplayProjection(quota, now),
|
|
950
|
+
// { now, form: 'narrow' }) from common-auth.
|
|
951
|
+
function formatNarrowQuota(quota: AccountQuota, now: number): string {
|
|
952
|
+
const parts = quotaWindowParts(quotaDisplayProjection(quota, now), { now })
|
|
953
|
+
const percent = (remaining: number | undefined) =>
|
|
954
|
+
remaining === undefined ? '—' : `${Math.round(remaining)}%`
|
|
955
|
+
const values = ['5h', '7d'].map((name) => {
|
|
956
|
+
const part = parts.windows.find(
|
|
957
|
+
(part) => part.name === name && part.kind === 'window',
|
|
958
|
+
)
|
|
959
|
+
return `${name} ${percent(part?.remainingPercent)}`
|
|
960
|
+
})
|
|
961
|
+
const scoped = quota.scoped ?? []
|
|
962
|
+
const names = [...new Set(scoped.map((window) => window.modelName))]
|
|
963
|
+
const lengths = names.map(() => 2)
|
|
964
|
+
while (true) {
|
|
965
|
+
const aliases = names.map((name, index) =>
|
|
966
|
+
[...name].slice(0, lengths[index]).join(''),
|
|
967
|
+
)
|
|
968
|
+
let extended = false
|
|
969
|
+
for (const [index, alias] of aliases.entries()) {
|
|
970
|
+
const length = lengths[index] ?? 2
|
|
971
|
+
if (
|
|
972
|
+
aliases.some(
|
|
973
|
+
(other, otherIndex) => otherIndex !== index && other === alias,
|
|
974
|
+
) &&
|
|
975
|
+
length < [...(names[index] ?? '')].length
|
|
976
|
+
) {
|
|
977
|
+
lengths[index] = length + 1
|
|
978
|
+
extended = true
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (!extended) break
|
|
982
|
+
}
|
|
983
|
+
const tightest = scoped.reduce<ScopedQuotaWindow | undefined>(
|
|
984
|
+
(lowest, window) =>
|
|
985
|
+
!lowest || window.remainingPercent < lowest.remainingPercent
|
|
986
|
+
? window
|
|
987
|
+
: lowest,
|
|
988
|
+
undefined,
|
|
989
|
+
)
|
|
990
|
+
const budget = parts.windows.find((part) => part.kind === 'budget')
|
|
991
|
+
const budgetRemaining = quota.extraUsage?.exhausted
|
|
992
|
+
? 0
|
|
993
|
+
: budget?.remainingPercent
|
|
994
|
+
// Monetary ratios can land a few floating-point bits below an equal model reading.
|
|
995
|
+
const budgetTighter =
|
|
996
|
+
budget &&
|
|
997
|
+
(!tightest ||
|
|
998
|
+
(budgetRemaining !== undefined &&
|
|
999
|
+
tightest.remainingPercent - budgetRemaining > 1e-9))
|
|
1000
|
+
if (budgetTighter) {
|
|
1001
|
+
values.push(`cr ${percent(budgetRemaining)}`)
|
|
1002
|
+
} else if (tightest) {
|
|
1003
|
+
const index = names.indexOf(tightest.modelName)
|
|
1004
|
+
const name = [...tightest.modelName].slice(0, lengths[index]).join('')
|
|
1005
|
+
const part = parts.windows.find(
|
|
1006
|
+
(part) => part.kind === 'window' && part.name === tightest.title,
|
|
1007
|
+
)
|
|
1008
|
+
values.push(`${name} ${percent(part?.remainingPercent)}`)
|
|
1009
|
+
}
|
|
1010
|
+
return `${values.join(' · ')} left`
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function getCollapsedQuotaSummary(
|
|
1014
|
+
quota: AccountQuota | null,
|
|
1015
|
+
now = Date.now(),
|
|
1016
|
+
): {
|
|
907
1017
|
fiveHourUsedPercent: number | null
|
|
908
1018
|
sevenDayUsedPercent: number | null
|
|
909
1019
|
scopedUsedPercents: number[]
|
|
910
1020
|
text: string | null
|
|
1021
|
+
stale: boolean
|
|
911
1022
|
} {
|
|
912
1023
|
const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
|
|
913
1024
|
const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
|
|
914
1025
|
const scoped = quota?.scoped ?? []
|
|
915
1026
|
const scopedUsedPercents = scoped.map((window) => window.usedPercent)
|
|
916
1027
|
if (
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1028
|
+
!quota ||
|
|
1029
|
+
(fiveHourUsedPercent == null &&
|
|
1030
|
+
sevenDayUsedPercent == null &&
|
|
1031
|
+
scoped.length === 0 &&
|
|
1032
|
+
!quota.extraUsage)
|
|
920
1033
|
) {
|
|
921
1034
|
return {
|
|
922
1035
|
fiveHourUsedPercent,
|
|
923
1036
|
sevenDayUsedPercent,
|
|
924
1037
|
scopedUsedPercents,
|
|
925
1038
|
text: null,
|
|
1039
|
+
stale: false,
|
|
926
1040
|
}
|
|
927
1041
|
}
|
|
928
1042
|
|
|
929
|
-
const scopedSegments = scoped.map(
|
|
930
|
-
(window) =>
|
|
931
|
-
`${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
|
|
932
|
-
)
|
|
933
|
-
const primarySegments =
|
|
934
|
-
fiveHourUsedPercent == null && sevenDayUsedPercent == null
|
|
935
|
-
? []
|
|
936
|
-
: [
|
|
937
|
-
`5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
|
|
938
|
-
`7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
|
|
939
|
-
]
|
|
940
|
-
|
|
941
1043
|
return {
|
|
942
1044
|
fiveHourUsedPercent,
|
|
943
1045
|
sevenDayUsedPercent,
|
|
944
1046
|
scopedUsedPercents,
|
|
945
|
-
text:
|
|
1047
|
+
text: formatNarrowQuota(quota, now),
|
|
1048
|
+
stale:
|
|
1049
|
+
quotaWindowParts(quotaDisplayProjection(quota, now), { now }).age !==
|
|
1050
|
+
undefined,
|
|
946
1051
|
}
|
|
947
1052
|
}
|
|
948
1053
|
|
|
1054
|
+
export function getCollapsedAccounts(
|
|
1055
|
+
state: SidebarState,
|
|
1056
|
+
showFallbacks = true,
|
|
1057
|
+
) {
|
|
1058
|
+
const activeId = resolveActiveAccount(state).id
|
|
1059
|
+
return [
|
|
1060
|
+
{
|
|
1061
|
+
id: 'main',
|
|
1062
|
+
name: 'main',
|
|
1063
|
+
quota: state.main?.quota ?? null,
|
|
1064
|
+
quotaDated: state.main?.quotaDated,
|
|
1065
|
+
active: activeId === 'main',
|
|
1066
|
+
},
|
|
1067
|
+
...(showFallbacks
|
|
1068
|
+
? (state.fallbacks ?? [])
|
|
1069
|
+
.filter(
|
|
1070
|
+
(account) =>
|
|
1071
|
+
account.enabled ||
|
|
1072
|
+
(account.custodyState && account.custodyState !== 'off'),
|
|
1073
|
+
)
|
|
1074
|
+
.map((account) => ({
|
|
1075
|
+
id: account.id,
|
|
1076
|
+
name: account.label ?? account.id,
|
|
1077
|
+
quota: account.quota,
|
|
1078
|
+
quotaDated: account.quotaDated,
|
|
1079
|
+
active: activeId === account.id,
|
|
1080
|
+
}))
|
|
1081
|
+
: []),
|
|
1082
|
+
]
|
|
1083
|
+
}
|
|
1084
|
+
|
|
949
1085
|
export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000
|
|
950
1086
|
export const SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000
|
|
951
1087
|
|
|
@@ -2,6 +2,7 @@ export interface QuotaWindow {
|
|
|
2
2
|
usedPercent: number
|
|
3
3
|
remainingPercent: number
|
|
4
4
|
resetsAt?: string
|
|
5
|
+
checkedAt?: number
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export interface ScopedQuotaWindow extends QuotaWindow {
|
|
@@ -12,6 +13,7 @@ export interface ScopedQuotaWindow extends QuotaWindow {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface AccountQuota {
|
|
16
|
+
checkedAt?: number
|
|
15
17
|
five_hour?: QuotaWindow
|
|
16
18
|
seven_day?: QuotaWindow
|
|
17
19
|
scoped?: ScopedQuotaWindow[]
|
|
@@ -31,10 +33,12 @@ export interface SidebarAccountState {
|
|
|
31
33
|
label: string | undefined
|
|
32
34
|
quota: AccountQuota | null
|
|
33
35
|
enabled: boolean
|
|
36
|
+
stateLine?: string
|
|
37
|
+
quotaDated?: boolean
|
|
34
38
|
// True when the account's refresh token is permanently dead (400
|
|
35
39
|
// invalid_grant) and it needs a re-login — distinct from a transient backoff.
|
|
36
40
|
needsReauth: boolean
|
|
37
|
-
// True when the vault
|
|
41
|
+
// True when the vault credential needs a new sign-in.
|
|
38
42
|
vaultReauth?: boolean
|
|
39
43
|
// A gate alone does not prove the sidecar can serve a request.
|
|
40
44
|
vaultServed?: boolean
|
|
@@ -77,7 +81,10 @@ export interface PrimeSidebarAccountState {
|
|
|
77
81
|
export interface SidebarState {
|
|
78
82
|
main: {
|
|
79
83
|
quota: AccountQuota | null
|
|
84
|
+
enabled?: boolean
|
|
80
85
|
tierLabel?: string
|
|
86
|
+
stateLine?: string
|
|
87
|
+
quotaDated?: boolean
|
|
81
88
|
quotaBackedOff?: boolean
|
|
82
89
|
quotaBackoffUntil?: number
|
|
83
90
|
refreshBackedOff?: boolean
|
|
@@ -120,7 +127,11 @@ import {
|
|
|
120
127
|
import { tmpdir } from 'node:os'
|
|
121
128
|
import { dirname, join } from 'node:path'
|
|
122
129
|
import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core'
|
|
123
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
logger,
|
|
132
|
+
quotaDisplayProjection,
|
|
133
|
+
quotaWindowParts,
|
|
134
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
124
135
|
|
|
125
136
|
const STATE_FILE_ENV = 'OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE'
|
|
126
137
|
const DEFAULT_STATE_DIR = join(tmpdir(), 'opencode-anthropic-auth')
|
|
@@ -229,12 +240,14 @@ function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
|
|
|
229
240
|
usedPercent,
|
|
230
241
|
remainingPercent,
|
|
231
242
|
resetsAt: typeof value.resetsAt === 'string' ? value.resetsAt : undefined,
|
|
243
|
+
...(isFiniteNumber(value.checkedAt) && { checkedAt: value.checkedAt }),
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
function normalizeAccountQuota(value: unknown): AccountQuota | null {
|
|
236
248
|
if (!isRecord(value)) return null
|
|
237
249
|
const quota: AccountQuota = {}
|
|
250
|
+
if (isFiniteNumber(value.checkedAt)) quota.checkedAt = value.checkedAt
|
|
238
251
|
const fiveHour = normalizeQuotaWindow(value.five_hour)
|
|
239
252
|
const sevenDay = normalizeQuotaWindow(value.seven_day)
|
|
240
253
|
if (fiveHour) quota.five_hour = fiveHour
|
|
@@ -327,6 +340,9 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
327
340
|
if (isRecord(raw.main)) {
|
|
328
341
|
const m = raw.main
|
|
329
342
|
main.quota = normalizeAccountQuota(m.quota)
|
|
343
|
+
if (typeof m.enabled === 'boolean') main.enabled = m.enabled
|
|
344
|
+
if (typeof m.stateLine === 'string') main.stateLine = m.stateLine
|
|
345
|
+
if (m.quotaDated === true) main.quotaDated = true
|
|
330
346
|
if (typeof m.tierLabel === 'string' && m.tierLabel.trim()) {
|
|
331
347
|
main.tierLabel = m.tierLabel.trim()
|
|
332
348
|
}
|
|
@@ -349,6 +365,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
349
365
|
label: typeof entry.label === 'string' ? entry.label : undefined,
|
|
350
366
|
quota: normalizeAccountQuota(entry.quota),
|
|
351
367
|
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
|
|
368
|
+
...(typeof entry.stateLine === 'string' && {
|
|
369
|
+
stateLine: entry.stateLine,
|
|
370
|
+
}),
|
|
371
|
+
...(entry.quotaDated === true && { quotaDated: true }),
|
|
352
372
|
needsReauth:
|
|
353
373
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
354
374
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
@@ -792,13 +812,13 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
792
812
|
id: string
|
|
793
813
|
name: string
|
|
794
814
|
quota: AccountQuota | null
|
|
815
|
+
stateLine?: string
|
|
816
|
+
quotaDated?: boolean
|
|
795
817
|
} {
|
|
796
818
|
const activeId = state.activeId
|
|
797
819
|
if (activeId && activeId !== 'main') {
|
|
798
|
-
//
|
|
799
|
-
// accounts
|
|
800
|
-
// enabled. Kept so this pure helper stays correct for any caller and is
|
|
801
|
-
// exercised directly by the unit tests.
|
|
820
|
+
// A disabled fallback cannot remain the active route, even though vault
|
|
821
|
+
// accounts stay visible in the expanded inventory while turned off.
|
|
802
822
|
const fallback = state.fallbacks?.find(
|
|
803
823
|
(account) => account.enabled && account.id === activeId,
|
|
804
824
|
)
|
|
@@ -807,15 +827,22 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
807
827
|
id: fallback.id,
|
|
808
828
|
name: fallback.label ?? fallback.id,
|
|
809
829
|
quota: fallback.quota,
|
|
830
|
+
...(fallback.stateLine && {
|
|
831
|
+
stateLine: fallback.stateLine,
|
|
832
|
+
quotaDated: fallback.quotaDated,
|
|
833
|
+
}),
|
|
810
834
|
}
|
|
811
835
|
}
|
|
812
836
|
}
|
|
813
|
-
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
837
|
+
return {
|
|
838
|
+
id: 'main',
|
|
839
|
+
name: 'main',
|
|
840
|
+
quota: state.main?.quota ?? null,
|
|
841
|
+
...(state.main?.stateLine && {
|
|
842
|
+
stateLine: state.main.stateLine,
|
|
843
|
+
quotaDated: state.main.quotaDated,
|
|
844
|
+
}),
|
|
845
|
+
}
|
|
819
846
|
}
|
|
820
847
|
|
|
821
848
|
/**
|
|
@@ -903,49 +930,158 @@ export function getFableRecoverySummary(
|
|
|
903
930
|
return `Opus 4.8 · ${recovery.remaining} left`
|
|
904
931
|
}
|
|
905
932
|
|
|
906
|
-
|
|
933
|
+
/** Keep full shared names when they fit; truncate only to preserve row columns. */
|
|
934
|
+
export function formatQuotaRowLabel(name: string, width = 10): string {
|
|
935
|
+
if (width <= 0) return ''
|
|
936
|
+
let label = name
|
|
937
|
+
if (Bun.stringWidth(label) > width) {
|
|
938
|
+
label = ''
|
|
939
|
+
for (const character of name) {
|
|
940
|
+
if (Bun.stringWidth(label + character) > width - 1) break
|
|
941
|
+
label += character
|
|
942
|
+
}
|
|
943
|
+
label += '…'
|
|
944
|
+
}
|
|
945
|
+
return label + ' '.repeat(Math.max(0, width - Bun.stringWidth(label)))
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// common-auth 0.18.0 has no narrow form, so keep its one-line quota wording here.
|
|
949
|
+
// Once supported, replace this body with formatQuota(quotaDisplayProjection(quota, now),
|
|
950
|
+
// { now, form: 'narrow' }) from common-auth.
|
|
951
|
+
function formatNarrowQuota(quota: AccountQuota, now: number): string {
|
|
952
|
+
const parts = quotaWindowParts(quotaDisplayProjection(quota, now), { now })
|
|
953
|
+
const percent = (remaining: number | undefined) =>
|
|
954
|
+
remaining === undefined ? '—' : `${Math.round(remaining)}%`
|
|
955
|
+
const values = ['5h', '7d'].map((name) => {
|
|
956
|
+
const part = parts.windows.find(
|
|
957
|
+
(part) => part.name === name && part.kind === 'window',
|
|
958
|
+
)
|
|
959
|
+
return `${name} ${percent(part?.remainingPercent)}`
|
|
960
|
+
})
|
|
961
|
+
const scoped = quota.scoped ?? []
|
|
962
|
+
const names = [...new Set(scoped.map((window) => window.modelName))]
|
|
963
|
+
const lengths = names.map(() => 2)
|
|
964
|
+
while (true) {
|
|
965
|
+
const aliases = names.map((name, index) =>
|
|
966
|
+
[...name].slice(0, lengths[index]).join(''),
|
|
967
|
+
)
|
|
968
|
+
let extended = false
|
|
969
|
+
for (const [index, alias] of aliases.entries()) {
|
|
970
|
+
const length = lengths[index] ?? 2
|
|
971
|
+
if (
|
|
972
|
+
aliases.some(
|
|
973
|
+
(other, otherIndex) => otherIndex !== index && other === alias,
|
|
974
|
+
) &&
|
|
975
|
+
length < [...(names[index] ?? '')].length
|
|
976
|
+
) {
|
|
977
|
+
lengths[index] = length + 1
|
|
978
|
+
extended = true
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (!extended) break
|
|
982
|
+
}
|
|
983
|
+
const tightest = scoped.reduce<ScopedQuotaWindow | undefined>(
|
|
984
|
+
(lowest, window) =>
|
|
985
|
+
!lowest || window.remainingPercent < lowest.remainingPercent
|
|
986
|
+
? window
|
|
987
|
+
: lowest,
|
|
988
|
+
undefined,
|
|
989
|
+
)
|
|
990
|
+
const budget = parts.windows.find((part) => part.kind === 'budget')
|
|
991
|
+
const budgetRemaining = quota.extraUsage?.exhausted
|
|
992
|
+
? 0
|
|
993
|
+
: budget?.remainingPercent
|
|
994
|
+
// Monetary ratios can land a few floating-point bits below an equal model reading.
|
|
995
|
+
const budgetTighter =
|
|
996
|
+
budget &&
|
|
997
|
+
(!tightest ||
|
|
998
|
+
(budgetRemaining !== undefined &&
|
|
999
|
+
tightest.remainingPercent - budgetRemaining > 1e-9))
|
|
1000
|
+
if (budgetTighter) {
|
|
1001
|
+
values.push(`cr ${percent(budgetRemaining)}`)
|
|
1002
|
+
} else if (tightest) {
|
|
1003
|
+
const index = names.indexOf(tightest.modelName)
|
|
1004
|
+
const name = [...tightest.modelName].slice(0, lengths[index]).join('')
|
|
1005
|
+
const part = parts.windows.find(
|
|
1006
|
+
(part) => part.kind === 'window' && part.name === tightest.title,
|
|
1007
|
+
)
|
|
1008
|
+
values.push(`${name} ${percent(part?.remainingPercent)}`)
|
|
1009
|
+
}
|
|
1010
|
+
return `${values.join(' · ')} left`
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function getCollapsedQuotaSummary(
|
|
1014
|
+
quota: AccountQuota | null,
|
|
1015
|
+
now = Date.now(),
|
|
1016
|
+
): {
|
|
907
1017
|
fiveHourUsedPercent: number | null
|
|
908
1018
|
sevenDayUsedPercent: number | null
|
|
909
1019
|
scopedUsedPercents: number[]
|
|
910
1020
|
text: string | null
|
|
1021
|
+
stale: boolean
|
|
911
1022
|
} {
|
|
912
1023
|
const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
|
|
913
1024
|
const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
|
|
914
1025
|
const scoped = quota?.scoped ?? []
|
|
915
1026
|
const scopedUsedPercents = scoped.map((window) => window.usedPercent)
|
|
916
1027
|
if (
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1028
|
+
!quota ||
|
|
1029
|
+
(fiveHourUsedPercent == null &&
|
|
1030
|
+
sevenDayUsedPercent == null &&
|
|
1031
|
+
scoped.length === 0 &&
|
|
1032
|
+
!quota.extraUsage)
|
|
920
1033
|
) {
|
|
921
1034
|
return {
|
|
922
1035
|
fiveHourUsedPercent,
|
|
923
1036
|
sevenDayUsedPercent,
|
|
924
1037
|
scopedUsedPercents,
|
|
925
1038
|
text: null,
|
|
1039
|
+
stale: false,
|
|
926
1040
|
}
|
|
927
1041
|
}
|
|
928
1042
|
|
|
929
|
-
const scopedSegments = scoped.map(
|
|
930
|
-
(window) =>
|
|
931
|
-
`${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
|
|
932
|
-
)
|
|
933
|
-
const primarySegments =
|
|
934
|
-
fiveHourUsedPercent == null && sevenDayUsedPercent == null
|
|
935
|
-
? []
|
|
936
|
-
: [
|
|
937
|
-
`5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
|
|
938
|
-
`7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
|
|
939
|
-
]
|
|
940
|
-
|
|
941
1043
|
return {
|
|
942
1044
|
fiveHourUsedPercent,
|
|
943
1045
|
sevenDayUsedPercent,
|
|
944
1046
|
scopedUsedPercents,
|
|
945
|
-
text:
|
|
1047
|
+
text: formatNarrowQuota(quota, now),
|
|
1048
|
+
stale:
|
|
1049
|
+
quotaWindowParts(quotaDisplayProjection(quota, now), { now }).age !==
|
|
1050
|
+
undefined,
|
|
946
1051
|
}
|
|
947
1052
|
}
|
|
948
1053
|
|
|
1054
|
+
export function getCollapsedAccounts(
|
|
1055
|
+
state: SidebarState,
|
|
1056
|
+
showFallbacks = true,
|
|
1057
|
+
) {
|
|
1058
|
+
const activeId = resolveActiveAccount(state).id
|
|
1059
|
+
return [
|
|
1060
|
+
{
|
|
1061
|
+
id: 'main',
|
|
1062
|
+
name: 'main',
|
|
1063
|
+
quota: state.main?.quota ?? null,
|
|
1064
|
+
quotaDated: state.main?.quotaDated,
|
|
1065
|
+
active: activeId === 'main',
|
|
1066
|
+
},
|
|
1067
|
+
...(showFallbacks
|
|
1068
|
+
? (state.fallbacks ?? [])
|
|
1069
|
+
.filter(
|
|
1070
|
+
(account) =>
|
|
1071
|
+
account.enabled ||
|
|
1072
|
+
(account.custodyState && account.custodyState !== 'off'),
|
|
1073
|
+
)
|
|
1074
|
+
.map((account) => ({
|
|
1075
|
+
id: account.id,
|
|
1076
|
+
name: account.label ?? account.id,
|
|
1077
|
+
quota: account.quota,
|
|
1078
|
+
quotaDated: account.quotaDated,
|
|
1079
|
+
active: activeId === account.id,
|
|
1080
|
+
}))
|
|
1081
|
+
: []),
|
|
1082
|
+
]
|
|
1083
|
+
}
|
|
1084
|
+
|
|
949
1085
|
export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000
|
|
950
1086
|
export const SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000
|
|
951
1087
|
|