@cortexkit/opencode-openai-auth 0.7.2 → 0.9.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/README.md +9 -5
- package/dist/index-301y4qa1.js +2 -0
- package/dist/index-338z7171.js +211 -0
- package/dist/index-5y4vzdf8.js +2 -0
- package/dist/{index-zxqb5ydj.js → index-9ehhb051.js} +1 -1
- package/dist/{index-zg1x8sty.js → index-jwn4r3sw.js} +1 -1
- package/dist/{index-9jpc8yct.js → index-sdt2d2ra.js} +1 -1
- package/dist/index-w42s0g6m.js +2 -0
- package/dist/index.d.ts +213 -36
- package/dist/index.js +59 -154
- package/dist/internal-Y7QNVDJT.d.ts +908 -0
- package/dist/quota-normalize-3qffywcm.js +1 -0
- package/dist/rpc/port-file.d.ts +11 -9
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -21
- package/dist/rpc/rpc-client.d.ts +7 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +7 -5
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-B98sERYt.d.ts +219 -0
- package/dist/sidebar-state.d.ts +2 -171
- package/dist/sidebar-state.js +1 -1
- package/dist/{token-bvp0y166.js → token-9823y0pc.js} +1 -1
- package/dist/token-util-ypgz7rge.js +1 -0
- package/dist/tui-preferences.d.ts +27 -26
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +42 -19
- package/package.json +8 -14
- package/src/tui/entry.mjs +2 -2
- package/src/tui-compiled/{core/account-paths.ts → raw/core-account-paths.ts} +29 -18
- package/src/tui-compiled/raw/core-custody-state.ts +243 -0
- package/src/tui-compiled/raw/logger.ts +53 -0
- package/src/tui-compiled/{rpc/port-file.ts → raw/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/raw/rpc-protocol.ts +12 -0
- package/src/{rpc/rpc-client.ts → tui-compiled/raw/rpc-rpc-client.ts} +2 -2
- package/src/{sidebar-state.ts → tui-compiled/raw/sidebar-state.ts} +142 -13
- package/src/{tui/command-dialogs.tsx → tui-compiled/raw/tui-command-dialogs.tsx} +94 -44
- package/src/{tui.tsx → tui-compiled/raw/tui.tsx} +149 -28
- package/src/{core/account-paths.ts → tui-compiled/runtime/core-account-paths.ts} +29 -18
- package/src/tui-compiled/runtime/core-custody-state.ts +243 -0
- package/src/tui-compiled/runtime/logger.ts +53 -0
- package/src/{rpc/port-file.ts → tui-compiled/runtime/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/runtime/rpc-protocol.ts +12 -0
- package/src/tui-compiled/{rpc/rpc-client.ts → runtime/rpc-rpc-client.ts} +2 -2
- package/src/tui-compiled/{sidebar-state.ts → runtime/sidebar-state.ts} +142 -13
- package/src/tui-compiled/{tui/command-dialogs.tsx → runtime/tui-command-dialogs.tsx} +73 -37
- package/src/tui-compiled/{tui.tsx → runtime/tui.tsx} +251 -153
- package/src/tui-compiled/shared/internal.ts +6 -0
- package/src/{logger.ts → tui-compiled/shared/logger.ts} +91 -23
- package/src/tui-compiled/shared/paths.ts +33 -0
- package/src/{core → tui-compiled/shared}/refresh-file-lock.ts +7 -4
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -15
- package/dist/codex-http.d.ts +0 -3
- package/dist/commands.d.ts +0 -88
- package/dist/config.d.ts +0 -47
- package/dist/core/account-paths.d.ts +0 -25
- package/dist/core/accounts.d.ts +0 -330
- package/dist/core/atomic-write.d.ts +0 -1
- package/dist/core/background-quota-refresh.d.ts +0 -46
- package/dist/core/backoff.d.ts +0 -53
- package/dist/core/cachekeep.d.ts +0 -118
- package/dist/core/oauth.d.ts +0 -138
- package/dist/core/provider.d.ts +0 -70
- package/dist/core/quota-manager.d.ts +0 -192
- package/dist/core/refresh-all-quota.d.ts +0 -86
- package/dist/core/refresh-file-lock.d.ts +0 -12
- package/dist/core/reset-credits.d.ts +0 -116
- package/dist/core/sticky-routing.d.ts +0 -43
- package/dist/dump.d.ts +0 -20
- package/dist/hosted-web-search.d.ts +0 -5
- package/dist/index-87145jkw.js +0 -2
- package/dist/index-8f1200qx.js +0 -6
- package/dist/index-cfjed1st.js +0 -89
- package/dist/index-nrvjr97q.js +0 -3
- package/dist/index-qaggaze1.js +0 -2
- package/dist/index-sdcpzbnk.js +0 -2
- package/dist/logger.d.ts +0 -22
- package/dist/model-costs.d.ts +0 -29
- package/dist/prompt-context.d.ts +0 -19
- package/dist/quota-normalize-dvwtmy7f.js +0 -1
- package/dist/quota-normalize.d.ts +0 -43
- package/dist/raw-ws-bun.d.ts +0 -40
- package/dist/raw-ws-node.d.ts +0 -37
- package/dist/raw-ws-upgrade.d.ts +0 -2
- package/dist/raw-ws.d.ts +0 -3
- package/dist/response-stream-error.d.ts +0 -12
- package/dist/rpc/notifications.d.ts +0 -5
- package/dist/rpc/rpc-server.d.ts +0 -17
- package/dist/token-util-jcj7k7pa.js +0 -1
- package/dist/tui/command-dialogs.d.ts +0 -21
- package/dist/util/error.d.ts +0 -1
- package/dist/util/open-url.d.ts +0 -6
- package/dist/util/proxy-env.d.ts +0 -4
- package/dist/util/record.d.ts +0 -1
- package/dist/util/stable-json.d.ts +0 -1
- package/dist/util/uuid-v7.d.ts +0 -1
- package/dist/version.d.ts +0 -1
- package/dist/ws-pool.d.ts +0 -57
- package/dist/ws.d.ts +0 -71
- package/src/rpc/notifications.ts +0 -54
- package/src/rpc/protocol.ts +0 -33
- package/src/rpc/rpc-server.ts +0 -172
- package/src/tui-compiled/core/refresh-file-lock.ts +0 -381
- package/src/tui-compiled/logger.ts +0 -196
- package/src/tui-compiled/rpc/protocol.ts +0 -33
- package/src/util/error.ts +0 -9
- package/src/util/open-url.ts +0 -30
- package/src/util/proxy-env.ts +0 -42
- package/src/util/record.ts +0 -3
- package/src/util/stable-json.ts +0 -15
- package/src/util/uuid-v7.ts +0 -18
- /package/src/{rpc/rpc-dir.ts → tui-compiled/raw/rpc-rpc-dir.ts} +0 -0
- /package/src/tui-compiled/{tui-preferences.ts → raw/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{rpc/rpc-dir.ts → runtime/rpc-rpc-dir.ts} +0 -0
- /package/src/{tui-preferences.ts → tui-compiled/runtime/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{util → shared/util}/error.ts +0 -0
- /package/src/tui-compiled/{util → shared/util}/open-url.ts +0 -0
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
onCleanup,
|
|
17
17
|
Show,
|
|
18
18
|
} from 'solid-js'
|
|
19
|
-
import { createLogger } from './logger.
|
|
20
|
-
import type { ApplyRequest, CommandModalName } from './rpc
|
|
21
|
-
import { createRpcClient } from './rpc
|
|
22
|
-
import { resolveRpcDir } from './rpc
|
|
19
|
+
import { createLogger } from './logger.ts'
|
|
20
|
+
import type { ApplyRequest, CommandModalName } from './rpc-protocol.ts'
|
|
21
|
+
import { createRpcClient } from './rpc-rpc-client.ts'
|
|
22
|
+
import { resolveRpcDir } from './rpc-rpc-dir.ts'
|
|
23
23
|
import {
|
|
24
24
|
type AccountQuota,
|
|
25
25
|
computeQuotaPacing,
|
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
resolveSessionSidebarRouting,
|
|
34
34
|
resolveSessionStickyAccount,
|
|
35
35
|
type SidebarState,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
type SpendControlReading,
|
|
37
|
+
} from './sidebar-state.ts'
|
|
38
|
+
import { openCommandDialog } from './tui-command-dialogs.tsx'
|
|
38
39
|
import {
|
|
39
40
|
type AppearancePrefs,
|
|
40
41
|
computeEffectiveOrder,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
readTuiPreferencesFile,
|
|
46
47
|
resolveOpenaiAuthPrefs,
|
|
47
48
|
watchTuiPreferences,
|
|
48
|
-
} from './tui-preferences.
|
|
49
|
+
} from './tui-preferences.ts'
|
|
49
50
|
|
|
50
51
|
const RPC_POLL_MS = 500
|
|
51
52
|
let rpcPollStarted = false
|
|
@@ -61,14 +62,15 @@ export function buildApplyRequest(
|
|
|
61
62
|
return { command, arguments: arguments_, sessionId }
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
// Read package metadata from
|
|
65
|
-
//
|
|
66
|
-
//
|
|
65
|
+
// Read package metadata from the development source or either generated TUI
|
|
66
|
+
// variant. Avoid a JSON import because package.json sits outside the declaration
|
|
67
|
+
// build's rootDir.
|
|
67
68
|
const PLUGIN_VERSION: string = (() => {
|
|
68
69
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
69
70
|
for (const packageFile of [
|
|
70
71
|
join(here, '..', 'package.json'),
|
|
71
72
|
join(here, '..', '..', 'package.json'),
|
|
73
|
+
join(here, '..', '..', '..', 'package.json'),
|
|
72
74
|
]) {
|
|
73
75
|
try {
|
|
74
76
|
const raw = readFileSync(packageFile, 'utf8')
|
|
@@ -240,28 +242,97 @@ function CollapsedRow(props: {
|
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
export interface QuotaDisplayRow {
|
|
243
|
-
key: 'primary' | 'secondary'
|
|
245
|
+
key: 'primary' | 'secondary' | 'spendControl'
|
|
244
246
|
label: string
|
|
247
|
+
labelWidth: number
|
|
245
248
|
window: QuotaWindow
|
|
246
249
|
pacing: QuotaPacing | null
|
|
247
250
|
}
|
|
248
251
|
|
|
252
|
+
const SPEND_CONTROL_LABEL = 'credits'
|
|
253
|
+
|
|
254
|
+
// Labels a quota snapshot contributes to the sidebar, independent of pacing or
|
|
255
|
+
// the wall clock — the label column is sized from these alone.
|
|
256
|
+
function quotaRowLabels(quota: AccountQuota | null): string[] {
|
|
257
|
+
const labels = getPresentQuotaWindows(quota).map((row) => row.label)
|
|
258
|
+
if (quota?.spendControl) labels.push(SPEND_CONTROL_LABEL)
|
|
259
|
+
return labels
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The label column is a property of the whole sidebar, not of one account's row
|
|
263
|
+
// set: every account's bars must start at the same column, so the width is
|
|
264
|
+
// derived once from every account rendered (main plus fallbacks) and threaded
|
|
265
|
+
// into each account's rows. The +1 keeps a separator before the longest label's
|
|
266
|
+
// bar; 3 is the floor the short 5h/7d labels already relied on.
|
|
267
|
+
/**
|
|
268
|
+
* Every quota the sidebar will draw, in one list.
|
|
269
|
+
*
|
|
270
|
+
* The label column is shared by all of them, so the width has to be measured
|
|
271
|
+
* over this exact set — measuring a subset is what made stacked accounts
|
|
272
|
+
* disagree about where the bar starts. Named rather than inlined at each call
|
|
273
|
+
* site because the set is the part that can be got wrong, and a call site
|
|
274
|
+
* cannot be tested without the host's rendering runtime.
|
|
275
|
+
*/
|
|
276
|
+
export function renderedQuotas(state: {
|
|
277
|
+
main?: { quota?: AccountQuota | null } | null
|
|
278
|
+
fallbacks?: ReadonlyArray<{ enabled?: boolean; quota?: AccountQuota | null }>
|
|
279
|
+
}): ReadonlyArray<AccountQuota | null | undefined> {
|
|
280
|
+
return [
|
|
281
|
+
state.main?.quota ?? null,
|
|
282
|
+
...(state.fallbacks ?? [])
|
|
283
|
+
.filter((fallback) => fallback.enabled)
|
|
284
|
+
.map((fallback) => fallback.quota),
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function computeQuotaLabelWidth(
|
|
289
|
+
quotas: ReadonlyArray<AccountQuota | null | undefined>,
|
|
290
|
+
): number {
|
|
291
|
+
let width = 3
|
|
292
|
+
for (const quota of quotas) {
|
|
293
|
+
for (const label of quotaRowLabels(quota ?? null)) {
|
|
294
|
+
width = Math.max(width, label.length + 1)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return width
|
|
298
|
+
}
|
|
299
|
+
|
|
249
300
|
// Maps present quota windows onto display rows and computes pacing against each
|
|
250
301
|
// dynamic duration or the historical 5h/7d duration for legacy snapshots.
|
|
302
|
+
// `labelWidth` comes from computeQuotaLabelWidth for rendered sidebars; callers
|
|
303
|
+
// that only inspect pacing may omit it and get this row set's own width.
|
|
251
304
|
export function buildQuotaRowsForDisplay(
|
|
252
305
|
quota: AccountQuota | null,
|
|
253
306
|
now: number,
|
|
254
307
|
pacingEnabled: boolean,
|
|
308
|
+
labelWidth?: number,
|
|
255
309
|
): QuotaDisplayRow[] {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
310
|
+
const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
|
|
311
|
+
getPresentQuotaWindows(quota).map((row) => ({
|
|
312
|
+
key: row.key,
|
|
313
|
+
label: row.label,
|
|
314
|
+
window: row.window,
|
|
315
|
+
pacing:
|
|
316
|
+
pacingEnabled && row.windowMs !== null
|
|
317
|
+
? computeQuotaPacing(row.window, row.windowMs, now)
|
|
318
|
+
: null,
|
|
319
|
+
}))
|
|
320
|
+
const spendControl = quota?.spendControl
|
|
321
|
+
if (spendControl) {
|
|
322
|
+
rows.push({
|
|
323
|
+
key: 'spendControl',
|
|
324
|
+
label: SPEND_CONTROL_LABEL,
|
|
325
|
+
window: {
|
|
326
|
+
usedPercent: spendControl.usedPercent,
|
|
327
|
+
remainingPercent: spendControl.remainingPercent,
|
|
328
|
+
resetsAt: spendControl.resetsAt,
|
|
329
|
+
},
|
|
330
|
+
pacing: null,
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
const width =
|
|
334
|
+
labelWidth ?? Math.max(3, ...rows.map((row) => row.label.length + 1))
|
|
335
|
+
return rows.map((row) => ({ ...row, labelWidth: width }))
|
|
265
336
|
}
|
|
266
337
|
|
|
267
338
|
export function isQuotaLoaded(quota: AccountQuota | null): boolean {
|
|
@@ -279,12 +350,39 @@ export function getQuotaMetadataRows(
|
|
|
279
350
|
return rows
|
|
280
351
|
}
|
|
281
352
|
|
|
353
|
+
function formatGroupedAmount(value: number): string {
|
|
354
|
+
return Math.round(value).toLocaleString('en-US')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// The unit names the budget the amounts are counted in, so it agrees with the
|
|
358
|
+
// total rather than staying singular against a plural quantity.
|
|
359
|
+
function pluralizeUnit(unit: string, count: number): string {
|
|
360
|
+
if (count === 1) return unit
|
|
361
|
+
return unit.endsWith('s') ? unit : `${unit}s`
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function formatSpendControlAmounts(
|
|
365
|
+
spendControl: SpendControlReading,
|
|
366
|
+
): string {
|
|
367
|
+
const unit = pluralizeUnit(spendControl.unit ?? 'unit', spendControl.limit)
|
|
368
|
+
return `${formatGroupedAmount(spendControl.used)} / ${formatGroupedAmount(spendControl.limit)} ${unit}`
|
|
369
|
+
}
|
|
370
|
+
|
|
282
371
|
export function getAccountMetadataRows(
|
|
283
372
|
resetCredits: number | undefined,
|
|
373
|
+
spendControl?: SpendControlReading,
|
|
284
374
|
): Array<{ label: string; value: string }> {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
375
|
+
const rows: Array<{ label: string; value: string }> = []
|
|
376
|
+
if (spendControl) {
|
|
377
|
+
rows.push({
|
|
378
|
+
label: 'credits',
|
|
379
|
+
value: formatSpendControlAmounts(spendControl),
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
if (resetCredits !== undefined) {
|
|
383
|
+
rows.push({ label: 'resets', value: String(resetCredits) })
|
|
384
|
+
}
|
|
385
|
+
return rows
|
|
288
386
|
}
|
|
289
387
|
|
|
290
388
|
// Quota window row: muted label left, tone-colored bar + percentage right,
|
|
@@ -295,6 +393,7 @@ function QuotaRow(props: {
|
|
|
295
393
|
theme: ThemeCurrent
|
|
296
394
|
appearance: AppearancePrefs
|
|
297
395
|
label: string
|
|
396
|
+
labelWidth: number
|
|
298
397
|
window: { usedPercent: number; resetsAt?: string } | undefined
|
|
299
398
|
pacing: QuotaPacing | null
|
|
300
399
|
}) {
|
|
@@ -314,7 +413,9 @@ function QuotaRow(props: {
|
|
|
314
413
|
when={props.window}
|
|
315
414
|
fallback={
|
|
316
415
|
<box width='100%' flexDirection='row'>
|
|
317
|
-
<text fg={props.theme.textMuted}>
|
|
416
|
+
<text fg={props.theme.textMuted}>
|
|
417
|
+
{props.label.padEnd(props.labelWidth)}
|
|
418
|
+
</text>
|
|
318
419
|
<text fg={props.theme.textMuted}>{'\u2014'}</text>
|
|
319
420
|
</box>
|
|
320
421
|
}
|
|
@@ -324,7 +425,9 @@ function QuotaRow(props: {
|
|
|
324
425
|
the right edge so reset times align in their own right column. */}
|
|
325
426
|
<box width='100%' flexDirection='row' justifyContent='space-between'>
|
|
326
427
|
<box flexDirection='row'>
|
|
327
|
-
<text fg={props.theme.textMuted}>
|
|
428
|
+
<text fg={props.theme.textMuted}>
|
|
429
|
+
{props.label.padEnd(props.labelWidth)}
|
|
430
|
+
</text>
|
|
328
431
|
<For each={quotaBarSegments(used(), props.appearance, props.pacing)}>
|
|
329
432
|
{(segment) => (
|
|
330
433
|
<text fg={toneColor(props.theme, segment.tone)}>
|
|
@@ -344,7 +447,7 @@ function QuotaRow(props: {
|
|
|
344
447
|
</box>
|
|
345
448
|
<Show when={paceLine()}>
|
|
346
449
|
<box width='100%' flexDirection='row'>
|
|
347
|
-
<text fg={props.theme.textMuted}>{'
|
|
450
|
+
<text fg={props.theme.textMuted}>{''.padEnd(props.labelWidth)}</text>
|
|
348
451
|
<text
|
|
349
452
|
fg={toneColor(
|
|
350
453
|
props.theme,
|
|
@@ -370,6 +473,7 @@ function AccountBlock(props: {
|
|
|
370
473
|
killed: boolean
|
|
371
474
|
active: boolean
|
|
372
475
|
pacingEnabled: boolean
|
|
476
|
+
labelWidth: number
|
|
373
477
|
resetCredits?: number
|
|
374
478
|
marginTop?: number
|
|
375
479
|
}) {
|
|
@@ -378,7 +482,12 @@ function AccountBlock(props: {
|
|
|
378
482
|
const statusTone = (): Tone =>
|
|
379
483
|
props.killed ? 'err' : props.active ? 'ok' : 'muted'
|
|
380
484
|
const rows = () =>
|
|
381
|
-
buildQuotaRowsForDisplay(
|
|
485
|
+
buildQuotaRowsForDisplay(
|
|
486
|
+
props.quota,
|
|
487
|
+
Date.now(),
|
|
488
|
+
props.pacingEnabled,
|
|
489
|
+
props.labelWidth,
|
|
490
|
+
)
|
|
382
491
|
return (
|
|
383
492
|
<box width='100%' flexDirection='column' marginTop={props.marginTop ?? 0}>
|
|
384
493
|
<box width='100%' flexDirection='row' justifyContent='space-between'>
|
|
@@ -403,6 +512,7 @@ function AccountBlock(props: {
|
|
|
403
512
|
theme={props.theme}
|
|
404
513
|
appearance={props.appearance}
|
|
405
514
|
label={row.label}
|
|
515
|
+
labelWidth={row.labelWidth}
|
|
406
516
|
window={row.window}
|
|
407
517
|
pacing={row.pacing}
|
|
408
518
|
/>
|
|
@@ -410,7 +520,12 @@ function AccountBlock(props: {
|
|
|
410
520
|
</For>
|
|
411
521
|
</Show>
|
|
412
522
|
</Show>
|
|
413
|
-
<For
|
|
523
|
+
<For
|
|
524
|
+
each={getAccountMetadataRows(
|
|
525
|
+
props.resetCredits,
|
|
526
|
+
props.quota?.spendControl,
|
|
527
|
+
)}
|
|
528
|
+
>
|
|
414
529
|
{(row) => (
|
|
415
530
|
<StatRow
|
|
416
531
|
theme={props.theme}
|
|
@@ -452,6 +567,7 @@ function QuotaDialogContent(props: {
|
|
|
452
567
|
const enabledFallbacks = () =>
|
|
453
568
|
(state().fallbacks ?? []).filter((f) => f.enabled)
|
|
454
569
|
const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId)
|
|
570
|
+
const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
|
|
455
571
|
return (
|
|
456
572
|
<box flexDirection='column' padding={2} width='100%' alignItems='center'>
|
|
457
573
|
<box flexDirection='column' width={58}>
|
|
@@ -468,6 +584,7 @@ function QuotaDialogContent(props: {
|
|
|
468
584
|
killed={state().main?.killed ?? false}
|
|
469
585
|
active={activeId() === 'main'}
|
|
470
586
|
pacingEnabled={prefs().sections.pacing}
|
|
587
|
+
labelWidth={quotaLabelWidth()}
|
|
471
588
|
resetCredits={state().main?.resetCredits}
|
|
472
589
|
/>
|
|
473
590
|
<Show when={prefs().sections.fallbackAccounts}>
|
|
@@ -481,6 +598,7 @@ function QuotaDialogContent(props: {
|
|
|
481
598
|
killed={fb.killed}
|
|
482
599
|
active={activeId() === fb.id}
|
|
483
600
|
pacingEnabled={prefs().sections.pacing}
|
|
601
|
+
labelWidth={quotaLabelWidth()}
|
|
484
602
|
resetCredits={fb.resetCredits}
|
|
485
603
|
marginTop={1}
|
|
486
604
|
/>
|
|
@@ -661,6 +779,7 @@ function QuotaSidebar(props: {
|
|
|
661
779
|
route: sessionRouting().route,
|
|
662
780
|
})
|
|
663
781
|
const activeAccount = () => resolveActiveAccount(sessionState())
|
|
782
|
+
const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
|
|
664
783
|
const activeQuotaSummary = () =>
|
|
665
784
|
getCollapsedQuotaSummary(activeAccount().quota)
|
|
666
785
|
const activePacingDeficit = () => {
|
|
@@ -793,6 +912,7 @@ function QuotaSidebar(props: {
|
|
|
793
912
|
killed={state().main?.killed ?? false}
|
|
794
913
|
active={sessionRouting().activeId === 'main'}
|
|
795
914
|
pacingEnabled={prefs().sections.pacing}
|
|
915
|
+
labelWidth={quotaLabelWidth()}
|
|
796
916
|
resetCredits={state().main?.resetCredits}
|
|
797
917
|
/>
|
|
798
918
|
<Show when={prefs().sections.fallbackAccounts}>
|
|
@@ -806,6 +926,7 @@ function QuotaSidebar(props: {
|
|
|
806
926
|
killed={fb.killed}
|
|
807
927
|
active={sessionRouting().activeId === fb.id}
|
|
808
928
|
pacingEnabled={prefs().sections.pacing}
|
|
929
|
+
labelWidth={quotaLabelWidth()}
|
|
809
930
|
resetCredits={fb.resetCredits}
|
|
810
931
|
marginTop={1}
|
|
811
932
|
/>
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs'
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
4
|
+
import {
|
|
5
|
+
ACCOUNT_FILE_NAME,
|
|
6
|
+
ACCOUNT_STATE_FILE_NAME,
|
|
7
|
+
type AccountPaths,
|
|
8
|
+
deriveStatePath,
|
|
9
|
+
} from '../shared/internal.ts'
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
* Where
|
|
12
|
+
* Where OpenCode's account store lives.
|
|
7
13
|
*
|
|
8
|
-
* This is the ONLY definition
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* path while writing another — mutual exclusion silently lost on a file holding
|
|
14
|
-
* credentials. Nothing would fail a test, since each copy works on its own.
|
|
14
|
+
* This is the ONLY definition of this host's resolution rules. The store code
|
|
15
|
+
* itself takes both paths as arguments and resolves nothing, so the lock and
|
|
16
|
+
* the write it guards can never disagree about which file they mean — a
|
|
17
|
+
* disagreement would lose mutual exclusion on a file holding credentials while
|
|
18
|
+
* every test still passed, since each side works on its own.
|
|
15
19
|
*
|
|
16
|
-
*
|
|
20
|
+
* The file names come from the core so the two hosts cannot drift apart on what
|
|
21
|
+
* the store is called.
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
|
-
export
|
|
20
|
-
export
|
|
24
|
+
export type { AccountPaths }
|
|
25
|
+
export { ACCOUNT_FILE_NAME, ACCOUNT_STATE_FILE_NAME, deriveStatePath }
|
|
21
26
|
|
|
22
27
|
function getConfigDir() {
|
|
23
28
|
if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
|
|
@@ -36,13 +41,6 @@ export function getAccountStoragePath() {
|
|
|
36
41
|
)
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
/** Derive the state-file path from the config path without reading env vars. */
|
|
40
|
-
export function deriveStatePath(configPath: string): string {
|
|
41
|
-
return basename(configPath) === ACCOUNT_FILE_NAME
|
|
42
|
-
? join(dirname(configPath), ACCOUNT_STATE_FILE_NAME)
|
|
43
|
-
: `${configPath}.state.json`
|
|
44
|
-
}
|
|
45
|
-
|
|
46
44
|
function normalizePathForComparison(path: string, platform: NodeJS.Platform) {
|
|
47
45
|
const resolved = resolve(path)
|
|
48
46
|
return platform === 'win32' || platform === 'darwin'
|
|
@@ -101,3 +99,16 @@ export function getAccountStatePath(configPath = getAccountStoragePath()) {
|
|
|
101
99
|
}
|
|
102
100
|
return deriveStatePath(configPath)
|
|
103
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Both file paths a store call needs, resolved the way this host resolves them.
|
|
105
|
+
*
|
|
106
|
+
* Every call site that used to pass a single config path and let the store
|
|
107
|
+
* derive the state path now passes this pair instead, so the state-file
|
|
108
|
+
* override keeps being honoured in exactly the places it was before.
|
|
109
|
+
*/
|
|
110
|
+
export function getAccountPaths(
|
|
111
|
+
configPath = getAccountStoragePath(),
|
|
112
|
+
): AccountPaths {
|
|
113
|
+
return { configPath, statePath: getAccountStatePath(configPath) }
|
|
114
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export type CustodyMode = 'local' | 'claustrum'
|
|
2
|
+
|
|
3
|
+
export type ManifestCustodyState = 'absent' | 'present' | 'unreadable'
|
|
4
|
+
|
|
5
|
+
export type LocalCustodyState =
|
|
6
|
+
| 'real'
|
|
7
|
+
| 'tombstone'
|
|
8
|
+
| 'empty'
|
|
9
|
+
| 'gone'
|
|
10
|
+
| 'slot-absent'
|
|
11
|
+
|
|
12
|
+
export type VaultCustodyState =
|
|
13
|
+
| 'serves'
|
|
14
|
+
| 'cold'
|
|
15
|
+
| 'needs_reauth'
|
|
16
|
+
| 'identity_mismatch'
|
|
17
|
+
| 'no_handle'
|
|
18
|
+
|
|
19
|
+
export const CUSTODY_INERT_REASONS = [
|
|
20
|
+
'needs-login',
|
|
21
|
+
'mode-mismatch',
|
|
22
|
+
'corrupt-under-binding',
|
|
23
|
+
'manifest-unreadable',
|
|
24
|
+
'unbound-under-claustrum',
|
|
25
|
+
'orphan-tombstone',
|
|
26
|
+
'vault-cold',
|
|
27
|
+
'vault-reauth',
|
|
28
|
+
'identity-mismatch',
|
|
29
|
+
'no-handle',
|
|
30
|
+
'takeover-incomplete',
|
|
31
|
+
'new-local-family-under-claustrum',
|
|
32
|
+
'takeover-incomplete/vault-unavailable',
|
|
33
|
+
'takeover-incomplete/slot-absent',
|
|
34
|
+
] as const
|
|
35
|
+
|
|
36
|
+
export type CustodyInertReason = (typeof CUSTODY_INERT_REASONS)[number]
|
|
37
|
+
|
|
38
|
+
export type CustodyVerdict =
|
|
39
|
+
| { kind: 'LOCAL' }
|
|
40
|
+
| {
|
|
41
|
+
kind: 'VAULT'
|
|
42
|
+
canonicalize?: boolean
|
|
43
|
+
installTombstone?: boolean
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
kind: 'INERT'
|
|
47
|
+
reason: CustodyInertReason
|
|
48
|
+
installTombstone?: boolean
|
|
49
|
+
canonicalize?: boolean
|
|
50
|
+
}
|
|
51
|
+
| { kind: 'NEEDS_LOGIN'; reason?: 'corrupt' }
|
|
52
|
+
|
|
53
|
+
export interface EvaluateCustodyStartupInput {
|
|
54
|
+
mode: CustodyMode
|
|
55
|
+
manifest: ManifestCustodyState
|
|
56
|
+
local: LocalCustodyState
|
|
57
|
+
vault: () => VaultCustodyState
|
|
58
|
+
manifestFailureReason?: string
|
|
59
|
+
fingerprintMatch?: boolean
|
|
60
|
+
verifiedInProcessLogin: boolean
|
|
61
|
+
isMain?: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unreachable(value: never): never {
|
|
65
|
+
return value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function withCanonicalization<
|
|
69
|
+
T extends Extract<CustodyVerdict, { kind: 'VAULT' | 'INERT' }>,
|
|
70
|
+
>(local: LocalCustodyState, verdict: T): T | (T & { canonicalize: true }) {
|
|
71
|
+
return local === 'empty' ? { ...verdict, canonicalize: true } : verdict
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function evaluateLocal(local: LocalCustodyState): CustodyVerdict {
|
|
75
|
+
switch (local) {
|
|
76
|
+
case 'real':
|
|
77
|
+
return { kind: 'LOCAL' }
|
|
78
|
+
case 'tombstone':
|
|
79
|
+
case 'empty':
|
|
80
|
+
case 'slot-absent':
|
|
81
|
+
return { kind: 'NEEDS_LOGIN' }
|
|
82
|
+
case 'gone':
|
|
83
|
+
return { kind: 'NEEDS_LOGIN', reason: 'corrupt' }
|
|
84
|
+
}
|
|
85
|
+
return unreachable(local)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function evaluateLocalWithBinding(
|
|
89
|
+
local: LocalCustodyState,
|
|
90
|
+
verifiedInProcessLogin: boolean,
|
|
91
|
+
): CustodyVerdict {
|
|
92
|
+
switch (local) {
|
|
93
|
+
case 'real':
|
|
94
|
+
return verifiedInProcessLogin
|
|
95
|
+
? { kind: 'LOCAL' }
|
|
96
|
+
: { kind: 'INERT', reason: 'needs-login' }
|
|
97
|
+
case 'tombstone':
|
|
98
|
+
case 'empty':
|
|
99
|
+
case 'slot-absent':
|
|
100
|
+
return { kind: 'INERT', reason: 'mode-mismatch' }
|
|
101
|
+
case 'gone':
|
|
102
|
+
return { kind: 'INERT', reason: 'corrupt-under-binding' }
|
|
103
|
+
}
|
|
104
|
+
return unreachable(local)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function evaluateUnboundClaustrum(local: LocalCustodyState): CustodyVerdict {
|
|
108
|
+
switch (local) {
|
|
109
|
+
case 'tombstone':
|
|
110
|
+
case 'empty':
|
|
111
|
+
return { kind: 'INERT', reason: 'orphan-tombstone' }
|
|
112
|
+
case 'real':
|
|
113
|
+
case 'gone':
|
|
114
|
+
case 'slot-absent':
|
|
115
|
+
return { kind: 'INERT', reason: 'unbound-under-claustrum' }
|
|
116
|
+
}
|
|
117
|
+
return unreachable(local)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function evaluateTombstone(
|
|
121
|
+
local: Extract<LocalCustodyState, 'tombstone' | 'empty'>,
|
|
122
|
+
vault: VaultCustodyState,
|
|
123
|
+
): CustodyVerdict {
|
|
124
|
+
switch (vault) {
|
|
125
|
+
case 'serves':
|
|
126
|
+
return withCanonicalization(local, { kind: 'VAULT' })
|
|
127
|
+
case 'cold':
|
|
128
|
+
return withCanonicalization(local, {
|
|
129
|
+
kind: 'INERT',
|
|
130
|
+
reason: 'vault-cold',
|
|
131
|
+
})
|
|
132
|
+
case 'needs_reauth':
|
|
133
|
+
return withCanonicalization(local, {
|
|
134
|
+
kind: 'INERT',
|
|
135
|
+
reason: 'vault-reauth',
|
|
136
|
+
})
|
|
137
|
+
case 'identity_mismatch':
|
|
138
|
+
return withCanonicalization(local, {
|
|
139
|
+
kind: 'INERT',
|
|
140
|
+
reason: 'identity-mismatch',
|
|
141
|
+
})
|
|
142
|
+
case 'no_handle':
|
|
143
|
+
return withCanonicalization(local, { kind: 'INERT', reason: 'no-handle' })
|
|
144
|
+
}
|
|
145
|
+
return unreachable(vault)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function evaluateReal(
|
|
149
|
+
vault: VaultCustodyState,
|
|
150
|
+
fingerprintMatch: boolean | undefined,
|
|
151
|
+
): CustodyVerdict {
|
|
152
|
+
switch (vault) {
|
|
153
|
+
case 'serves':
|
|
154
|
+
return fingerprintMatch === false
|
|
155
|
+
? { kind: 'INERT', reason: 'new-local-family-under-claustrum' }
|
|
156
|
+
: { kind: 'INERT', reason: 'takeover-incomplete' }
|
|
157
|
+
case 'cold':
|
|
158
|
+
case 'needs_reauth':
|
|
159
|
+
return { kind: 'INERT', reason: 'takeover-incomplete/vault-unavailable' }
|
|
160
|
+
case 'identity_mismatch':
|
|
161
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
162
|
+
case 'no_handle':
|
|
163
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
164
|
+
}
|
|
165
|
+
return unreachable(vault)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function evaluateGone(vault: VaultCustodyState): CustodyVerdict {
|
|
169
|
+
switch (vault) {
|
|
170
|
+
case 'serves':
|
|
171
|
+
return { kind: 'VAULT', installTombstone: true }
|
|
172
|
+
case 'cold':
|
|
173
|
+
return { kind: 'INERT', reason: 'vault-cold', installTombstone: true }
|
|
174
|
+
case 'needs_reauth':
|
|
175
|
+
return { kind: 'INERT', reason: 'vault-reauth', installTombstone: true }
|
|
176
|
+
case 'identity_mismatch':
|
|
177
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
178
|
+
case 'no_handle':
|
|
179
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
180
|
+
}
|
|
181
|
+
return unreachable(vault)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function evaluateSlotAbsent(vault: VaultCustodyState): CustodyVerdict {
|
|
185
|
+
switch (vault) {
|
|
186
|
+
case 'serves':
|
|
187
|
+
case 'cold':
|
|
188
|
+
case 'needs_reauth':
|
|
189
|
+
return { kind: 'INERT', reason: 'takeover-incomplete/slot-absent' }
|
|
190
|
+
case 'identity_mismatch':
|
|
191
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
192
|
+
case 'no_handle':
|
|
193
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
194
|
+
}
|
|
195
|
+
return unreachable(vault)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function evaluatePresentClaustrum(
|
|
199
|
+
local: LocalCustodyState,
|
|
200
|
+
vault: VaultCustodyState,
|
|
201
|
+
fingerprintMatch: boolean | undefined,
|
|
202
|
+
): CustodyVerdict {
|
|
203
|
+
switch (local) {
|
|
204
|
+
case 'tombstone':
|
|
205
|
+
case 'empty':
|
|
206
|
+
return evaluateTombstone(local, vault)
|
|
207
|
+
case 'real':
|
|
208
|
+
return evaluateReal(vault, fingerprintMatch)
|
|
209
|
+
case 'gone':
|
|
210
|
+
return evaluateGone(vault)
|
|
211
|
+
case 'slot-absent':
|
|
212
|
+
return evaluateSlotAbsent(vault)
|
|
213
|
+
}
|
|
214
|
+
return unreachable(local)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function evaluateCustodyStartup(
|
|
218
|
+
input: EvaluateCustodyStartupInput,
|
|
219
|
+
): CustodyVerdict {
|
|
220
|
+
if (input.manifest === 'unreadable') {
|
|
221
|
+
return { kind: 'INERT', reason: 'manifest-unreadable' }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (input.mode) {
|
|
225
|
+
case 'local':
|
|
226
|
+
return input.manifest === 'present'
|
|
227
|
+
? evaluateLocalWithBinding(input.local, input.verifiedInProcessLogin)
|
|
228
|
+
: evaluateLocal(input.local)
|
|
229
|
+
case 'claustrum':
|
|
230
|
+
if (input.manifest === 'absent') {
|
|
231
|
+
return evaluateUnboundClaustrum(input.local)
|
|
232
|
+
}
|
|
233
|
+
if (input.manifest === 'present') {
|
|
234
|
+
return evaluatePresentClaustrum(
|
|
235
|
+
input.local,
|
|
236
|
+
input.vault(),
|
|
237
|
+
input.fingerprintMatch,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
return unreachable(input.manifest)
|
|
241
|
+
}
|
|
242
|
+
return unreachable(input.mode)
|
|
243
|
+
}
|