@slkiser/opencode-quota 4.5.1 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/data/modelsdev-pricing.min.json +5 -0
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +75 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/maintainer-announcements.d.ts +3 -0
- package/dist/lib/maintainer-announcements.d.ts.map +1 -1
- package/dist/lib/maintainer-announcements.js +13 -0
- package/dist/lib/maintainer-announcements.js.map +1 -1
- package/dist/lib/quota-export.d.ts +1 -0
- package/dist/lib/quota-export.d.ts.map +1 -1
- package/dist/lib/quota-export.js +4 -0
- package/dist/lib/quota-export.js.map +1 -1
- package/dist/lib/quota-render-data.d.ts +2 -0
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +7 -0
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-reset-notifications.d.ts +20 -0
- package/dist/lib/quota-reset-notifications.d.ts.map +1 -0
- package/dist/lib/quota-reset-notifications.js +328 -0
- package/dist/lib/quota-reset-notifications.js.map +1 -0
- package/dist/lib/quota-stats.d.ts.map +1 -1
- package/dist/lib/quota-stats.js +4 -0
- package/dist/lib/quota-stats.js.map +1 -1
- package/dist/lib/tui-panel-state.d.ts +15 -0
- package/dist/lib/tui-panel-state.d.ts.map +1 -1
- package/dist/lib/tui-panel-state.js.map +1 -1
- package/dist/lib/tui-refresh-lifecycle.d.ts.map +1 -1
- package/dist/lib/tui-refresh-lifecycle.js +0 -1
- package/dist/lib/tui-refresh-lifecycle.js.map +1 -1
- package/dist/lib/tui-runtime.d.ts +14 -2
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +87 -6
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/types.d.ts +14 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +7 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +38 -1
- package/dist/plugin.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +472 -181
- package/dist/tui.tsx +395 -73
- package/package.json +1 -1
package/dist/tui.tsx
CHANGED
|
@@ -8,18 +8,21 @@ import type {
|
|
|
8
8
|
} from "@opencode-ai/plugin/tui";
|
|
9
9
|
import type { JSX } from "@opentui/solid";
|
|
10
10
|
import { createEffect, createSignal, onCleanup, Show } from "solid-js";
|
|
11
|
+
import { formatDisplayedPercentLabel, formatResetCountdown } from "./lib/format-utils.js";
|
|
11
12
|
import {
|
|
12
13
|
buildQuotaDialogCommandOutput,
|
|
13
14
|
QUOTA_DIALOG_COMMANDS,
|
|
14
15
|
type QuotaDialogCommandId,
|
|
15
16
|
type QuotaDialogCommandSpec,
|
|
16
17
|
} from "./lib/quota-dialog-commands.js";
|
|
18
|
+
import { extractSingleWindowWindowLabel } from "./lib/quota-entry-display.js";
|
|
17
19
|
import type { SessionTokenError } from "./lib/quota-status.js";
|
|
18
20
|
import { disposeQuotaTelemetryOwner } from "./lib/quota-telemetry.js";
|
|
19
21
|
import { getSidebarBodyLineColor } from "./lib/tui-line-style.js";
|
|
20
22
|
import type {
|
|
21
23
|
CompactStatusState,
|
|
22
24
|
HomeBottomState,
|
|
25
|
+
PromptBarState,
|
|
23
26
|
SidebarPanelState,
|
|
24
27
|
} from "./lib/tui-panel-state.js";
|
|
25
28
|
import {
|
|
@@ -40,6 +43,8 @@ import {
|
|
|
40
43
|
loadTuiSessionQuotaSurfaces,
|
|
41
44
|
normalizeTuiSessionID,
|
|
42
45
|
resolveTuiSurfaceRegistration,
|
|
46
|
+
type TuiInitialRuntimeSeed,
|
|
47
|
+
type TuiSurfaceRegistration,
|
|
43
48
|
writeTuiQuotaExportIfEnabled,
|
|
44
49
|
} from "./lib/tui-runtime.js";
|
|
45
50
|
import type { TuiCommandDisplay } from "./lib/types.js";
|
|
@@ -63,6 +68,7 @@ type SessionQuotaResource = {
|
|
|
63
68
|
sessionID: string;
|
|
64
69
|
sidebar: () => SidebarPanelState;
|
|
65
70
|
compact: () => CompactStatusState;
|
|
71
|
+
promptBar: () => PromptBarState;
|
|
66
72
|
retain: () => SessionQuotaResource;
|
|
67
73
|
release: () => void;
|
|
68
74
|
};
|
|
@@ -73,6 +79,78 @@ type HomeBottomResource = {
|
|
|
73
79
|
release: () => void;
|
|
74
80
|
};
|
|
75
81
|
|
|
82
|
+
type TuiInitialLoadCoordinator = {
|
|
83
|
+
takeInitialSession: () => TuiInitialRuntimeSeed | undefined;
|
|
84
|
+
takeInitialHome: () => TuiInitialRuntimeSeed | undefined;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type TuiRegistrationState =
|
|
88
|
+
| { status: "pending" }
|
|
89
|
+
| {
|
|
90
|
+
status: "active";
|
|
91
|
+
registration: TuiSurfaceRegistration;
|
|
92
|
+
initialLoads?: TuiInitialLoadCoordinator;
|
|
93
|
+
}
|
|
94
|
+
| { status: "disposed" };
|
|
95
|
+
|
|
96
|
+
type TuiRegistrationGate = {
|
|
97
|
+
current: () => TuiRegistrationState;
|
|
98
|
+
activate: (
|
|
99
|
+
registration: TuiSurfaceRegistration,
|
|
100
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
101
|
+
) => void;
|
|
102
|
+
dispose: () => void;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const FALLBACK_SURFACE_REGISTRATION: TuiSurfaceRegistration = {
|
|
106
|
+
commandDisplay: "inline",
|
|
107
|
+
sidebar: { enabled: true },
|
|
108
|
+
compact: {
|
|
109
|
+
enabled: false,
|
|
110
|
+
homeBottom: false,
|
|
111
|
+
sessionPrompt: false,
|
|
112
|
+
hasNativeProviderQuota: false,
|
|
113
|
+
suppressedByNativeProviderQuota: false,
|
|
114
|
+
},
|
|
115
|
+
promptBar: { enabled: false },
|
|
116
|
+
announcements: { homeBottom: false },
|
|
117
|
+
homeBottom: false,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function createTuiInitialLoadCoordinator(seed: TuiInitialRuntimeSeed): TuiInitialLoadCoordinator {
|
|
121
|
+
let sessionAvailable = true;
|
|
122
|
+
let homeAvailable = true;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
takeInitialSession() {
|
|
126
|
+
if (!sessionAvailable) return undefined;
|
|
127
|
+
sessionAvailable = false;
|
|
128
|
+
return seed;
|
|
129
|
+
},
|
|
130
|
+
takeInitialHome() {
|
|
131
|
+
if (!homeAvailable) return undefined;
|
|
132
|
+
homeAvailable = false;
|
|
133
|
+
return seed;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function createTuiRegistrationGate(): TuiRegistrationGate {
|
|
139
|
+
const [current, setCurrent] = createSignal<TuiRegistrationState>({ status: "pending" });
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
current,
|
|
143
|
+
activate(registration, initialLoads) {
|
|
144
|
+
if (current().status !== "pending") return;
|
|
145
|
+
setCurrent({ status: "active", registration, initialLoads });
|
|
146
|
+
},
|
|
147
|
+
dispose() {
|
|
148
|
+
if (current().status === "disposed") return;
|
|
149
|
+
setCurrent({ status: "disposed" });
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
76
154
|
const sessionResources = new WeakMap<TuiPluginApi, Map<string, SessionQuotaResource>>();
|
|
77
155
|
const homeResources = new WeakMap<TuiPluginApi, HomeBottomResource>();
|
|
78
156
|
|
|
@@ -85,18 +163,33 @@ function getSessionResourceMap(api: TuiPluginApi): Map<string, SessionQuotaResou
|
|
|
85
163
|
return next;
|
|
86
164
|
}
|
|
87
165
|
|
|
88
|
-
function createSessionQuotaResource(
|
|
166
|
+
function createSessionQuotaResource(
|
|
167
|
+
api: TuiPluginApi,
|
|
168
|
+
sessionID: string,
|
|
169
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
170
|
+
): SessionQuotaResource {
|
|
89
171
|
const [sidebar, setSidebar] = createSignal<SidebarPanelState>({
|
|
90
172
|
status: "loading",
|
|
91
173
|
lines: [],
|
|
92
174
|
});
|
|
93
175
|
const [compact, setCompact] = createSignal<CompactStatusState>({ status: "loading" });
|
|
176
|
+
const [promptBar, setPromptBar] = createSignal<PromptBarState>({ status: "loading" });
|
|
94
177
|
|
|
178
|
+
let loadOrdinal = 0;
|
|
95
179
|
const lifecycle = createTuiRefreshLifecycle({
|
|
96
|
-
load: () =>
|
|
180
|
+
load: () => {
|
|
181
|
+
const initialRuntimeSeed = loadOrdinal === 0 ? initialLoads?.takeInitialSession() : undefined;
|
|
182
|
+
loadOrdinal += 1;
|
|
183
|
+
return loadTuiSessionQuotaSurfaces({
|
|
184
|
+
api,
|
|
185
|
+
sessionID,
|
|
186
|
+
...(initialRuntimeSeed ? { initialRuntimeSeed } : {}),
|
|
187
|
+
});
|
|
188
|
+
},
|
|
97
189
|
apply: (next) => {
|
|
98
190
|
setSidebar(next.sidebar);
|
|
99
191
|
setCompact(next.compact);
|
|
192
|
+
setPromptBar(next.promptBar ?? { status: "loading" });
|
|
100
193
|
},
|
|
101
194
|
intervalMs: REFRESH_INTERVAL_MS,
|
|
102
195
|
eventRefreshDelaysMs: EVENT_REFRESH_DELAYS_MS,
|
|
@@ -134,6 +227,7 @@ function createSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sessi
|
|
|
134
227
|
sessionID,
|
|
135
228
|
sidebar,
|
|
136
229
|
compact,
|
|
230
|
+
promptBar,
|
|
137
231
|
retain: () => {
|
|
138
232
|
lifecycle.retain();
|
|
139
233
|
return resource;
|
|
@@ -144,12 +238,16 @@ function createSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sessi
|
|
|
144
238
|
return resource;
|
|
145
239
|
}
|
|
146
240
|
|
|
147
|
-
function acquireSessionQuotaResource(
|
|
241
|
+
function acquireSessionQuotaResource(
|
|
242
|
+
api: TuiPluginApi,
|
|
243
|
+
sessionID: string,
|
|
244
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
245
|
+
): SessionQuotaResource {
|
|
148
246
|
const resources = getSessionResourceMap(api);
|
|
149
247
|
const existing = resources.get(sessionID);
|
|
150
248
|
if (existing) return existing.retain();
|
|
151
249
|
|
|
152
|
-
const next = createSessionQuotaResource(api, sessionID).retain();
|
|
250
|
+
const next = createSessionQuotaResource(api, sessionID, initialLoads).retain();
|
|
153
251
|
resources.set(sessionID, next);
|
|
154
252
|
return next;
|
|
155
253
|
}
|
|
@@ -157,14 +255,23 @@ function acquireSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sess
|
|
|
157
255
|
function createHomeBottomResource(
|
|
158
256
|
api: TuiPluginApi,
|
|
159
257
|
compactHomeBottomEnabled: boolean,
|
|
258
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
160
259
|
): HomeBottomResource {
|
|
161
260
|
const [bottom, setBottom] = createSignal<HomeBottomState>({
|
|
162
261
|
status: "loading",
|
|
163
262
|
compact: compactHomeBottomEnabled ? { status: "loading" } : { status: "disabled" },
|
|
164
263
|
});
|
|
165
264
|
|
|
265
|
+
let loadOrdinal = 0;
|
|
166
266
|
const lifecycle = createTuiRefreshLifecycle({
|
|
167
|
-
load: () =>
|
|
267
|
+
load: () => {
|
|
268
|
+
const initialRuntimeSeed = loadOrdinal === 0 ? initialLoads?.takeInitialHome() : undefined;
|
|
269
|
+
loadOrdinal += 1;
|
|
270
|
+
return loadTuiHomeBottomStatus({
|
|
271
|
+
api,
|
|
272
|
+
...(initialRuntimeSeed ? { initialRuntimeSeed } : {}),
|
|
273
|
+
});
|
|
274
|
+
},
|
|
168
275
|
apply: setBottom,
|
|
169
276
|
afterApply: () => {
|
|
170
277
|
// Fire-and-forget: write export file if enabled. A failed write must
|
|
@@ -201,11 +308,12 @@ function createHomeBottomResource(
|
|
|
201
308
|
function acquireHomeBottomResource(
|
|
202
309
|
api: TuiPluginApi,
|
|
203
310
|
compactHomeBottomEnabled: boolean,
|
|
311
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
204
312
|
): HomeBottomResource {
|
|
205
313
|
const existing = homeResources.get(api);
|
|
206
314
|
if (existing) return existing.retain();
|
|
207
315
|
|
|
208
|
-
const next = createHomeBottomResource(api, compactHomeBottomEnabled).retain();
|
|
316
|
+
const next = createHomeBottomResource(api, compactHomeBottomEnabled, initialLoads).retain();
|
|
209
317
|
homeResources.set(api, next);
|
|
210
318
|
return next;
|
|
211
319
|
}
|
|
@@ -213,8 +321,9 @@ function acquireHomeBottomResource(
|
|
|
213
321
|
function useSessionQuotaResource(
|
|
214
322
|
api: TuiPluginApi,
|
|
215
323
|
sessionID: () => string,
|
|
324
|
+
initialLoads?: TuiInitialLoadCoordinator,
|
|
216
325
|
): () => SessionQuotaResource {
|
|
217
|
-
let current = acquireSessionQuotaResource(api, sessionID());
|
|
326
|
+
let current = acquireSessionQuotaResource(api, sessionID(), initialLoads);
|
|
218
327
|
const [resource, setResource] = createSignal(current);
|
|
219
328
|
|
|
220
329
|
createEffect(() => {
|
|
@@ -222,7 +331,7 @@ function useSessionQuotaResource(
|
|
|
222
331
|
if (current.sessionID === nextSessionID) return;
|
|
223
332
|
|
|
224
333
|
const previous = current;
|
|
225
|
-
current = acquireSessionQuotaResource(api, nextSessionID);
|
|
334
|
+
current = acquireSessionQuotaResource(api, nextSessionID, initialLoads);
|
|
226
335
|
setResource(current);
|
|
227
336
|
previous.release();
|
|
228
337
|
});
|
|
@@ -234,8 +343,12 @@ function useSessionQuotaResource(
|
|
|
234
343
|
return resource;
|
|
235
344
|
}
|
|
236
345
|
|
|
237
|
-
function SidebarContentView(props: {
|
|
238
|
-
|
|
346
|
+
function SidebarContentView(props: {
|
|
347
|
+
api: TuiPluginApi;
|
|
348
|
+
sessionID: string;
|
|
349
|
+
initialLoads?: TuiInitialLoadCoordinator;
|
|
350
|
+
}) {
|
|
351
|
+
const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
|
|
239
352
|
const panel = () => resource().sidebar();
|
|
240
353
|
|
|
241
354
|
const lines = () => getSidebarPanelLines(panel());
|
|
@@ -319,12 +432,13 @@ function CompactStatusLine(props: {
|
|
|
319
432
|
function SessionPromptWithCompactStatus(props: {
|
|
320
433
|
api: TuiPluginApi;
|
|
321
434
|
sessionID: string;
|
|
435
|
+
initialLoads?: TuiInitialLoadCoordinator;
|
|
322
436
|
visible?: boolean;
|
|
323
437
|
disabled?: boolean;
|
|
324
438
|
onSubmit?: () => void;
|
|
325
439
|
promptRef?: TuiPromptRefCallback;
|
|
326
440
|
}) {
|
|
327
|
-
const resource = useSessionQuotaResource(props.api, () => props.sessionID);
|
|
441
|
+
const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
|
|
328
442
|
const panel = () => resource().compact();
|
|
329
443
|
|
|
330
444
|
return (
|
|
@@ -341,8 +455,176 @@ function SessionPromptWithCompactStatus(props: {
|
|
|
341
455
|
);
|
|
342
456
|
}
|
|
343
457
|
|
|
344
|
-
|
|
345
|
-
|
|
458
|
+
const PROMPT_BAR_WIDTH = 12;
|
|
459
|
+
|
|
460
|
+
function shouldRenderPromptBar(
|
|
461
|
+
bar: PromptBarState,
|
|
462
|
+
): bar is Extract<PromptBarState, { status: "ready" }> {
|
|
463
|
+
return bar.status === "ready" && Boolean(bar.entry);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function useSessionRunning(api: TuiPluginApi, sessionID: () => string): () => boolean {
|
|
467
|
+
const [running, setRunning] = createSignal(false);
|
|
468
|
+
createEffect(() => {
|
|
469
|
+
const id = sessionID();
|
|
470
|
+
if (!id) {
|
|
471
|
+
setRunning(false);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const update = () => {
|
|
475
|
+
try {
|
|
476
|
+
const sessionState = api.state.session as {
|
|
477
|
+
status?: (sessionID: string) => { type?: string } | undefined;
|
|
478
|
+
};
|
|
479
|
+
const status = sessionState.status?.(id);
|
|
480
|
+
setRunning(status?.type === "busy" || status?.type === "retry");
|
|
481
|
+
} catch {
|
|
482
|
+
setRunning(false);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
update();
|
|
486
|
+
const disposers = [
|
|
487
|
+
api.event.on("session.status", (event) => {
|
|
488
|
+
if (event.properties?.sessionID === id) {
|
|
489
|
+
update();
|
|
490
|
+
}
|
|
491
|
+
}),
|
|
492
|
+
api.event.on("session.updated", (event) => {
|
|
493
|
+
if (event.properties?.info?.id === id) {
|
|
494
|
+
update();
|
|
495
|
+
}
|
|
496
|
+
}),
|
|
497
|
+
];
|
|
498
|
+
onCleanup(() => {
|
|
499
|
+
for (const dispose of disposers) {
|
|
500
|
+
if (typeof dispose === "function") {
|
|
501
|
+
dispose();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
return running;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function buildPromptBarParts(params: {
|
|
510
|
+
bar: () => PromptBarState;
|
|
511
|
+
running: () => boolean;
|
|
512
|
+
phase: () => number;
|
|
513
|
+
}): { label: string; barText: string; meta: string } | undefined {
|
|
514
|
+
const bar = params.bar();
|
|
515
|
+
if (!shouldRenderPromptBar(bar)) return undefined;
|
|
516
|
+
const entry = bar.entry;
|
|
517
|
+
if (!entry) return undefined;
|
|
518
|
+
const windowLabel =
|
|
519
|
+
extractSingleWindowWindowLabel(entry.label ?? "") ??
|
|
520
|
+
extractSingleWindowWindowLabel(entry.name ?? "") ??
|
|
521
|
+
"Quota";
|
|
522
|
+
const percent = formatDisplayedPercentLabel(
|
|
523
|
+
entry.percentRemaining ?? 0,
|
|
524
|
+
bar.percentDisplayMode ?? "remaining",
|
|
525
|
+
);
|
|
526
|
+
const reset = entry.resetTimeIso
|
|
527
|
+
? formatResetCountdown(entry.resetTimeIso, {
|
|
528
|
+
compactRounded: true,
|
|
529
|
+
decimals: bar.resetTimeDecimals,
|
|
530
|
+
})
|
|
531
|
+
: "";
|
|
532
|
+
const p = Math.max(0, Math.min(100, Math.round(entry.percentRemaining ?? 0)));
|
|
533
|
+
const filled = Math.round((p / 100) * PROMPT_BAR_WIDTH);
|
|
534
|
+
const empty = PROMPT_BAR_WIDTH - filled;
|
|
535
|
+
let barText = "█".repeat(filled) + "░".repeat(empty);
|
|
536
|
+
if (params.running() && filled > 0) {
|
|
537
|
+
const cells = Array(filled).fill("▓");
|
|
538
|
+
const center = params.phase() % filled;
|
|
539
|
+
const gradient = ["▒", "▓", "█", "▓", "▒"];
|
|
540
|
+
for (let offset = -2; offset <= 2; offset++) {
|
|
541
|
+
const position = (center + offset + filled) % filled;
|
|
542
|
+
cells[position] = gradient[offset + 2];
|
|
543
|
+
}
|
|
544
|
+
barText = cells.join("") + "░".repeat(empty);
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
label: windowLabel,
|
|
548
|
+
barText,
|
|
549
|
+
meta: [percent.replace(/\s+left$/u, ""), reset].filter(Boolean).join(" | "),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function PromptQuotaHint(props: {
|
|
554
|
+
api: TuiPluginApi;
|
|
555
|
+
bar: () => PromptBarState;
|
|
556
|
+
running: () => boolean;
|
|
557
|
+
phase: () => number;
|
|
558
|
+
}) {
|
|
559
|
+
const parts = () => buildPromptBarParts(props);
|
|
560
|
+
const barColor = () => props.api.theme.current.textMuted;
|
|
561
|
+
const label = () => parts()?.label ?? "";
|
|
562
|
+
const bar = () => parts()?.barText ?? "";
|
|
563
|
+
const meta = () => parts()?.meta ?? "";
|
|
564
|
+
|
|
565
|
+
return (
|
|
566
|
+
<Show when={parts()}>
|
|
567
|
+
<box flexDirection="row" justifyContent="flex-end" gap={1}>
|
|
568
|
+
<text fg={props.api.theme.current.textMuted} wrapMode="none">
|
|
569
|
+
{label()}
|
|
570
|
+
</text>
|
|
571
|
+
<text fg={barColor()} wrapMode="none">
|
|
572
|
+
{bar()}
|
|
573
|
+
</text>
|
|
574
|
+
<text fg={props.api.theme.current.textMuted} wrapMode="none">
|
|
575
|
+
{meta()}
|
|
576
|
+
</text>
|
|
577
|
+
</box>
|
|
578
|
+
</Show>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function SessionQuotaPromptBar(props: {
|
|
583
|
+
api: TuiPluginApi;
|
|
584
|
+
sessionID: string;
|
|
585
|
+
initialLoads?: TuiInitialLoadCoordinator;
|
|
586
|
+
visible?: boolean;
|
|
587
|
+
disabled?: boolean;
|
|
588
|
+
onSubmit?: () => void;
|
|
589
|
+
promptRef?: TuiPromptRefCallback;
|
|
590
|
+
}) {
|
|
591
|
+
const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
|
|
592
|
+
const promptBar = () => resource().promptBar();
|
|
593
|
+
const running = useSessionRunning(props.api, () => props.sessionID);
|
|
594
|
+
const [phase, setPhase] = createSignal(0);
|
|
595
|
+
createEffect(() => {
|
|
596
|
+
if (!running() || !shouldRenderPromptBar(promptBar())) {
|
|
597
|
+
setPhase(0);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const interval = setInterval(() => setPhase((p) => p + 1), 160);
|
|
601
|
+
onCleanup(() => clearInterval(interval));
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
<box gap={0}>
|
|
606
|
+
<props.api.ui.Prompt
|
|
607
|
+
sessionID={props.sessionID}
|
|
608
|
+
visible={props.visible}
|
|
609
|
+
disabled={props.disabled}
|
|
610
|
+
onSubmit={props.onSubmit}
|
|
611
|
+
ref={props.promptRef}
|
|
612
|
+
/>
|
|
613
|
+
<PromptQuotaHint api={props.api} bar={promptBar} running={running} phase={phase} />
|
|
614
|
+
</box>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function HomeBottomView(props: {
|
|
619
|
+
api: TuiPluginApi;
|
|
620
|
+
compactHomeBottomEnabled: boolean;
|
|
621
|
+
initialLoads?: TuiInitialLoadCoordinator;
|
|
622
|
+
}) {
|
|
623
|
+
const resource = acquireHomeBottomResource(
|
|
624
|
+
props.api,
|
|
625
|
+
props.compactHomeBottomEnabled,
|
|
626
|
+
props.initialLoads,
|
|
627
|
+
);
|
|
346
628
|
onCleanup(() => resource.release());
|
|
347
629
|
|
|
348
630
|
const announcement = () => getHomeBottomAnnouncementText(resource.bottom());
|
|
@@ -566,7 +848,7 @@ async function runQuotaDialogCommandAsync(
|
|
|
566
848
|
}
|
|
567
849
|
}
|
|
568
850
|
|
|
569
|
-
function registerQuotaDialogCommands(api: TuiPluginApi,
|
|
851
|
+
function registerQuotaDialogCommands(api: TuiPluginApi, gate: TuiRegistrationGate): void {
|
|
570
852
|
const commandState: QuotaDialogCommandState = {};
|
|
571
853
|
const dispose = api.keymap.registerLayer({
|
|
572
854
|
commands: QUOTA_DIALOG_COMMANDS.map((spec) => ({
|
|
@@ -577,7 +859,15 @@ function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiComma
|
|
|
577
859
|
category: "OpenCode Quota",
|
|
578
860
|
slashName: spec.slashName,
|
|
579
861
|
run(input?: unknown) {
|
|
580
|
-
|
|
862
|
+
const state = gate.current();
|
|
863
|
+
if (state.status !== "active") return;
|
|
864
|
+
void runQuotaDialogCommandAsync(
|
|
865
|
+
api,
|
|
866
|
+
spec.id,
|
|
867
|
+
state.registration.commandDisplay,
|
|
868
|
+
input,
|
|
869
|
+
commandState,
|
|
870
|
+
);
|
|
581
871
|
},
|
|
582
872
|
})),
|
|
583
873
|
bindings: [],
|
|
@@ -586,12 +876,75 @@ function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiComma
|
|
|
586
876
|
api.lifecycle.onDispose(dispose);
|
|
587
877
|
}
|
|
588
878
|
|
|
589
|
-
function
|
|
879
|
+
function registerStableTuiSlots(api: TuiPluginApi, current: () => TuiRegistrationState): void {
|
|
590
880
|
api.slots.register({
|
|
591
881
|
order: SIDEBAR_ORDER,
|
|
592
882
|
slots: {
|
|
593
883
|
sidebar_content(_ctx, props: { session_id: string }) {
|
|
594
|
-
|
|
884
|
+
const state = current();
|
|
885
|
+
if (state.status !== "active" || !state.registration.sidebar.enabled) return null;
|
|
886
|
+
return (
|
|
887
|
+
<SidebarContentView
|
|
888
|
+
api={api}
|
|
889
|
+
sessionID={props.session_id}
|
|
890
|
+
initialLoads={state.initialLoads}
|
|
891
|
+
/>
|
|
892
|
+
);
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
api.slots.register({
|
|
898
|
+
order: COMPACT_ORDER,
|
|
899
|
+
slots: {
|
|
900
|
+
session_prompt(
|
|
901
|
+
_ctx,
|
|
902
|
+
props: {
|
|
903
|
+
session_id: string;
|
|
904
|
+
visible?: boolean;
|
|
905
|
+
disabled?: boolean;
|
|
906
|
+
on_submit?: () => void;
|
|
907
|
+
ref?: TuiPromptRefCallback;
|
|
908
|
+
},
|
|
909
|
+
) {
|
|
910
|
+
const state = current();
|
|
911
|
+
if (state.status !== "active") return null;
|
|
912
|
+
if (state.registration.promptBar.enabled) {
|
|
913
|
+
return (
|
|
914
|
+
<SessionQuotaPromptBar
|
|
915
|
+
api={api}
|
|
916
|
+
sessionID={props.session_id}
|
|
917
|
+
initialLoads={state.initialLoads}
|
|
918
|
+
visible={props.visible}
|
|
919
|
+
disabled={props.disabled}
|
|
920
|
+
onSubmit={props.on_submit}
|
|
921
|
+
promptRef={props.ref}
|
|
922
|
+
/>
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
if (!state.registration.compact.sessionPrompt) return null;
|
|
926
|
+
return (
|
|
927
|
+
<SessionPromptWithCompactStatus
|
|
928
|
+
api={api}
|
|
929
|
+
sessionID={props.session_id}
|
|
930
|
+
initialLoads={state.initialLoads}
|
|
931
|
+
visible={props.visible}
|
|
932
|
+
disabled={props.disabled}
|
|
933
|
+
onSubmit={props.on_submit}
|
|
934
|
+
promptRef={props.ref}
|
|
935
|
+
/>
|
|
936
|
+
);
|
|
937
|
+
},
|
|
938
|
+
home_bottom() {
|
|
939
|
+
const state = current();
|
|
940
|
+
if (state.status !== "active" || !state.registration.homeBottom) return null;
|
|
941
|
+
return (
|
|
942
|
+
<HomeBottomView
|
|
943
|
+
api={api}
|
|
944
|
+
compactHomeBottomEnabled={state.registration.compact.homeBottom}
|
|
945
|
+
initialLoads={state.initialLoads}
|
|
946
|
+
/>
|
|
947
|
+
);
|
|
595
948
|
},
|
|
596
949
|
},
|
|
597
950
|
});
|
|
@@ -599,74 +952,43 @@ function registerSidebarSlots(api: TuiPluginApi): void {
|
|
|
599
952
|
|
|
600
953
|
async function initializeTuiRegistration(
|
|
601
954
|
api: TuiPluginApi,
|
|
602
|
-
|
|
955
|
+
gate: TuiRegistrationGate,
|
|
603
956
|
): Promise<void> {
|
|
604
|
-
let
|
|
957
|
+
let initialRuntimeSeed: TuiInitialRuntimeSeed | undefined;
|
|
958
|
+
let surfaceRegistration: Promise<{
|
|
959
|
+
registration: TuiSurfaceRegistration;
|
|
960
|
+
initialRuntimeSeed?: TuiInitialRuntimeSeed;
|
|
961
|
+
}>;
|
|
605
962
|
try {
|
|
606
|
-
surfaceRegistration =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
registerQuotaDialogCommands(api, "inline");
|
|
610
|
-
registerSidebarSlots(api);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (isDisposed()) return;
|
|
615
|
-
registerQuotaDialogCommands(api, surfaceRegistration.commandDisplay);
|
|
616
|
-
|
|
617
|
-
if (surfaceRegistration.sidebar.enabled) {
|
|
618
|
-
registerSidebarSlots(api);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const compactRegistration = surfaceRegistration.compact;
|
|
622
|
-
if (!compactRegistration.enabled && !surfaceRegistration.homeBottom) return;
|
|
623
|
-
|
|
624
|
-
const compactSlots: Record<string, (ctx: any, props: any) => JSX.Element | null> = {};
|
|
625
|
-
|
|
626
|
-
if (compactRegistration.sessionPrompt) {
|
|
627
|
-
compactSlots.session_prompt = (
|
|
628
|
-
_ctx,
|
|
629
|
-
props: {
|
|
630
|
-
session_id: string;
|
|
631
|
-
visible?: boolean;
|
|
632
|
-
disabled?: boolean;
|
|
633
|
-
on_submit?: () => void;
|
|
634
|
-
ref?: TuiPromptRefCallback;
|
|
963
|
+
surfaceRegistration = resolveTuiSurfaceRegistration(api, {
|
|
964
|
+
captureInitialRuntime(seed) {
|
|
965
|
+
initialRuntimeSeed = seed;
|
|
635
966
|
},
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
disabled={props.disabled}
|
|
642
|
-
onSubmit={props.on_submit}
|
|
643
|
-
promptRef={props.ref}
|
|
644
|
-
/>
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (surfaceRegistration.homeBottom) {
|
|
649
|
-
compactSlots.home_bottom = () => (
|
|
650
|
-
<HomeBottomView api={api} compactHomeBottomEnabled={surfaceRegistration.compact.homeBottom} />
|
|
651
|
-
);
|
|
967
|
+
})
|
|
968
|
+
.then((registration) => ({ registration, initialRuntimeSeed }))
|
|
969
|
+
.catch(() => ({ registration: FALLBACK_SURFACE_REGISTRATION }));
|
|
970
|
+
} catch {
|
|
971
|
+
surfaceRegistration = Promise.resolve({ registration: FALLBACK_SURFACE_REGISTRATION });
|
|
652
972
|
}
|
|
653
973
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
974
|
+
registerQuotaDialogCommands(api, gate);
|
|
975
|
+
void surfaceRegistration.then(({ registration, initialRuntimeSeed }) =>
|
|
976
|
+
gate.activate(
|
|
977
|
+
registration,
|
|
978
|
+
initialRuntimeSeed ? createTuiInitialLoadCoordinator(initialRuntimeSeed) : undefined,
|
|
979
|
+
),
|
|
980
|
+
);
|
|
981
|
+
registerStableTuiSlots(api, gate.current);
|
|
660
982
|
}
|
|
661
983
|
|
|
662
984
|
const tui: TuiPlugin = async (api) => {
|
|
663
|
-
|
|
985
|
+
const registrationGate = createTuiRegistrationGate();
|
|
664
986
|
api.lifecycle.onDispose(() => {
|
|
665
|
-
|
|
987
|
+
registrationGate.dispose();
|
|
666
988
|
disposeQuotaTelemetryOwner(createTuiQuotaClient(api));
|
|
667
989
|
});
|
|
668
990
|
|
|
669
|
-
void initializeTuiRegistration(api,
|
|
991
|
+
void initializeTuiRegistration(api, registrationGate).catch(() => {});
|
|
670
992
|
};
|
|
671
993
|
|
|
672
994
|
const pluginModule: TuiPluginModule & { id: string } = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slkiser/opencode-quota",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.1",
|
|
4
4
|
"description": "OpenCode quota & tokens usage with zero context window pollution. Supports GitHub Copilot, OpenAI (Plus/Pro), Qwen Code, Chutes AI, Synthetic, Google Antigravity, Z.ai coding plan and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|