@cortexkit/opencode-openai-auth 1.0.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +9 -8
- package/dist/{pool-lifecycle-C_6YRmsh.d.ts → codex-request-CjXETpTA.d.ts} +190 -27
- package/dist/index-03em8hv1.js +69 -0
- package/dist/index-6n4pnfkg.js +2 -0
- package/dist/{index-pg305s90.js → index-bc89akak.js} +1 -1
- package/dist/{index-y4gxyxqd.js → index-ehjj89rx.js} +2 -2
- package/dist/index-f21addzm.js +90 -0
- package/dist/{index-fsky1rsh.js → index-ffg0sgg5.js} +2 -2
- package/dist/{index-nx2qh047.js → index-ftkhhr6d.js} +1 -1
- package/dist/{index-826s1yxa.js → index-htvcmtcx.js} +2 -2
- package/dist/{index-fsxbncpw.js → index-qp5hdpw8.js} +1 -1
- package/dist/index-qxbchh2d.js +8 -0
- package/dist/{index-116q849r.js → index-r2s51ac1.js} +2 -2
- package/dist/index-rwbrhkse.js +65 -0
- package/dist/{index-66asrje2.js → index-xwvze96g.js} +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +1 -1
- package/dist/{protocol-DctqemRI.d.ts → model-CQcw10oW.d.ts} +67 -25
- package/dist/{port-file-ZPlxXB4G.d.ts → port-file-BDJnzgHH.d.ts} +1 -1
- package/dist/protocol-C-kagE6O.d.ts +29 -0
- package/dist/rpc/port-file.d.ts +1 -1
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +1 -1
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +3 -3
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/{sidebar-state-z_M7Trnd.d.ts → sidebar-state-BfQ4mpZO.d.ts} +109 -10
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-cf0nyh7n.js +1 -0
- package/dist/{token-bzhkgc29.js → token-z6qq7a1d.js} +1 -1
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +20 -2
- package/dist/v2/server.d.ts +180 -1
- package/dist/v2/server.js +3 -3
- package/package.json +3 -3
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +148 -26
- package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +371 -0
- package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
- package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +430 -0
- package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
- package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
- package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
- package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
- package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
- package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
- package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
- package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
- package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
- package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
- package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
- package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
- package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
- package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
- package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
- package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
- package/src/tui-compiled/raw/sidebar-state.js +103 -14
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
- package/src/tui-compiled/raw/tui/section-text.js +21 -0
- package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
- package/src/tui-compiled/raw/tui/v2.tsx +133 -0
- package/src/tui-compiled/raw/tui.tsx +228 -70
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +148 -26
- package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +371 -0
- package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
- package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +430 -0
- package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
- package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
- package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
- package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
- package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
- package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
- package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
- package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
- package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
- package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
- package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
- package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
- package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
- package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
- package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
- package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
- package/src/tui-compiled/runtime/sidebar-state.js +103 -14
- package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
- package/src/tui-compiled/runtime/tui/section-text.js +21 -0
- package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
- package/src/tui-compiled/runtime/tui/v2.js +95 -0
- package/src/tui-compiled/runtime/tui.js +351 -222
- package/dist/index-4fnghj9e.js +0 -65
- package/dist/index-a69gr76b.js +0 -2
- package/dist/index-h263aftt.js +0 -157
- package/dist/index-ssj7m2rd.js +0 -8
- package/dist/index-ydz3hbhc.js +0 -2
- package/dist/map-m6f5a_IA.d.ts +0 -56
- package/dist/token-util-4a2xxnpg.js +0 -1
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir, open, readFile, rename, rm, stat
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatQuota,
|
|
3
|
+
isQuotaStale,
|
|
3
4
|
projectQuota,
|
|
4
5
|
quotaWindowName
|
|
5
6
|
} from "./shared/d5a8f1061ee2f114-index.js";
|
|
7
|
+
import { orderForPlacement } from "./shared/3e7d45a77c6ad4d5-index.js";
|
|
8
|
+
import {
|
|
9
|
+
orderedPlacement,
|
|
10
|
+
vaultMainRouteId
|
|
11
|
+
} from "./shared/abd062d633e29fd2-pool-routing.js";
|
|
6
12
|
const QUOTA_WINDOW_KEYS = ["primary", "secondary"], LEGACY_WINDOW_MINUTES = {
|
|
7
13
|
primary: 300,
|
|
8
14
|
secondary: 10080
|
|
@@ -167,11 +173,14 @@ function normalizeAccountList(raw) {
|
|
|
167
173
|
return [];
|
|
168
174
|
return raw.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && typeof entry.id === "string").map((e) => ({
|
|
169
175
|
id: e.id,
|
|
176
|
+
...typeof e.credentialId === "string" ? { credentialId: e.credentialId } : {},
|
|
170
177
|
label: typeof e.label === "string" ? e.label : void 0,
|
|
171
178
|
...typeof e.accountId === "string" ? { accountId: e.accountId } : {},
|
|
172
179
|
quota: "quota" in e ? e.quota : null,
|
|
173
180
|
killed: typeof e.killed === "boolean" ? e.killed : !1,
|
|
174
181
|
enabled: typeof e.enabled === "boolean" ? e.enabled : !0,
|
|
182
|
+
...typeof e.routable === "boolean" ? { routable: e.routable } : {},
|
|
183
|
+
...["needs sign-in", "turned off in vault", "unavailable"].includes(e.vaultState) ? { vaultState: e.vaultState } : {},
|
|
175
184
|
...resetCreditsField(e.resetCredits)
|
|
176
185
|
}));
|
|
177
186
|
}
|
|
@@ -192,7 +201,7 @@ export function isUsableRoutingEntry(entry, accounts, now = Date.now()) {
|
|
|
192
201
|
return !1;
|
|
193
202
|
if (accounts === void 0)
|
|
194
203
|
return !0;
|
|
195
|
-
return entry.activeId === "main" || accounts.some((account) => account.enabled !== !1 && account.killed !== !0 && account.id === entry.activeId);
|
|
204
|
+
return entry.activeId === "main" || accounts.some((account) => account.enabled !== !1 && account.routable !== !1 && account.killed !== !0 && account.id === entry.activeId);
|
|
196
205
|
}
|
|
197
206
|
export function spendControlExhaustedResetAt(quota, now = Date.now()) {
|
|
198
207
|
const spendControl = quota?.spendControl;
|
|
@@ -229,28 +238,40 @@ export function resolveSessionStickyAccount(state, sessionId, now = Date.now())
|
|
|
229
238
|
const assignment = state.stickyAssignments?.[hashSidebarSessionId(sessionId)];
|
|
230
239
|
if (!assignment || assignment.lastSeenAt < now - STICKY_ASSIGNMENT_MAX_AGE_MS)
|
|
231
240
|
return;
|
|
232
|
-
if (assignment.accountId === "main")
|
|
241
|
+
if (assignment.accountId === "main" && state.vaultAccounts === void 0)
|
|
233
242
|
return state.main?.killed || isQuotaExhausted(state.main?.quota, now) ? void 0 : "main";
|
|
234
243
|
const fallback = routingAccountsOf(state).find((account) => account.id === assignment.accountId);
|
|
235
|
-
if (!fallback || fallback.enabled === !1 || fallback.killed === !0 || isQuotaExhausted(fallback.quota, now))
|
|
244
|
+
if (!fallback || fallback.enabled === !1 || fallback.routable === !1 || fallback.killed === !0 || isQuotaExhausted(fallback.quota, now))
|
|
236
245
|
return;
|
|
237
246
|
return fallback.id;
|
|
238
247
|
}
|
|
239
248
|
export function resolveSessionSidebarRouting(state, sessionId, now = Date.now()) {
|
|
240
|
-
if (!sessionId)
|
|
241
|
-
|
|
249
|
+
if (!sessionId) {
|
|
250
|
+
if (state.vaultAccounts === void 0)
|
|
251
|
+
return { activeId: state.activeId ?? "main", route: state.route };
|
|
252
|
+
const active = state.vaultAccounts.find((account) => account.id === state.activeId && account.enabled && account.routable !== !1 && !account.killed);
|
|
253
|
+
if (active && !isQuotaExhausted(active.quota, now))
|
|
254
|
+
return { activeId: active.id, route: state.route };
|
|
255
|
+
}
|
|
242
256
|
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))
|
|
257
|
+
if (own && (state.vaultAccounts === void 0 || own.activeId.startsWith("vault:")) && isUsableRoutingEntry(own, accounts, now) && !isQuotaExhausted(ownQuota, now))
|
|
244
258
|
return { activeId: own.activeId, route: own.route };
|
|
245
259
|
const stickyAccountId = resolveSessionStickyAccount(state, sessionId, now);
|
|
246
260
|
if (stickyAccountId)
|
|
247
261
|
return { activeId: stickyAccountId, route: state.route };
|
|
248
|
-
const vaultMode = state.vaultAccounts !== void 0, enabledFallbacks = (vaultMode ? state
|
|
262
|
+
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
263
|
return {
|
|
250
|
-
activeId:
|
|
264
|
+
activeId: vaultMode ? fallback?.id ?? "vault" : state.route === "fallback-first" && fallback ? fallback.id : "main",
|
|
251
265
|
route: state.route
|
|
252
266
|
};
|
|
253
267
|
}
|
|
268
|
+
function vaultAccountsInPlacementOrder(state) {
|
|
269
|
+
const accounts = state.vaultAccounts ?? [], byId = new Map(accounts.map((account) => [account.id, account]));
|
|
270
|
+
return orderForPlacement(accounts.map((account) => account.id), placementOf(state), vaultMainRouteId(accounts) ?? "").flatMap((id) => {
|
|
271
|
+
const account = byId.get(id);
|
|
272
|
+
return account ? [account] : [];
|
|
273
|
+
});
|
|
274
|
+
}
|
|
254
275
|
export function pruneActiveRouting(activeRouting, accounts, now = Date.now(), removedSessionId) {
|
|
255
276
|
if (!activeRouting)
|
|
256
277
|
return;
|
|
@@ -407,7 +428,14 @@ function writeMergedSidebarState(file, merge, hooks) {
|
|
|
407
428
|
if (hooks?.canWrite && !hooks.canWrite())
|
|
408
429
|
return;
|
|
409
430
|
const next = merge(latest);
|
|
410
|
-
|
|
431
|
+
if (next === void 0)
|
|
432
|
+
return;
|
|
433
|
+
if (next.vaultAccounts !== void 0) {
|
|
434
|
+
next.activeId = next.activeId?.startsWith("vault:") ? next.activeId : void 0;
|
|
435
|
+
next.activeRouting = Object.fromEntries(Object.entries(next.activeRouting ?? {}).filter(([, entry]) => entry.activeId.startsWith("vault:")));
|
|
436
|
+
next.stickyAssignments = Object.fromEntries(Object.entries(next.stickyAssignments ?? {}).filter(([, pin]) => pin.accountId.startsWith("vault:") && !next.vaultAccounts?.some((account) => account.id === pin.accountId && account.killed)));
|
|
437
|
+
}
|
|
438
|
+
return normalizeSidebarState(next);
|
|
411
439
|
}, {
|
|
412
440
|
beforeRecheck: async (...args) => {
|
|
413
441
|
await hooks?.beforeRecheck?.(...args);
|
|
@@ -493,7 +521,7 @@ function mergeQuotaByWindow(incoming, existing) {
|
|
|
493
521
|
export function setSidebarMachineState(machineState, file = getSidebarStateFile(), hooks) {
|
|
494
522
|
return enqueueSidebarWrite(async () => {
|
|
495
523
|
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);
|
|
524
|
+
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
525
|
return {
|
|
498
526
|
...latest,
|
|
499
527
|
...machineState,
|
|
@@ -530,7 +558,7 @@ export function upsertSidebarActiveRouting(input, accounts, file = getSidebarSta
|
|
|
530
558
|
route: input.route,
|
|
531
559
|
updatedAt: input.updatedAt
|
|
532
560
|
}
|
|
533
|
-
}, accounts, Date.now()), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, usableRoutingAccountIds(accounts), Date.now());
|
|
561
|
+
}, accounts, Date.now()), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, latest.vaultAccounts !== void 0 ? void 0 : usableRoutingAccountIds(accounts), Date.now());
|
|
534
562
|
return {
|
|
535
563
|
...latest,
|
|
536
564
|
activeId: input.activeId,
|
|
@@ -555,7 +583,7 @@ export function setSidebarLegacyRouting(input, file = getSidebarStateFile(), hoo
|
|
|
555
583
|
export function removeSidebarActiveRouting(sessionId, accounts, file = getSidebarStateFile(), hooks) {
|
|
556
584
|
return enqueueSidebarWrite(async () => {
|
|
557
585
|
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));
|
|
586
|
+
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
587
|
return {
|
|
560
588
|
...latest,
|
|
561
589
|
activeRouting,
|
|
@@ -652,7 +680,7 @@ export function persistSidebarStickyAssignment(input, file = getSidebarStateFile
|
|
|
652
680
|
const sessionHash = hashSidebarSessionId(input.sessionId), validPinnedAccountIds = new Set(input.validPinnedAccountIds);
|
|
653
681
|
return enqueueSidebarWrite(async () => {
|
|
654
682
|
await writeMergedSidebarState(file, (latest) => {
|
|
655
|
-
const now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, validPinnedAccountIds, now), current = stickyAssignments?.[sessionHash];
|
|
683
|
+
const now = Date.now(), stickyAssignments = pruneStickyAssignments(latest.stickyAssignments, latest.vaultAccounts !== void 0 ? void 0 : validPinnedAccountIds, now), current = stickyAssignments?.[sessionHash];
|
|
656
684
|
if (current && current.assignedAt > input.assignment.assignedAt) {
|
|
657
685
|
if (stickyAssignmentsEqual(latest.stickyAssignments, stickyAssignments))
|
|
658
686
|
return;
|
|
@@ -875,7 +903,7 @@ export function createSidebarBookkeepingQueue(options) {
|
|
|
875
903
|
export function resolveActiveAccount(state) {
|
|
876
904
|
const activeId = state.activeId;
|
|
877
905
|
if (state.vaultAccounts !== void 0) {
|
|
878
|
-
const enabled = state.
|
|
906
|
+
const enabled = vaultAccountsInPlacementOrder(state).filter((account) => account.enabled && account.routable !== !1), vaultAccount = enabled.find((account) => account.id === activeId) ?? enabled[0];
|
|
879
907
|
return vaultAccount ? {
|
|
880
908
|
id: vaultAccount.id,
|
|
881
909
|
name: vaultAccount.label ?? vaultAccount.id,
|
|
@@ -935,6 +963,67 @@ export function getCollapsedQuotaSummary(quota, now = Date.now()) {
|
|
|
935
963
|
text: !projection || projection.limits.length === 0 && !projection.budget ? null : formatQuota(projection, { now, form: "compact" })
|
|
936
964
|
};
|
|
937
965
|
}
|
|
966
|
+
function placementOf(state) {
|
|
967
|
+
return state.route === "sticky-balanced" ? "roster" : orderedPlacement(state.route);
|
|
968
|
+
}
|
|
969
|
+
export function getCollapsedAccountLines(state, options = {}) {
|
|
970
|
+
const now = options.now ?? Date.now(), activeId = resolveActiveAccount(state).id;
|
|
971
|
+
return (state.vaultAccounts !== void 0 ? vaultAccountsInPlacementOrder(state).map((account) => ({
|
|
972
|
+
id: account.id,
|
|
973
|
+
name: account.label ?? account.id,
|
|
974
|
+
quota: account.quota,
|
|
975
|
+
killed: account.killed
|
|
976
|
+
})) : localAccountsInPlacementOrder(state, activeId, options.showFallbacks ?? !0)).map((account) => {
|
|
977
|
+
const projection = sidebarQuotaProjection(account.quota, now), reading = projection && (projection.limits.length > 0 || projection.budget) ? projection : void 0;
|
|
978
|
+
return {
|
|
979
|
+
id: account.id,
|
|
980
|
+
name: account.name,
|
|
981
|
+
killed: account.killed,
|
|
982
|
+
active: account.id === activeId,
|
|
983
|
+
stale: reading ? isQuotaStale(reading, { now }) : !1,
|
|
984
|
+
text: reading ? formatQuota(reading, { now, form: "narrow" }) : null
|
|
985
|
+
};
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
function localAccountsInPlacementOrder(state, activeId, showFallbacks) {
|
|
989
|
+
const accounts = [
|
|
990
|
+
{
|
|
991
|
+
id: "main",
|
|
992
|
+
name: "main",
|
|
993
|
+
quota: state.main.quota,
|
|
994
|
+
killed: state.main.killed
|
|
995
|
+
},
|
|
996
|
+
...state.fallbacks.filter((account) => account.enabled && (showFallbacks || account.id === activeId)).map((account) => ({
|
|
997
|
+
id: account.id,
|
|
998
|
+
name: account.label ?? account.id,
|
|
999
|
+
quota: account.quota,
|
|
1000
|
+
killed: account.killed
|
|
1001
|
+
}))
|
|
1002
|
+
], byId = new Map(accounts.map((account) => [account.id, account]));
|
|
1003
|
+
return orderForPlacement(accounts.map((account) => account.id), placementOf(state), "main").flatMap((id) => {
|
|
1004
|
+
const account = byId.get(id);
|
|
1005
|
+
return account ? [account] : [];
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
export function displayWidth(text) {
|
|
1009
|
+
const bun = globalThis.Bun;
|
|
1010
|
+
return bun?.stringWidth ? bun.stringWidth(text) : Array.from(text).length;
|
|
1011
|
+
}
|
|
1012
|
+
export function fitCollapsedLabel(label, columns) {
|
|
1013
|
+
if (displayWidth(label) <= columns)
|
|
1014
|
+
return label;
|
|
1015
|
+
if (columns < 1)
|
|
1016
|
+
return "";
|
|
1017
|
+
let kept = "", used = 0;
|
|
1018
|
+
for (const char of label) {
|
|
1019
|
+
const width = displayWidth(char);
|
|
1020
|
+
if (used + width > columns - 1)
|
|
1021
|
+
break;
|
|
1022
|
+
kept += char;
|
|
1023
|
+
used += width;
|
|
1024
|
+
}
|
|
1025
|
+
return `${kept}\u2026`;
|
|
1026
|
+
}
|
|
938
1027
|
const PACING_MIN_ELAPSED_MS = 300000, PACING_MIN_ELAPSED_FRACTION = 0.01, ON_PACE_DELTA = 1;
|
|
939
1028
|
export function computeQuotaPacing(window, windowMs, now) {
|
|
940
1029
|
if (!window.resetsAt)
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ApplyResult,
|
|
18
18
|
OpenDialogPayload,
|
|
19
19
|
} from "../rpc/protocol.js"
|
|
20
|
+
import { factLines, sectionTitle } from "./section-text.js"
|
|
20
21
|
|
|
21
22
|
const log = createLogger('rpc-tui')
|
|
22
23
|
|
|
@@ -48,31 +49,8 @@ export function sectionListOptions(menu: Menu): DrawerOption[] {
|
|
|
48
49
|
}))
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
return Object.entries(facts ?? {}).map(
|
|
53
|
-
([name, value]) =>
|
|
54
|
-
`${name}: ${typeof value === 'string' ? value : JSON.stringify(value)}`,
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Read-only text is a category header; every option navigates or runs an action. */
|
|
52
|
+
/** Every option navigates or runs an action; read-only context is in the title. */
|
|
59
53
|
export function sectionOptions(section: Section): DrawerOption[] {
|
|
60
|
-
const header = [
|
|
61
|
-
...section.lines,
|
|
62
|
-
...factLines(section.facts),
|
|
63
|
-
...section.items
|
|
64
|
-
.filter((item) => item.actions.length === 0)
|
|
65
|
-
.map((item) =>
|
|
66
|
-
[
|
|
67
|
-
item.group,
|
|
68
|
-
[item.label, item.status].filter(Boolean).join(' · '),
|
|
69
|
-
item.detail,
|
|
70
|
-
...factLines(item.facts),
|
|
71
|
-
]
|
|
72
|
-
.filter(Boolean)
|
|
73
|
-
.join('\n'),
|
|
74
|
-
),
|
|
75
|
-
]
|
|
76
54
|
const options: DrawerOption[] = [
|
|
77
55
|
...section.items
|
|
78
56
|
.filter((item) => item.actions.length > 0)
|
|
@@ -91,15 +69,6 @@ export function sectionOptions(section: Section): DrawerOption[] {
|
|
|
91
69
|
})),
|
|
92
70
|
{ title: 'Back', value: BACK },
|
|
93
71
|
]
|
|
94
|
-
const first = options[0]
|
|
95
|
-
if (first && header.length > 0) {
|
|
96
|
-
const category = first.category
|
|
97
|
-
const text = [...header, category].filter(Boolean).join('\n')
|
|
98
|
-
for (const option of options) {
|
|
99
|
-
if (option.category !== category) break
|
|
100
|
-
option.category = text
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
72
|
return options
|
|
104
73
|
}
|
|
105
74
|
|
|
@@ -200,7 +169,7 @@ function showSection(
|
|
|
200
169
|
api.ui.dialog.setSize('xlarge')
|
|
201
170
|
api.ui.dialog.replace(() => (
|
|
202
171
|
<DialogSelect
|
|
203
|
-
title={section
|
|
172
|
+
title={sectionTitle(section)}
|
|
204
173
|
options={sectionOptions(section)}
|
|
205
174
|
onSelect={(option) => {
|
|
206
175
|
const value = String(option.value)
|
|
@@ -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,133 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { Plugin } from "@opencode/plugin/tui"
|
|
3
|
+
import type { JSX } from "solid-js"
|
|
4
|
+
import {
|
|
5
|
+
type ApplyRequest,
|
|
6
|
+
type ApplyResult,
|
|
7
|
+
isNotifyPayload,
|
|
8
|
+
type OpenDialogPayload,
|
|
9
|
+
} from "../rpc/protocol.js"
|
|
10
|
+
import { createRpcClient } from "../rpc/rpc-client.js"
|
|
11
|
+
import { resolveRpcDir } from "../rpc/rpc-dir.js"
|
|
12
|
+
import { openControlDialog } from "./v2-dialog.js"
|
|
13
|
+
|
|
14
|
+
export interface TuiTransportOptions {
|
|
15
|
+
/** Test-only label for proving that the host can render a sidebar component. */
|
|
16
|
+
sidebarText?: string
|
|
17
|
+
sidebar?: {
|
|
18
|
+
render(input: { sessionID: string }): JSX.Element
|
|
19
|
+
dispose(): void
|
|
20
|
+
}
|
|
21
|
+
openDialog?: (
|
|
22
|
+
payload: OpenDialogPayload,
|
|
23
|
+
apply: (request: Omit<ApplyRequest, 'sessionId'>) => Promise<ApplyResult>,
|
|
24
|
+
) => Promise<void>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const KEY = Symbol.for('cortexkit.openai-auth.v2.tui')
|
|
28
|
+
type Mount = { dispose(): void }
|
|
29
|
+
type State = { mount?: Mount; lastIds: Map<string, number> }
|
|
30
|
+
|
|
31
|
+
/** Uses OpenCode 2's transport without running the OpenCode 1 UI registration. */
|
|
32
|
+
export async function setupTui(
|
|
33
|
+
context: Plugin.Context,
|
|
34
|
+
options: TuiTransportOptions = {},
|
|
35
|
+
) {
|
|
36
|
+
const processState = globalThis as typeof globalThis & { [KEY]?: State }
|
|
37
|
+
processState[KEY] ??= { lastIds: new Map() }
|
|
38
|
+
const state = processState[KEY]
|
|
39
|
+
state.mount?.dispose()
|
|
40
|
+
const openDialog: NonNullable<TuiTransportOptions['openDialog']> =
|
|
41
|
+
options.openDialog ??
|
|
42
|
+
((payload, apply) =>
|
|
43
|
+
openControlDialog(context.ui.dialog, payload, apply, () => !disposed))
|
|
44
|
+
let disposed = false
|
|
45
|
+
let inFlight = false
|
|
46
|
+
let dialogOpen = false
|
|
47
|
+
let directory = ''
|
|
48
|
+
let client: ReturnType<typeof createRpcClient> | undefined
|
|
49
|
+
const unmount =
|
|
50
|
+
options.sidebarText === undefined
|
|
51
|
+
? options.sidebar
|
|
52
|
+
? context.ui.slot({
|
|
53
|
+
append: 'sidebar.content',
|
|
54
|
+
render: options.sidebar.render,
|
|
55
|
+
})
|
|
56
|
+
: undefined
|
|
57
|
+
: context.ui.slot({
|
|
58
|
+
append: 'sidebar.content',
|
|
59
|
+
render: () => <text>{options.sidebarText}</text>,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const poll = async () => {
|
|
63
|
+
if (disposed || inFlight || dialogOpen) return
|
|
64
|
+
const route = context.ui.router.current()
|
|
65
|
+
if (route.type !== 'session') return
|
|
66
|
+
const sessionId = route.sessionID
|
|
67
|
+
const session = context.data.session.get(sessionId)
|
|
68
|
+
if (!session) return
|
|
69
|
+
inFlight = true
|
|
70
|
+
try {
|
|
71
|
+
if (directory !== session.location.directory) {
|
|
72
|
+
directory = session.location.directory
|
|
73
|
+
const resolved = await resolveRpcDir(directory)
|
|
74
|
+
if (disposed) return
|
|
75
|
+
client = createRpcClient(resolved.dir, process.pid)
|
|
76
|
+
}
|
|
77
|
+
const rpc = client
|
|
78
|
+
if (!rpc) return
|
|
79
|
+
const messages = await rpc.pending(
|
|
80
|
+
state.lastIds.get(sessionId) ?? 0,
|
|
81
|
+
sessionId,
|
|
82
|
+
)
|
|
83
|
+
if (
|
|
84
|
+
disposed ||
|
|
85
|
+
context.ui.router.current().type !== 'session' ||
|
|
86
|
+
(context.ui.router.current() as { sessionID?: string }).sessionID !==
|
|
87
|
+
sessionId
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
for (const message of [...messages].sort((a, b) => a.id - b.id)) {
|
|
91
|
+
state.lastIds.set(
|
|
92
|
+
sessionId,
|
|
93
|
+
Math.max(state.lastIds.get(sessionId) ?? 0, message.id),
|
|
94
|
+
)
|
|
95
|
+
if (isNotifyPayload(message.payload))
|
|
96
|
+
context.ui.toast.show({
|
|
97
|
+
message: message.payload.notify.message,
|
|
98
|
+
sessionID: sessionId,
|
|
99
|
+
})
|
|
100
|
+
else {
|
|
101
|
+
dialogOpen = true
|
|
102
|
+
await openDialog(message.payload, (request) => {
|
|
103
|
+
if (disposed) throw new Error('The OpenAI dialog was closed')
|
|
104
|
+
return rpc.apply({ ...request, sessionId }, 90_000)
|
|
105
|
+
}).finally(() => {
|
|
106
|
+
dialogOpen = false
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// The server can be absent while a location starts or is reloaded.
|
|
112
|
+
} finally {
|
|
113
|
+
inFlight = false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const timer = setInterval(() => {
|
|
117
|
+
void poll()
|
|
118
|
+
}, 500)
|
|
119
|
+
const mount: Mount = {
|
|
120
|
+
dispose() {
|
|
121
|
+
if (disposed) return
|
|
122
|
+
disposed = true
|
|
123
|
+
clearInterval(timer)
|
|
124
|
+
unmount?.()
|
|
125
|
+
options.sidebar?.dispose()
|
|
126
|
+
if (dialogOpen) context.ui.dialog.clear()
|
|
127
|
+
if (state.mount === mount) state.mount = undefined
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
state.mount = mount
|
|
131
|
+
void poll()
|
|
132
|
+
return () => mount.dispose()
|
|
133
|
+
}
|