@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.
Files changed (118) hide show
  1. package/README.md +9 -5
  2. package/dist/index-301y4qa1.js +2 -0
  3. package/dist/index-338z7171.js +211 -0
  4. package/dist/index-5y4vzdf8.js +2 -0
  5. package/dist/{index-zxqb5ydj.js → index-9ehhb051.js} +1 -1
  6. package/dist/{index-zg1x8sty.js → index-jwn4r3sw.js} +1 -1
  7. package/dist/{index-9jpc8yct.js → index-sdt2d2ra.js} +1 -1
  8. package/dist/index-w42s0g6m.js +2 -0
  9. package/dist/index.d.ts +213 -36
  10. package/dist/index.js +59 -154
  11. package/dist/internal-Y7QNVDJT.d.ts +908 -0
  12. package/dist/quota-normalize-3qffywcm.js +1 -0
  13. package/dist/rpc/port-file.d.ts +11 -9
  14. package/dist/rpc/port-file.js +1 -1
  15. package/dist/rpc/protocol.d.ts +2 -21
  16. package/dist/rpc/rpc-client.d.ts +7 -4
  17. package/dist/rpc/rpc-client.js +1 -1
  18. package/dist/rpc/rpc-dir.d.ts +7 -5
  19. package/dist/rpc/rpc-dir.js +1 -1
  20. package/dist/sidebar-state-B98sERYt.d.ts +219 -0
  21. package/dist/sidebar-state.d.ts +2 -171
  22. package/dist/sidebar-state.js +1 -1
  23. package/dist/{token-bvp0y166.js → token-9823y0pc.js} +1 -1
  24. package/dist/token-util-ypgz7rge.js +1 -0
  25. package/dist/tui-preferences.d.ts +27 -26
  26. package/dist/tui-preferences.js +1 -1
  27. package/dist/tui.d.ts +42 -19
  28. package/package.json +8 -14
  29. package/src/tui/entry.mjs +2 -2
  30. package/src/tui-compiled/{core/account-paths.ts → raw/core-account-paths.ts} +29 -18
  31. package/src/tui-compiled/raw/core-custody-state.ts +243 -0
  32. package/src/tui-compiled/raw/logger.ts +53 -0
  33. package/src/tui-compiled/{rpc/port-file.ts → raw/rpc-port-file.ts} +1 -1
  34. package/src/tui-compiled/raw/rpc-protocol.ts +12 -0
  35. package/src/{rpc/rpc-client.ts → tui-compiled/raw/rpc-rpc-client.ts} +2 -2
  36. package/src/{sidebar-state.ts → tui-compiled/raw/sidebar-state.ts} +142 -13
  37. package/src/{tui/command-dialogs.tsx → tui-compiled/raw/tui-command-dialogs.tsx} +94 -44
  38. package/src/{tui.tsx → tui-compiled/raw/tui.tsx} +149 -28
  39. package/src/{core/account-paths.ts → tui-compiled/runtime/core-account-paths.ts} +29 -18
  40. package/src/tui-compiled/runtime/core-custody-state.ts +243 -0
  41. package/src/tui-compiled/runtime/logger.ts +53 -0
  42. package/src/{rpc/port-file.ts → tui-compiled/runtime/rpc-port-file.ts} +1 -1
  43. package/src/tui-compiled/runtime/rpc-protocol.ts +12 -0
  44. package/src/tui-compiled/{rpc/rpc-client.ts → runtime/rpc-rpc-client.ts} +2 -2
  45. package/src/tui-compiled/{sidebar-state.ts → runtime/sidebar-state.ts} +142 -13
  46. package/src/tui-compiled/{tui/command-dialogs.tsx → runtime/tui-command-dialogs.tsx} +73 -37
  47. package/src/tui-compiled/{tui.tsx → runtime/tui.tsx} +251 -153
  48. package/src/tui-compiled/shared/internal.ts +6 -0
  49. package/src/{logger.ts → tui-compiled/shared/logger.ts} +91 -23
  50. package/src/tui-compiled/shared/paths.ts +33 -0
  51. package/src/{core → tui-compiled/shared}/refresh-file-lock.ts +7 -4
  52. package/dist/cli.d.ts +0 -2
  53. package/dist/cli.js +0 -15
  54. package/dist/codex-http.d.ts +0 -3
  55. package/dist/commands.d.ts +0 -88
  56. package/dist/config.d.ts +0 -47
  57. package/dist/core/account-paths.d.ts +0 -25
  58. package/dist/core/accounts.d.ts +0 -330
  59. package/dist/core/atomic-write.d.ts +0 -1
  60. package/dist/core/background-quota-refresh.d.ts +0 -46
  61. package/dist/core/backoff.d.ts +0 -53
  62. package/dist/core/cachekeep.d.ts +0 -118
  63. package/dist/core/oauth.d.ts +0 -138
  64. package/dist/core/provider.d.ts +0 -70
  65. package/dist/core/quota-manager.d.ts +0 -192
  66. package/dist/core/refresh-all-quota.d.ts +0 -86
  67. package/dist/core/refresh-file-lock.d.ts +0 -12
  68. package/dist/core/reset-credits.d.ts +0 -116
  69. package/dist/core/sticky-routing.d.ts +0 -43
  70. package/dist/dump.d.ts +0 -20
  71. package/dist/hosted-web-search.d.ts +0 -5
  72. package/dist/index-87145jkw.js +0 -2
  73. package/dist/index-8f1200qx.js +0 -6
  74. package/dist/index-cfjed1st.js +0 -89
  75. package/dist/index-nrvjr97q.js +0 -3
  76. package/dist/index-qaggaze1.js +0 -2
  77. package/dist/index-sdcpzbnk.js +0 -2
  78. package/dist/logger.d.ts +0 -22
  79. package/dist/model-costs.d.ts +0 -29
  80. package/dist/prompt-context.d.ts +0 -19
  81. package/dist/quota-normalize-dvwtmy7f.js +0 -1
  82. package/dist/quota-normalize.d.ts +0 -43
  83. package/dist/raw-ws-bun.d.ts +0 -40
  84. package/dist/raw-ws-node.d.ts +0 -37
  85. package/dist/raw-ws-upgrade.d.ts +0 -2
  86. package/dist/raw-ws.d.ts +0 -3
  87. package/dist/response-stream-error.d.ts +0 -12
  88. package/dist/rpc/notifications.d.ts +0 -5
  89. package/dist/rpc/rpc-server.d.ts +0 -17
  90. package/dist/token-util-jcj7k7pa.js +0 -1
  91. package/dist/tui/command-dialogs.d.ts +0 -21
  92. package/dist/util/error.d.ts +0 -1
  93. package/dist/util/open-url.d.ts +0 -6
  94. package/dist/util/proxy-env.d.ts +0 -4
  95. package/dist/util/record.d.ts +0 -1
  96. package/dist/util/stable-json.d.ts +0 -1
  97. package/dist/util/uuid-v7.d.ts +0 -1
  98. package/dist/version.d.ts +0 -1
  99. package/dist/ws-pool.d.ts +0 -57
  100. package/dist/ws.d.ts +0 -71
  101. package/src/rpc/notifications.ts +0 -54
  102. package/src/rpc/protocol.ts +0 -33
  103. package/src/rpc/rpc-server.ts +0 -172
  104. package/src/tui-compiled/core/refresh-file-lock.ts +0 -381
  105. package/src/tui-compiled/logger.ts +0 -196
  106. package/src/tui-compiled/rpc/protocol.ts +0 -33
  107. package/src/util/error.ts +0 -9
  108. package/src/util/open-url.ts +0 -30
  109. package/src/util/proxy-env.ts +0 -42
  110. package/src/util/record.ts +0 -3
  111. package/src/util/stable-json.ts +0 -15
  112. package/src/util/uuid-v7.ts +0 -18
  113. /package/src/{rpc/rpc-dir.ts → tui-compiled/raw/rpc-rpc-dir.ts} +0 -0
  114. /package/src/tui-compiled/{tui-preferences.ts → raw/tui-preferences.ts} +0 -0
  115. /package/src/tui-compiled/{rpc/rpc-dir.ts → runtime/rpc-rpc-dir.ts} +0 -0
  116. /package/src/{tui-preferences.ts → tui-compiled/runtime/tui-preferences.ts} +0 -0
  117. /package/src/tui-compiled/{util → shared/util}/error.ts +0 -0
  118. /package/src/tui-compiled/{util → shared/util}/open-url.ts +0 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * OpenCode's log destination.
3
+ *
4
+ * The logger itself lives in the shared core so both hosts get the same
5
+ * redaction rules. What stays here is the part that is specific to this host:
6
+ * which file the lines go to, which level floor applies, and when the buffer is
7
+ * drained at process exit. Those are the only three things that read this
8
+ * host's environment variables, and the core reads none.
9
+ *
10
+ * The init runs at module load rather than from one call site because every
11
+ * entry point in this package — the plugin loader, the RPC server, the separate
12
+ * TUI process — reaches the logger through this module, and a core logger no
13
+ * host has initialised silently drops every line. Both values are passed as
14
+ * functions so the destination and level keep resolving on each write, which is
15
+ * what they do today.
16
+ */
17
+ import { tmpdir } from 'node:os'
18
+ import { join } from 'node:path'
19
+ import {
20
+ flushLogs,
21
+ initLogger,
22
+ type Level,
23
+ } from '../shared/internal.ts'
24
+
25
+ export {
26
+ createLogger,
27
+ flushForTest,
28
+ flushLogs,
29
+ type Level,
30
+ redact,
31
+ redactStrings,
32
+ setLogLevel,
33
+ } from '../shared/internal.ts'
34
+
35
+ const LEVELS: readonly Level[] = ['error', 'warn', 'info', 'debug', 'trace']
36
+
37
+ export function getLogFile(): string {
38
+ return (
39
+ process.env.OPENCODE_OPENAI_AUTH_LOG_FILE ??
40
+ join(tmpdir(), 'opencode-openai-auth.log')
41
+ )
42
+ }
43
+
44
+ export function getEnvLogLevel(): Level | undefined {
45
+ const env = process.env.OPENCODE_OPENAI_AUTH_LOG_LEVEL as Level | undefined
46
+ return env && LEVELS.includes(env) ? env : undefined
47
+ }
48
+
49
+ initLogger({ file: getLogFile, level: getEnvLogLevel })
50
+
51
+ // Under `bun test` the buffer is drained explicitly by the tests that assert on
52
+ // it, and registering a process-wide exit handler per test file is noise.
53
+ if (process.env.NODE_ENV !== 'test') process.on('exit', flushLogs)
@@ -10,7 +10,7 @@ import {
10
10
  writeFile,
11
11
  } from 'node:fs/promises'
12
12
  import { join, resolve } from 'node:path'
13
- import { createLogger } from '../logger'
13
+ import { createLogger } from './logger.ts'
14
14
 
15
15
  const log = createLogger('rpc')
16
16
 
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The RPC payload types now live in the shared core, because both hosts build
3
+ * the same payloads. This file stays on disk as a re-export so the existing
4
+ * build entry and every importer that names `rpc/protocol` keep working.
5
+ */
6
+ export type {
7
+ ApplyRequest,
8
+ ApplyResult,
9
+ CommandModalName,
10
+ OpenDialogPayload,
11
+ RpcNotification,
12
+ } from '../shared/internal.ts'
@@ -1,5 +1,5 @@
1
- import { discoverPortFile, type PortFileEntry } from './port-file'
2
- import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
1
+ import { discoverPortFile, type PortFileEntry } from './rpc-port-file.ts'
2
+ import type { ApplyRequest, ApplyResult, RpcNotification } from './rpc-protocol.ts'
3
3
 
4
4
  export interface RpcClient {
5
5
  pending: (
@@ -6,10 +6,23 @@ export interface QuotaWindow {
6
6
  windowMinutes?: number
7
7
  }
8
8
 
9
+ export interface SpendControlReading {
10
+ limit: number
11
+ used: number
12
+ remaining: number
13
+ usedPercent: number
14
+ remainingPercent: number
15
+ resetsAt?: string
16
+ unit?: string
17
+ source?: string
18
+ reached: boolean
19
+ }
20
+
9
21
  export interface AccountQuota {
10
22
  checkedAt?: number
11
23
  primary?: QuotaWindow
12
24
  secondary?: QuotaWindow
25
+ spendControl?: SpendControlReading
13
26
  resetCreditsAvailable?: number
14
27
  }
15
28
 
@@ -88,6 +101,17 @@ export interface SidebarAccountState {
88
101
  killed: boolean
89
102
  enabled: boolean
90
103
  resetCredits?: number
104
+ custody?: SidebarAccountCustody
105
+ }
106
+
107
+ export type SidebarCustodyState = 'vault' | 'needsLogin' | 'local' | 'inert'
108
+
109
+ export type SidebarCustodyReason = CustodyInertReason | 'corrupt'
110
+
111
+ export interface SidebarAccountCustody {
112
+ state: SidebarCustodyState
113
+ reason?: SidebarCustodyReason
114
+ recordVersion?: number
91
115
  }
92
116
 
93
117
  export interface ActiveRoutingEntry {
@@ -132,6 +156,7 @@ export interface SidebarState {
132
156
  quota: AccountQuota | null
133
157
  /** ChatGPT identity of the main account this quota belongs to. */
134
158
  mainAccountId?: string
159
+ custody?: SidebarAccountCustody
135
160
  killed: boolean
136
161
  quotaBackedOff?: boolean
137
162
  quotaBackoffUntil?: number
@@ -155,8 +180,13 @@ import { createHash, randomUUID } from 'node:crypto'
155
180
  import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
156
181
  import { tmpdir } from 'node:os'
157
182
  import { dirname, join } from 'node:path'
158
- import { acquireRefreshFileLock } from './core/refresh-file-lock'
159
- import { createLogger } from './logger'
183
+ import { acquireRefreshFileLock } from '../shared/internal.ts'
184
+ import {
185
+ CUSTODY_INERT_REASONS,
186
+ type CustodyInertReason,
187
+ type CustodyVerdict,
188
+ } from './core-custody-state.ts'
189
+ import { createLogger } from './logger.ts'
160
190
 
161
191
  const logSb = createLogger('sidebar')
162
192
 
@@ -183,6 +213,49 @@ function resetCreditsField(value: unknown): { resetCredits?: number } {
183
213
  return credits !== undefined ? { resetCredits: credits } : {}
184
214
  }
185
215
 
216
+ const CUSTODY_STATES = new Set<SidebarCustodyState>([
217
+ 'vault',
218
+ 'needsLogin',
219
+ 'local',
220
+ 'inert',
221
+ ])
222
+
223
+ const CUSTODY_REASONS = new Set<SidebarCustodyReason>([
224
+ ...CUSTODY_INERT_REASONS,
225
+ 'corrupt',
226
+ ])
227
+
228
+ /**
229
+ * Tolerant reader for the per-fallback `custody` projection. Unknown state
230
+ * or reason values are dropped (NOT replaced with a default — a stale state
231
+ * file with an experimental `vaultHealing` value must not silently render
232
+ * as `local`, it must render as no-projection-at-all). Valid values round-
233
+ * trip byte-identical. The output contains only `state` and `reason`.
234
+ */
235
+ function normalizeSidebarCustody(
236
+ value: unknown,
237
+ ): SidebarAccountCustody | undefined {
238
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
239
+ return undefined
240
+ }
241
+ const c = value as Record<string, unknown>
242
+ if (
243
+ typeof c.state !== 'string' ||
244
+ !CUSTODY_STATES.has(c.state as SidebarCustodyState)
245
+ ) {
246
+ return undefined
247
+ }
248
+ const state = c.state as SidebarCustodyState
249
+ const out: SidebarAccountCustody = { state }
250
+ if (
251
+ typeof c.reason === 'string' &&
252
+ CUSTODY_REASONS.has(c.reason as SidebarCustodyReason)
253
+ ) {
254
+ out.reason = c.reason as SidebarCustodyReason
255
+ }
256
+ return out
257
+ }
258
+
186
259
  function normalizeActiveRouting(value: unknown): ActiveRoutingMap | undefined {
187
260
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
188
261
  return undefined
@@ -276,6 +349,23 @@ export function getSidebarStateFile(): string {
276
349
  return process.env[STATE_FILE_ENV] || DEFAULT_STATE_FILE
277
350
  }
278
351
 
352
+ export function projectCustodyForSidebar(
353
+ verdict: CustodyVerdict,
354
+ ): SidebarAccountCustody {
355
+ switch (verdict.kind) {
356
+ case 'LOCAL':
357
+ return { state: 'local' }
358
+ case 'VAULT':
359
+ return { state: 'vault' }
360
+ case 'INERT':
361
+ return { state: 'inert', reason: verdict.reason }
362
+ case 'NEEDS_LOGIN':
363
+ return verdict.reason === 'corrupt'
364
+ ? { state: 'needsLogin', reason: 'corrupt' }
365
+ : { state: 'needsLogin' }
366
+ }
367
+ }
368
+
279
369
  export const DEFAULT_SIDEBAR_STATE: SidebarState = {
280
370
  main: { quota: null, killed: false },
281
371
  fallbacks: [],
@@ -315,6 +405,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
315
405
  ...(typeof m.mainAccountId === 'string'
316
406
  ? { mainAccountId: m.mainAccountId }
317
407
  : {}),
408
+ ...(() => {
409
+ const custody = normalizeSidebarCustody(m.custody)
410
+ return custody ? { custody } : {}
411
+ })(),
318
412
  // Preserve optional backoff fields if present
319
413
  ...(typeof m.quotaBackedOff === 'boolean'
320
414
  ? { quotaBackedOff: m.quotaBackedOff }
@@ -347,17 +441,21 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
347
441
  !Array.isArray(entry) &&
348
442
  typeof (entry as Record<string, unknown>).id === 'string',
349
443
  )
350
- .map((e) => ({
351
- id: e.id as string,
352
- label: typeof e.label === 'string' ? e.label : undefined,
353
- ...(typeof e.accountId === 'string'
354
- ? { accountId: e.accountId }
355
- : {}),
356
- quota: ('quota' in e ? e.quota : null) as AccountQuota | null,
357
- killed: typeof e.killed === 'boolean' ? e.killed : false,
358
- enabled: typeof e.enabled === 'boolean' ? e.enabled : true,
359
- ...resetCreditsField(e.resetCredits),
360
- }))
444
+ .map((e) => {
445
+ const custody = normalizeSidebarCustody(e.custody)
446
+ return {
447
+ id: e.id as string,
448
+ label: typeof e.label === 'string' ? e.label : undefined,
449
+ ...(typeof e.accountId === 'string'
450
+ ? { accountId: e.accountId }
451
+ : {}),
452
+ quota: ('quota' in e ? e.quota : null) as AccountQuota | null,
453
+ killed: typeof e.killed === 'boolean' ? e.killed : false,
454
+ enabled: typeof e.enabled === 'boolean' ? e.enabled : true,
455
+ ...resetCreditsField(e.resetCredits),
456
+ ...(custody ? { custody } : {}),
457
+ }
458
+ })
361
459
  : []
362
460
 
363
461
  // activeId — string or undefined
@@ -428,6 +526,27 @@ export function isUsableRoutingEntry(
428
526
  )
429
527
  }
430
528
 
529
+ // The credit budget's own exhaustion signal, shared by admission and sticky
530
+ // migration so both agree on what "spent" means. `reached` is the provider's
531
+ // authoritative boolean — the percentage is only a display approximation — and
532
+ // the check fails open on a missing or lapsed reset exactly like a rate-limit
533
+ // window, so a stale or corrupt reading never blocks.
534
+ export function spendControlExhaustedResetAt(
535
+ quota: AccountQuota | null | undefined,
536
+ now = Date.now(),
537
+ ): { resetsAt: string; resetAtMs: number } | undefined {
538
+ const spendControl = quota?.spendControl
539
+ if (
540
+ spendControl?.reached !== true ||
541
+ typeof spendControl.resetsAt !== 'string'
542
+ ) {
543
+ return undefined
544
+ }
545
+ const resetAtMs = Date.parse(spendControl.resetsAt)
546
+ if (!Number.isFinite(resetAtMs) || resetAtMs <= now) return undefined
547
+ return { resetsAt: spendControl.resetsAt, resetAtMs }
548
+ }
549
+
431
550
  // Earliest future reset among the quota's exhausted windows, or undefined when
432
551
  // no present window is exhausted. Every present window is evaluated — matching
433
552
  // the admission policy, which rejects an account when ANY live window is below
@@ -454,6 +573,12 @@ export function exhaustedQuotaResetAt(
454
573
  earliest = { resetsAt: window.resetsAt, resetAtMs }
455
574
  }
456
575
  }
576
+ // The credit budget is a third axis on its own reset clock (a month, not
577
+ // 5h/7d), judged by the same shared signal sticky migration uses.
578
+ const spendReset = spendControlExhaustedResetAt(quota, now)
579
+ if (spendReset && (!earliest || spendReset.resetAtMs < earliest.resetAtMs)) {
580
+ earliest = spendReset
581
+ }
457
582
  return earliest
458
583
  }
459
584
 
@@ -1004,6 +1129,9 @@ function mergeQuotaByWindow(
1004
1129
  incoming.checkedAt,
1005
1130
  existing.checkedAt,
1006
1131
  )
1132
+ const spendControl = existingSnapshotIsFresher
1133
+ ? (existing.spendControl ?? incoming.spendControl)
1134
+ : (incoming.spendControl ?? existing.spendControl)
1007
1135
  let checkedAt: number | undefined
1008
1136
  for (const stamp of [
1009
1137
  finiteWindowCheckedAt(primary),
@@ -1017,6 +1145,7 @@ function mergeQuotaByWindow(
1017
1145
  ...incoming,
1018
1146
  primary,
1019
1147
  secondary,
1148
+ ...(spendControl !== undefined ? { spendControl } : {}),
1020
1149
  checkedAt: checkedAt ?? incoming.checkedAt,
1021
1150
  }
1022
1151
  }
@@ -7,10 +7,9 @@ import { createElement as _$createElement } from "opentui:runtime-module:%40open
7
7
  import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
8
8
  /** @jsxImportSource @opentui/solid */
9
9
 
10
- import { createLogger } from "../logger";
11
- import { getCollapsedQuotaSummary, getSidebarState, resolveSessionSidebarRouting } from "../sidebar-state.js";
12
- import { errorMessage } from "../util/error";
13
- import { openUrl } from "../util/open-url";
10
+ import { errorMessage, openUrl } from "../shared/internal.ts";
11
+ import { createLogger } from "./logger.ts";
12
+ import { getCollapsedQuotaSummary, getSidebarState, resolveSessionSidebarRouting } from "./sidebar-state.ts";
14
13
  const log = createLogger('rpc-tui');
15
14
  export function buildCachekeepDialogOptions(payload) {
16
15
  const enabled = payload.knobs.enabled === true;
@@ -467,7 +466,7 @@ export function openCommandDialog(api, payload, apply, sessionId) {
467
466
  return;
468
467
  }
469
468
  if (payload.command === 'openai-account') {
470
- openAccountDialog(api, apply, sessionId);
469
+ openAccountDialog(api, apply, sessionId, payload);
471
470
  return;
472
471
  }
473
472
 
@@ -508,8 +507,49 @@ export function buildAccountDialogRows(state, sessionId) {
508
507
  description: formatQuotaWindows(fb.quota)
509
508
  }))];
510
509
  }
511
- function openAccountDialog(api, apply, sessionId) {
510
+ export function accountDialogModeOption(mode) {
511
+ const claustrum = mode === 'claustrum';
512
+ return {
513
+ title: claustrum ? 'Leave Claustrum' : 'Enter Claustrum',
514
+ value: claustrum ? '__local__' : '__claustrum__',
515
+ description: claustrum ? 'Require fresh local logins before local refresh resumes' : 'Verify every enabled account before custody takes over'
516
+ };
517
+ }
518
+ export function buildFallbackAccountOptions(input) {
519
+ const name = input.label ?? input.id;
520
+ const options = [{
521
+ title: input.enabled ? 'Disable' : 'Enable',
522
+ value: input.enabled ? 'disable' : 'enable',
523
+ description: `${input.enabled ? 'Disable' : 'Enable'} ${name}`
524
+ }, {
525
+ title: 'Remove',
526
+ value: 'remove',
527
+ description: `Remove ${name}`
528
+ }];
529
+ if (input.index > 0) {
530
+ options.push({
531
+ title: 'Move up',
532
+ value: 'move_up',
533
+ description: 'Swap with the preceding fallback'
534
+ });
535
+ }
536
+ if (input.index < input.count - 1) {
537
+ options.push({
538
+ title: 'Move down',
539
+ value: 'move_down',
540
+ description: 'Swap with the following fallback'
541
+ });
542
+ }
543
+ options.push({
544
+ title: 'Back',
545
+ value: 'back',
546
+ description: 'Return to account list'
547
+ });
548
+ return options;
549
+ }
550
+ function openAccountDialog(api, apply, sessionId, payload) {
512
551
  const DialogConfirm = api.ui.DialogConfirm;
552
+ let claustrumMode = payload?.knobs.claustrumMode;
513
553
  function showL1() {
514
554
  void getSidebarState().then(state => {
515
555
  const DialogSelectInner = api.ui.DialogSelect;
@@ -517,13 +557,24 @@ function openAccountDialog(api, apply, sessionId) {
517
557
  api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
518
558
  title: "OpenAI Accounts",
519
559
  get options() {
520
- return [...buildAccountDialogRows(state, sessionId), {
560
+ return [accountDialogModeOption(claustrumMode), ...buildAccountDialogRows(state, sessionId), {
521
561
  title: 'Add account\u2026',
522
562
  value: '__add__',
523
563
  description: 'Sign in to a new OpenAI account'
524
564
  }];
525
565
  },
526
566
  onSelect: option => {
567
+ if (option.value === '__claustrum__' || option.value === '__local__') {
568
+ const args = option.value === '__claustrum__' ? 'claustrum' : 'local';
569
+ void apply('openai-account', args).then(r => {
570
+ api.ui.toast({
571
+ message: r.text
572
+ });
573
+ claustrumMode = r.knobs.claustrumMode;
574
+ showL1();
575
+ });
576
+ return;
577
+ }
527
578
  if (option.value === '__add__') {
528
579
  showAddFlow();
529
580
  return;
@@ -545,36 +596,12 @@ function openAccountDialog(api, apply, sessionId) {
545
596
  showL1();
546
597
  return;
547
598
  }
548
- const options = [];
549
- options.push({
550
- title: 'Remove',
551
- value: 'remove',
552
- description: `Remove ${fb.label ?? fb.id}`
553
- });
554
- if (fbIndex > 0) {
555
- const neighbor = state.fallbacks[fbIndex - 1];
556
- if (neighbor) {
557
- options.push({
558
- title: 'Move up',
559
- value: 'move_up',
560
- description: `Swap with ${neighbor.label ?? neighbor.id}`
561
- });
562
- }
563
- }
564
- if (fbIndex < state.fallbacks.length - 1) {
565
- const neighbor = state.fallbacks[fbIndex + 1];
566
- if (neighbor) {
567
- options.push({
568
- title: 'Move down',
569
- value: 'move_down',
570
- description: `Swap with ${neighbor.label ?? neighbor.id}`
571
- });
572
- }
573
- }
574
- options.push({
575
- title: 'Back',
576
- value: 'back',
577
- description: 'Return to account list'
599
+ const options = buildFallbackAccountOptions({
600
+ id: fb.id,
601
+ label: fb.label,
602
+ enabled: fb.enabled,
603
+ index: fbIndex,
604
+ count: state.fallbacks.length
578
605
  });
579
606
  api.ui.dialog.setSize('xlarge');
580
607
  api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
@@ -602,6 +629,15 @@ function openAccountDialog(api, apply, sessionId) {
602
629
  }));
603
630
  return;
604
631
  }
632
+ if (option.value === 'enable' || option.value === 'disable') {
633
+ void apply('openai-account', `${option.value} ${id}`).then(r => {
634
+ api.ui.toast({
635
+ message: r.text
636
+ });
637
+ showL1();
638
+ });
639
+ return;
640
+ }
605
641
  if (option.value === 'move_up') {
606
642
  const neighbor = state.fallbacks[fbIndex - 1];
607
643
  if (neighbor) {