@cortexkit/opencode-openai-auth 1.0.4 → 1.1.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 (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-DqKq8Eej.d.ts → codex-request-BXDXToyt.d.ts} +158 -27
  3. package/dist/index-0yh85bwb.js +69 -0
  4. package/dist/{index-jye6vccc.js → index-4c8yw54k.js} +2 -2
  5. package/dist/{index-k4eacw7x.js → index-7c6sxtd6.js} +2 -2
  6. package/dist/index-7eb3dw37.js +90 -0
  7. package/dist/index-8re8rprk.js +2 -0
  8. package/dist/{index-bgq9d21h.js → index-csky2xpg.js} +1 -1
  9. package/dist/index-edbdscy7.js +65 -0
  10. package/dist/{index-45qgkb0m.js → index-f2fmb596.js} +1 -1
  11. package/dist/{index-envpbt0k.js → index-j8v2xwq6.js} +1 -1
  12. package/dist/{index-egrty6tz.js → index-k6cydkgt.js} +2 -2
  13. package/dist/{index-dryydk6b.js → index-m5qmvr6c.js} +2 -2
  14. package/dist/index-msx2m78k.js +8 -0
  15. package/dist/{index-yeyp8t0k.js → index-zpm95mt5.js} +1 -1
  16. package/dist/index.d.ts +15 -7
  17. package/dist/index.js +1 -1
  18. package/dist/{map-CCbSZIhT.d.ts → map-Buq-GW7l.d.ts} +1 -1
  19. package/dist/{protocol-CSIDDBDf.d.ts → model-YRg7Uowo.d.ts} +67 -25
  20. package/dist/{port-file-CZ9MIdy2.d.ts → port-file-CxEpgSHY.d.ts} +1 -1
  21. package/dist/protocol-CDV1Pc6h.d.ts +29 -0
  22. package/dist/rpc/port-file.d.ts +1 -1
  23. package/dist/rpc/port-file.js +1 -1
  24. package/dist/rpc/protocol.d.ts +1 -1
  25. package/dist/rpc/protocol.js +1 -1
  26. package/dist/rpc/rpc-client.d.ts +3 -3
  27. package/dist/rpc/rpc-client.js +1 -1
  28. package/dist/rpc/rpc-dir.js +1 -1
  29. package/dist/{sidebar-state-BCxFpH68.d.ts → sidebar-state-DjTWUbmf.d.ts} +15 -9
  30. package/dist/sidebar-state.d.ts +1 -1
  31. package/dist/sidebar-state.js +1 -1
  32. package/dist/{token-vykm9bxh.js → token-rz4t3ay6.js} +1 -1
  33. package/dist/token-util-tpy2c7h6.js +1 -0
  34. package/dist/tui-preferences.js +1 -1
  35. package/dist/tui.d.ts +20 -2
  36. package/dist/v2/server.d.ts +73 -1
  37. package/dist/v2/server.js +3 -3
  38. package/package.json +2 -2
  39. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +78 -42
  40. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +354 -0
  41. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  42. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +393 -0
  43. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  44. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  45. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  46. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  47. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  48. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  49. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  50. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  51. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  52. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  53. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  54. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  55. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  56. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  57. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  58. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  59. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  60. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  61. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  62. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  63. package/src/tui-compiled/raw/sidebar-state.js +41 -14
  64. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  65. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  66. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  67. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  68. package/src/tui-compiled/raw/tui.tsx +146 -40
  69. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +78 -42
  70. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +354 -0
  71. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  72. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +393 -0
  73. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  74. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  75. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  76. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  77. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  78. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  79. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  80. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  81. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  82. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  83. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  84. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  85. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  86. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  87. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  88. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  89. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  90. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  91. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  92. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  93. package/src/tui-compiled/runtime/sidebar-state.js +41 -14
  94. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  95. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  96. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  97. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  98. package/src/tui-compiled/runtime/tui.js +255 -163
  99. package/dist/index-2hch7e8n.js +0 -8
  100. package/dist/index-5dg931q0.js +0 -65
  101. package/dist/index-99esnzg0.js +0 -157
  102. package/dist/index-9reyazwh.js +0 -2
  103. package/dist/index-yf6arn5w.js +0 -2
  104. package/dist/token-util-gmn5yaqc.js +0 -1
@@ -1,8 +1,25 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, open, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
2
+ import { mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { createPrivateBytes, createPrivateDirectoryExclusive, ensurePrivateDirectory, PrivateFileError, privateOperation, readPrivateText, verifyPrivate, windowsPrivateFiles, writePrivateAtomic, } from "./375d9cca2def037d-private-files.js";
5
5
  import { LockOwnershipError, lockPathFor } from "./4825e03209d6ae3e-with-lock.js";
6
+ /**
7
+ * Create a new owner-only file and write `text` into it, failing with EEXIST if
8
+ * the file already exists. Writing through the handle rather than path-based
9
+ * `writeFile(path, …, { flag: 'wx' })` matters: Bun's path form also truncates
10
+ * the file it has just created, and on some Linux filesystems (XFS under load)
11
+ * that redundant ftruncate stalls for hundreds of milliseconds, inside every
12
+ * lock acquisition.
13
+ */
14
+ async function createExclusiveText(path, text) {
15
+ const handle = await open(path, 'wx', 0o600);
16
+ try {
17
+ await handle.writeFile(text, 'utf8');
18
+ }
19
+ finally {
20
+ await handle.close();
21
+ }
22
+ }
6
23
  const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis);
7
24
  const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis);
8
25
  // A concurrent contender renaming the freshly-created eviction-marker directory
@@ -14,7 +31,12 @@ export function isLostMarkerRaceError(error) {
14
31
  return (code === 'ENOENT' ||
15
32
  (!windowsPrivateFiles && (code === 'EINVAL' || code === 'ENOTDIR')));
16
33
  }
17
- export async function acquireRefreshFileLock(options) {
34
+ /** Filesystem leases share wall time across all callers and processes. */
35
+ export function acquireRefreshFileLock(options) {
36
+ return acquireRefreshFileLockWithClock({ ...options, now: Date.now });
37
+ }
38
+ /** @internal Clock seam for lease tests; not exported by the package's fs entry point. */
39
+ export async function acquireRefreshFileLockWithClock(options) {
18
40
  const lockPath = lockPathFor(options.path, options.name);
19
41
  const legacyOwnerPath = join(lockPath, 'owner.json');
20
42
  const ownerId = randomUUID();
@@ -152,11 +174,7 @@ export async function acquireRefreshFileLock(options) {
152
174
  if (windowsPrivateFiles)
153
175
  await createPrivateBytes(lockPath, bytes);
154
176
  else
155
- await writeFile(lockPath, bytes, {
156
- encoding: 'utf8',
157
- mode: 0o600,
158
- flag: 'wx',
159
- });
177
+ await createExclusiveText(lockPath, bytes);
160
178
  }
161
179
  async function leaseContention(error) {
162
180
  const code = error.code;
@@ -304,11 +322,7 @@ export async function acquireRefreshFileLock(options) {
304
322
  if (windowsPrivateFiles)
305
323
  await createPrivateBytes(evictOwnerPath, bytes);
306
324
  else
307
- await writeFile(evictOwnerPath, bytes, {
308
- encoding: 'utf8',
309
- mode: 0o600,
310
- flag: 'wx',
311
- });
325
+ await createExclusiveText(evictOwnerPath, bytes);
312
326
  }
313
327
  catch (error) {
314
328
  // A competing contender can rename our just-created marker directory
@@ -0,0 +1,24 @@
1
+ // Claustrum vault custody shared by the auth plugins: scoped enrollment (run
2
+ // from setup), discovery of this consumer's vault accounts as rows for this
3
+ // package's `/routing` subpath, per-send authorization, 401 reporting, the
4
+ // declined-account interlock and the host-slot guard. Needs the optional
5
+ // peer `@cortexkit/claustrum-client`.
6
+ import { ClaustrumClient, } from "@cortexkit/claustrum-client";
7
+ import { insulaUsageTransport } from "./d0f0b6be200311af-insula.js";
8
+ export { ClaustrumCredentialError, } from "@cortexkit/claustrum-client";
9
+ export { ClaustrumConsumer, } from "./169b2b4bf30909bc-consumer.js";
10
+ export { ClaustrumScopedCustody, decideScopedRetryAfter401, isScopedCredentialRotation, SERVING_MARGIN_MS, } from "./0f77ebc3c914c9c6-custody.js";
11
+ export { ClaustrumEnrollmentManager, classifyEnrollmentError, connectClaustrumEnrollmentClient, disconnectClaustrumEnrollment, enrollmentAuthority, enrollmentName, getClaustrumEnrollmentPaths, hostEnrollmentPaths, RETRYABLE_ENROLLMENT_CODES, readClaustrumEnrollmentStatus, readClaustrumEnrollmentToken, resetClaustrumEnrollmentState, TERMINAL_ENROLLMENT_CODES, } from "./b9588ad4725c1d10-enrollment.js";
12
+ export { ClaustrumConsumerError, } from "./ca0fccaba1d87af4-errors.js";
13
+ export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, isCustodyPlaceholder, isCustodyPlaceholderValue, } from "./d9d2892a4c371301-host-slot.js";
14
+ export { acceptAccount, declineAccount, isDeclined, } from "./7c140680fc81be92-interlock.js";
15
+ export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
16
+ /**
17
+ * Connect the client that lists and fetches this consumer's vault credentials
18
+ * on the request path. `connectionFile` is required: this library
19
+ * reads no environment, so the plugin resolves the vault's connection file.
20
+ */
21
+ export async function connectClaustrumScopedClient(options) {
22
+ const client = await ClaustrumClient.connect(options);
23
+ return Object.assign(client, { insula: insulaUsageTransport(options) });
24
+ }
@@ -3,6 +3,11 @@ import {
3
3
  projectQuota,
4
4
  quotaWindowName
5
5
  } from "./shared/d5a8f1061ee2f114-index.js";
6
+ import { orderForPlacement } from "./shared/3e7d45a77c6ad4d5-index.js";
7
+ import {
8
+ orderedPlacement,
9
+ vaultMainRouteId
10
+ } from "./shared/abd062d633e29fd2-pool-routing.js";
6
11
  const QUOTA_WINDOW_KEYS = ["primary", "secondary"], LEGACY_WINDOW_MINUTES = {
7
12
  primary: 300,
8
13
  secondary: 10080
@@ -167,11 +172,14 @@ function normalizeAccountList(raw) {
167
172
  return [];
168
173
  return raw.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && typeof entry.id === "string").map((e) => ({
169
174
  id: e.id,
175
+ ...typeof e.credentialId === "string" ? { credentialId: e.credentialId } : {},
170
176
  label: typeof e.label === "string" ? e.label : void 0,
171
177
  ...typeof e.accountId === "string" ? { accountId: e.accountId } : {},
172
178
  quota: "quota" in e ? e.quota : null,
173
179
  killed: typeof e.killed === "boolean" ? e.killed : !1,
174
180
  enabled: typeof e.enabled === "boolean" ? e.enabled : !0,
181
+ ...typeof e.routable === "boolean" ? { routable: e.routable } : {},
182
+ ...["needs sign-in", "turned off in vault", "unavailable"].includes(e.vaultState) ? { vaultState: e.vaultState } : {},
175
183
  ...resetCreditsField(e.resetCredits)
176
184
  }));
177
185
  }
@@ -192,7 +200,7 @@ export function isUsableRoutingEntry(entry, accounts, now = Date.now()) {
192
200
  return !1;
193
201
  if (accounts === void 0)
194
202
  return !0;
195
- return entry.activeId === "main" || accounts.some((account) => account.enabled !== !1 && account.killed !== !0 && account.id === entry.activeId);
203
+ return entry.activeId === "main" || accounts.some((account) => account.enabled !== !1 && account.routable !== !1 && account.killed !== !0 && account.id === entry.activeId);
196
204
  }
197
205
  export function spendControlExhaustedResetAt(quota, now = Date.now()) {
198
206
  const spendControl = quota?.spendControl;
@@ -229,28 +237,40 @@ export function resolveSessionStickyAccount(state, sessionId, now = Date.now())
229
237
  const assignment = state.stickyAssignments?.[hashSidebarSessionId(sessionId)];
230
238
  if (!assignment || assignment.lastSeenAt < now - STICKY_ASSIGNMENT_MAX_AGE_MS)
231
239
  return;
232
- if (assignment.accountId === "main")
240
+ if (assignment.accountId === "main" && state.vaultAccounts === void 0)
233
241
  return state.main?.killed || isQuotaExhausted(state.main?.quota, now) ? void 0 : "main";
234
242
  const fallback = routingAccountsOf(state).find((account) => account.id === assignment.accountId);
235
- if (!fallback || fallback.enabled === !1 || fallback.killed === !0 || isQuotaExhausted(fallback.quota, now))
243
+ if (!fallback || fallback.enabled === !1 || fallback.routable === !1 || fallback.killed === !0 || isQuotaExhausted(fallback.quota, now))
236
244
  return;
237
245
  return fallback.id;
238
246
  }
239
247
  export function resolveSessionSidebarRouting(state, sessionId, now = Date.now()) {
240
- if (!sessionId)
241
- return { activeId: state.activeId ?? "main", route: state.route };
248
+ if (!sessionId) {
249
+ if (state.vaultAccounts === void 0)
250
+ return { activeId: state.activeId ?? "main", route: state.route };
251
+ const active = state.vaultAccounts.find((account) => account.id === state.activeId && account.enabled && account.routable !== !1 && !account.killed);
252
+ if (active && !isQuotaExhausted(active.quota, now))
253
+ return { activeId: active.id, route: state.route };
254
+ }
242
255
  const own = sessionId ? state.activeRouting?.[sessionId] : void 0, accounts = routingAccountsOf(state), ownQuota = own?.activeId === "main" ? state.main.quota : accounts.find((account) => account.id === own?.activeId)?.quota;
243
- if (own && isUsableRoutingEntry(own, accounts, now) && !isQuotaExhausted(ownQuota, now))
256
+ if (own && (state.vaultAccounts === void 0 || own.activeId.startsWith("vault:")) && isUsableRoutingEntry(own, accounts, now) && !isQuotaExhausted(ownQuota, now))
244
257
  return { activeId: own.activeId, route: own.route };
245
258
  const stickyAccountId = resolveSessionStickyAccount(state, sessionId, now);
246
259
  if (stickyAccountId)
247
260
  return { activeId: stickyAccountId, route: state.route };
248
- const vaultMode = state.vaultAccounts !== void 0, enabledFallbacks = (vaultMode ? state.vaultAccounts ?? [] : state.fallbacks).filter((account) => account.enabled && !account.killed), fallback = enabledFallbacks.find((account) => !isQuotaExhausted(account.quota, now)) ?? enabledFallbacks[0];
261
+ const vaultMode = state.vaultAccounts !== void 0, enabledFallbacks = (vaultMode ? vaultAccountsInPlacementOrder(state) : state.fallbacks).filter((account) => account.enabled && account.routable !== !1 && !account.killed), fallback = enabledFallbacks.find((account) => !isQuotaExhausted(account.quota, now)) ?? enabledFallbacks[0];
249
262
  return {
250
- activeId: (vaultMode || state.route === "fallback-first") && fallback ? fallback.id : "main",
263
+ activeId: vaultMode ? fallback?.id ?? "vault" : state.route === "fallback-first" && fallback ? fallback.id : "main",
251
264
  route: state.route
252
265
  };
253
266
  }
267
+ function vaultAccountsInPlacementOrder(state) {
268
+ const accounts = state.vaultAccounts ?? [], byId = new Map(accounts.map((account) => [account.id, account]));
269
+ return orderForPlacement(accounts.map((account) => account.id), state.route === "sticky-balanced" ? "roster" : orderedPlacement(state.route), vaultMainRouteId(accounts) ?? "").flatMap((id) => {
270
+ const account = byId.get(id);
271
+ return account ? [account] : [];
272
+ });
273
+ }
254
274
  export function pruneActiveRouting(activeRouting, accounts, now = Date.now(), removedSessionId) {
255
275
  if (!activeRouting)
256
276
  return;
@@ -407,7 +427,14 @@ function writeMergedSidebarState(file, merge, hooks) {
407
427
  if (hooks?.canWrite && !hooks.canWrite())
408
428
  return;
409
429
  const next = merge(latest);
410
- return next === void 0 ? void 0 : normalizeSidebarState(next);
430
+ if (next === void 0)
431
+ return;
432
+ if (next.vaultAccounts !== void 0) {
433
+ next.activeId = next.activeId?.startsWith("vault:") ? next.activeId : void 0;
434
+ next.activeRouting = Object.fromEntries(Object.entries(next.activeRouting ?? {}).filter(([, entry]) => entry.activeId.startsWith("vault:")));
435
+ next.stickyAssignments = Object.fromEntries(Object.entries(next.stickyAssignments ?? {}).filter(([, pin]) => pin.accountId.startsWith("vault:") && !next.vaultAccounts?.some((account) => account.id === pin.accountId && account.killed)));
436
+ }
437
+ return normalizeSidebarState(next);
411
438
  }, {
412
439
  beforeRecheck: async (...args) => {
413
440
  await hooks?.beforeRecheck?.(...args);
@@ -493,7 +520,7 @@ function mergeQuotaByWindow(incoming, existing) {
493
520
  export function setSidebarMachineState(machineState, file = getSidebarStateFile(), hooks) {
494
521
  return enqueueSidebarWrite(async () => {
495
522
  await writeMergedSidebarState(file, (latest) => {
496
- const latestFallbacks = new Map(latest.fallbacks.map((account) => [account.id, account])), vaultMode = machineState.vaultAccounts !== void 0, mainSameIdentity = sameAccountIdentity(machineState.main.mainAccountId, latest.main.mainAccountId), mergedMainQuota = mainSameIdentity ? mergeQuotaByWindow(machineState.main.quota, latest.main.quota) : freshestQuota(machineState.main.quota, latest.main.quota), now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(routingAccountsOf(machineState)), now);
523
+ const latestFallbacks = new Map(latest.fallbacks.map((account) => [account.id, account])), vaultMode = machineState.vaultAccounts !== void 0, mainSameIdentity = sameAccountIdentity(machineState.main.mainAccountId, latest.main.mainAccountId), mergedMainQuota = mainSameIdentity ? mergeQuotaByWindow(machineState.main.quota, latest.main.quota) : freshestQuota(machineState.main.quota, latest.main.quota), now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, vaultMode ? void 0 : usableRoutingAccountIds(routingAccountsOf(machineState)), now);
497
524
  return {
498
525
  ...latest,
499
526
  ...machineState,
@@ -530,7 +557,7 @@ export function upsertSidebarActiveRouting(input, accounts, file = getSidebarSta
530
557
  route: input.route,
531
558
  updatedAt: input.updatedAt
532
559
  }
533
- }, accounts, Date.now()), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(accounts), Date.now());
560
+ }, accounts, Date.now()), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, latest.vaultAccounts !== void 0 ? void 0 : usableRoutingAccountIds(accounts), Date.now());
534
561
  return {
535
562
  ...latest,
536
563
  activeId: input.activeId,
@@ -555,7 +582,7 @@ export function setSidebarLegacyRouting(input, file = getSidebarStateFile(), hoo
555
582
  export function removeSidebarActiveRouting(sessionId, accounts, file = getSidebarStateFile(), hooks) {
556
583
  return enqueueSidebarWrite(async () => {
557
584
  await writeMergedSidebarState(file, (latest) => {
558
- const now = Date.now(), activeRouting = pruneActiveRouting(latest.activeRouting, accounts, now, sessionId), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(accounts), now, hashSidebarSessionId(sessionId));
585
+ const now = Date.now(), activeRouting = pruneActiveRouting(latest.activeRouting, accounts, now, sessionId), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, latest.vaultAccounts !== void 0 ? void 0 : usableRoutingAccountIds(accounts), now, hashSidebarSessionId(sessionId));
559
586
  return {
560
587
  ...latest,
561
588
  activeRouting,
@@ -652,7 +679,7 @@ export function persistSidebarStickyAssignment(input, file = getSidebarStateFile
652
679
  const sessionHash = hashSidebarSessionId(input.sessionId), validPinnedAccountIds = new Set(input.validPinnedAccountIds);
653
680
  return enqueueSidebarWrite(async () => {
654
681
  await writeMergedSidebarState(file, (latest) => {
655
- const now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, validPinnedAccountIds, now), current = stickyAssignments?.[sessionHash];
682
+ const now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, latest.vaultAccounts !== void 0 ? void 0 : validPinnedAccountIds, now), current = stickyAssignments?.[sessionHash];
656
683
  if (current && current.assignedAt > input.assignment.assignedAt) {
657
684
  if (stickyAssignmentsEqual(latest.stickyAssignments, stickyAssignments))
658
685
  return;
@@ -875,7 +902,7 @@ export function createSidebarBookkeepingQueue(options) {
875
902
  export function resolveActiveAccount(state) {
876
903
  const activeId = state.activeId;
877
904
  if (state.vaultAccounts !== void 0) {
878
- const enabled = state.vaultAccounts.filter((account) => account.enabled), vaultAccount = enabled.find((account) => account.id === activeId) ?? enabled[0];
905
+ const enabled = vaultAccountsInPlacementOrder(state).filter((account) => account.enabled && account.routable !== !1), vaultAccount = enabled.find((account) => account.id === activeId) ?? enabled[0];
879
906
  return vaultAccount ? {
880
907
  id: vaultAccount.id,
881
908
  name: vaultAccount.label ?? vaultAccount.id,
@@ -14,6 +14,7 @@ import { createComponent as _$createComponent } from "opentui:runtime-module:%40
14
14
  // result carries.
15
15
 
16
16
  import { createLogger } from "../logger.js";
17
+ import { factLines, sectionTitle } from "./section-text.js";
17
18
  const log = createLogger('rpc-tui');
18
19
 
19
20
  /** Sends one action to the plugin; the session id is added by the caller. */
@@ -30,13 +31,9 @@ export function sectionListOptions(menu) {
30
31
  } : {})
31
32
  }));
32
33
  }
33
- function factLines(facts) {
34
- return Object.entries(facts ?? {}).map(([name, value]) => `${name}: ${typeof value === 'string' ? value : JSON.stringify(value)}`);
35
- }
36
34
 
37
- /** Read-only text is a category header; every option navigates or runs an action. */
35
+ /** Every option navigates or runs an action; read-only context is in the title. */
38
36
  export function sectionOptions(section) {
39
- const header = [...section.lines, ...factLines(section.facts), ...section.items.filter(item => item.actions.length === 0).map(item => [item.group, [item.label, item.status].filter(Boolean).join(' · '), item.detail, ...factLines(item.facts)].filter(Boolean).join('\n'))];
40
37
  const options = [...section.items.filter(item => item.actions.length > 0).map(item => ({
41
38
  title: item.label,
42
39
  value: `item:${item.id}`,
@@ -62,15 +59,6 @@ export function sectionOptions(section) {
62
59
  title: 'Back',
63
60
  value: BACK
64
61
  }];
65
- const first = options[0];
66
- if (first && header.length > 0) {
67
- const category = first.category;
68
- const text = [...header, category].filter(Boolean).join('\n');
69
- for (const option of options) {
70
- if (option.category !== category) break;
71
- option.category = text;
72
- }
73
- }
74
62
  return options;
75
63
  }
76
64
 
@@ -158,7 +146,7 @@ function showSection(context, command, menu, sectionId) {
158
146
  api.ui.dialog.setSize('xlarge');
159
147
  api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
160
148
  get title() {
161
- return section.title;
149
+ return sectionTitle(section);
162
150
  },
163
151
  get options() {
164
152
  return sectionOptions(section);
@@ -0,0 +1,21 @@
1
+ export function factLines(facts) {
2
+ return Object.entries(facts ?? {}).map(([name, value]) => `${name}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
3
+ }
4
+ export function sectionText(section) {
5
+ return [
6
+ ...section.lines,
7
+ ...factLines(section.facts),
8
+ ...section.items.filter((item) => item.actions.length === 0).map((item) => [
9
+ item.group,
10
+ [item.label, item.status].filter(Boolean).join(" \xB7 "),
11
+ item.detail,
12
+ ...factLines(item.facts)
13
+ ].filter(Boolean).join(`
14
+ `))
15
+ ].filter(Boolean).join(`
16
+ `);
17
+ }
18
+ export function sectionTitle(section) {
19
+ return [section.title, sectionText(section)].filter(Boolean).join(`
20
+ `);
21
+ }
@@ -0,0 +1,137 @@
1
+ import { sectionText, sectionTitle } from "./section-text.js";
2
+ function inlineDescription(...parts) {
3
+ return parts.filter(Boolean).join(" \xB7 ").replace(/\r?\n/g, " \xB7 ");
4
+ }
5
+ export async function openControlDialog(dialog, initial, apply, active = () => !0) {
6
+ let payload = initial;
7
+ menu:
8
+ while (active()) {
9
+ const sectionId = await dialog.select({
10
+ title: payload.menu.title,
11
+ options: payload.menu.sections.map((section) => ({
12
+ title: section.title,
13
+ value: section.id,
14
+ description: inlineDescription(...section.lines)
15
+ }))
16
+ });
17
+ if (!active() || sectionId === void 0)
18
+ return;
19
+ const section = payload.menu.sections.find((entry) => entry.id === sectionId);
20
+ if (!section)
21
+ return;
22
+ const choices = [
23
+ ...section.items.filter((item) => item.actions.length > 0).map((item) => ({
24
+ title: item.label,
25
+ description: inlineDescription(item.status, item.detail),
26
+ value: { itemId: item.id, actions: item.actions }
27
+ })),
28
+ ...section.actions.map((action) => ({
29
+ title: action.label,
30
+ description: inlineDescription(action.description),
31
+ value: { itemId: void 0, actions: [action] }
32
+ }))
33
+ ];
34
+ if (choices.length === 0) {
35
+ await dialog.alert({
36
+ title: section.title,
37
+ message: sectionText(section)
38
+ });
39
+ continue;
40
+ }
41
+ const choice = await dialog.select({
42
+ title: sectionTitle(section),
43
+ options: [...choices, { title: "Back", value: void 0 }]
44
+ });
45
+ if (!active())
46
+ return;
47
+ if (!choice)
48
+ continue;
49
+ let actionId;
50
+ if (choice.itemId !== void 0) {
51
+ if (choice.actions.length === 0)
52
+ continue;
53
+ actionId = await dialog.select({
54
+ title: section.items.find((item) => item.id === choice.itemId)?.label ?? section.title,
55
+ options: [
56
+ ...choice.actions.map((action) => ({
57
+ title: action.label,
58
+ value: action.id
59
+ })),
60
+ { title: "Back", value: void 0 }
61
+ ]
62
+ });
63
+ } else
64
+ actionId = choice.actions[0]?.id;
65
+ if (!active())
66
+ return;
67
+ if (actionId === void 0)
68
+ continue;
69
+ const action = choice.actions.find((entry) => entry.id === actionId);
70
+ if (!action)
71
+ continue;
72
+ try {
73
+ const values = {};
74
+ for (const knob of action.knobs) {
75
+ let value;
76
+ if (knob.kind === "choice")
77
+ value = await dialog.select({
78
+ title: knob.label,
79
+ options: knob.choices.map((entry) => ({
80
+ title: entry.label,
81
+ value: entry.value
82
+ }))
83
+ });
84
+ else if (knob.kind === "toggle")
85
+ value = await dialog.select({
86
+ title: knob.label,
87
+ options: [
88
+ { title: `On${knob.value ? " (current)" : ""}`, value: !0 },
89
+ { title: `Off${!knob.value ? " (current)" : ""}`, value: !1 }
90
+ ]
91
+ });
92
+ else {
93
+ const text = await dialog.prompt({
94
+ title: knob.label,
95
+ ...knob.value !== void 0 ? { value: String(knob.value) } : {},
96
+ ...knob.kind === "text" && knob.placeholder ? { placeholder: knob.placeholder } : {}
97
+ });
98
+ value = text === void 0 ? void 0 : text === "" ? null : knob.kind === "number" && Number.isFinite(Number(text)) ? Number(text) : text;
99
+ }
100
+ if (!active())
101
+ return;
102
+ if (value === void 0)
103
+ continue menu;
104
+ values[knob.id] = value;
105
+ }
106
+ if (action.confirm) {
107
+ const confirmed = await dialog.confirm({
108
+ title: action.label,
109
+ message: action.confirm.message
110
+ });
111
+ if (!active())
112
+ return;
113
+ if (confirmed !== !0)
114
+ continue;
115
+ }
116
+ const result = await apply({
117
+ command: payload.command,
118
+ sectionId,
119
+ actionId,
120
+ ...choice.itemId !== void 0 ? { itemId: choice.itemId } : {},
121
+ ...action.knobs.length ? { values } : {},
122
+ ...action.confirm ? { confirmed: !0 } : {}
123
+ });
124
+ if (!active())
125
+ return;
126
+ payload = { command: result.command, menu: result.menu };
127
+ await dialog.alert({ title: payload.menu.title, message: result.text });
128
+ } catch (error) {
129
+ if (!active())
130
+ return;
131
+ await dialog.alert({
132
+ title: payload.menu.title,
133
+ message: error instanceof Error ? error.message : String(error)
134
+ });
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,95 @@
1
+ import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
2
+ import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
3
+ /** @jsxImportSource @opentui/solid */
4
+
5
+ import { isNotifyPayload } from "../rpc/protocol.js";
6
+ import { createRpcClient } from "../rpc/rpc-client.js";
7
+ import { resolveRpcDir } from "../rpc/rpc-dir.js";
8
+ import { openControlDialog } from "./v2-dialog.js";
9
+ const KEY = Symbol.for('cortexkit.openai-auth.v2.tui');
10
+ /** Uses OpenCode 2's transport without running the OpenCode 1 UI registration. */
11
+ export async function setupTui(context, options = {}) {
12
+ const processState = globalThis;
13
+ processState[KEY] ??= {
14
+ lastIds: new Map()
15
+ };
16
+ const state = processState[KEY];
17
+ state.mount?.dispose();
18
+ const openDialog = options.openDialog ?? ((payload, apply) => openControlDialog(context.ui.dialog, payload, apply, () => !disposed));
19
+ let disposed = false;
20
+ let inFlight = false;
21
+ let dialogOpen = false;
22
+ let directory = '';
23
+ let client;
24
+ const unmount = options.sidebarText === undefined ? options.sidebar ? context.ui.slot({
25
+ append: 'sidebar.content',
26
+ render: options.sidebar.render
27
+ }) : undefined : context.ui.slot({
28
+ append: 'sidebar.content',
29
+ render: () => (() => {
30
+ var _el$ = _$createElement("text");
31
+ _$insert(_el$, () => options.sidebarText);
32
+ return _el$;
33
+ })()
34
+ });
35
+ const poll = async () => {
36
+ if (disposed || inFlight || dialogOpen) return;
37
+ const route = context.ui.router.current();
38
+ if (route.type !== 'session') return;
39
+ const sessionId = route.sessionID;
40
+ const session = context.data.session.get(sessionId);
41
+ if (!session) return;
42
+ inFlight = true;
43
+ try {
44
+ if (directory !== session.location.directory) {
45
+ directory = session.location.directory;
46
+ const resolved = await resolveRpcDir(directory);
47
+ if (disposed) return;
48
+ client = createRpcClient(resolved.dir, process.pid);
49
+ }
50
+ const rpc = client;
51
+ if (!rpc) return;
52
+ const messages = await rpc.pending(state.lastIds.get(sessionId) ?? 0, sessionId);
53
+ if (disposed || context.ui.router.current().type !== 'session' || context.ui.router.current().sessionID !== sessionId) return;
54
+ for (const message of [...messages].sort((a, b) => a.id - b.id)) {
55
+ state.lastIds.set(sessionId, Math.max(state.lastIds.get(sessionId) ?? 0, message.id));
56
+ if (isNotifyPayload(message.payload)) context.ui.toast.show({
57
+ message: message.payload.notify.message,
58
+ sessionID: sessionId
59
+ });else {
60
+ dialogOpen = true;
61
+ await openDialog(message.payload, request => {
62
+ if (disposed) throw new Error('The OpenAI dialog was closed');
63
+ return rpc.apply({
64
+ ...request,
65
+ sessionId
66
+ }, 90_000);
67
+ }).finally(() => {
68
+ dialogOpen = false;
69
+ });
70
+ }
71
+ }
72
+ } catch {
73
+ // The server can be absent while a location starts or is reloaded.
74
+ } finally {
75
+ inFlight = false;
76
+ }
77
+ };
78
+ const timer = setInterval(() => {
79
+ void poll();
80
+ }, 500);
81
+ const mount = {
82
+ dispose() {
83
+ if (disposed) return;
84
+ disposed = true;
85
+ clearInterval(timer);
86
+ unmount?.();
87
+ options.sidebar?.dispose();
88
+ if (dialogOpen) context.ui.dialog.clear();
89
+ if (state.mount === mount) state.mount = undefined;
90
+ }
91
+ };
92
+ state.mount = mount;
93
+ void poll();
94
+ return () => mount.dispose();
95
+ }