@slkiser/opencode-quota 4.6.0 → 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 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +24 -0
- package/dist/lib/config.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-runtime.d.ts +6 -1
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +51 -1
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/types.d.ts +5 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +3 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +307 -110
- package/dist/tui.tsx +183 -1
- 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 {
|
|
@@ -65,6 +68,7 @@ type SessionQuotaResource = {
|
|
|
65
68
|
sessionID: string;
|
|
66
69
|
sidebar: () => SidebarPanelState;
|
|
67
70
|
compact: () => CompactStatusState;
|
|
71
|
+
promptBar: () => PromptBarState;
|
|
68
72
|
retain: () => SessionQuotaResource;
|
|
69
73
|
release: () => void;
|
|
70
74
|
};
|
|
@@ -108,6 +112,7 @@ const FALLBACK_SURFACE_REGISTRATION: TuiSurfaceRegistration = {
|
|
|
108
112
|
hasNativeProviderQuota: false,
|
|
109
113
|
suppressedByNativeProviderQuota: false,
|
|
110
114
|
},
|
|
115
|
+
promptBar: { enabled: false },
|
|
111
116
|
announcements: { homeBottom: false },
|
|
112
117
|
homeBottom: false,
|
|
113
118
|
};
|
|
@@ -168,6 +173,7 @@ function createSessionQuotaResource(
|
|
|
168
173
|
lines: [],
|
|
169
174
|
});
|
|
170
175
|
const [compact, setCompact] = createSignal<CompactStatusState>({ status: "loading" });
|
|
176
|
+
const [promptBar, setPromptBar] = createSignal<PromptBarState>({ status: "loading" });
|
|
171
177
|
|
|
172
178
|
let loadOrdinal = 0;
|
|
173
179
|
const lifecycle = createTuiRefreshLifecycle({
|
|
@@ -183,6 +189,7 @@ function createSessionQuotaResource(
|
|
|
183
189
|
apply: (next) => {
|
|
184
190
|
setSidebar(next.sidebar);
|
|
185
191
|
setCompact(next.compact);
|
|
192
|
+
setPromptBar(next.promptBar ?? { status: "loading" });
|
|
186
193
|
},
|
|
187
194
|
intervalMs: REFRESH_INTERVAL_MS,
|
|
188
195
|
eventRefreshDelaysMs: EVENT_REFRESH_DELAYS_MS,
|
|
@@ -220,6 +227,7 @@ function createSessionQuotaResource(
|
|
|
220
227
|
sessionID,
|
|
221
228
|
sidebar,
|
|
222
229
|
compact,
|
|
230
|
+
promptBar,
|
|
223
231
|
retain: () => {
|
|
224
232
|
lifecycle.retain();
|
|
225
233
|
return resource;
|
|
@@ -447,6 +455,166 @@ function SessionPromptWithCompactStatus(props: {
|
|
|
447
455
|
);
|
|
448
456
|
}
|
|
449
457
|
|
|
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
|
+
|
|
450
618
|
function HomeBottomView(props: {
|
|
451
619
|
api: TuiPluginApi;
|
|
452
620
|
compactHomeBottomEnabled: boolean;
|
|
@@ -740,7 +908,21 @@ function registerStableTuiSlots(api: TuiPluginApi, current: () => TuiRegistratio
|
|
|
740
908
|
},
|
|
741
909
|
) {
|
|
742
910
|
const state = current();
|
|
743
|
-
if (state.status !== "active"
|
|
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;
|
|
744
926
|
return (
|
|
745
927
|
<SessionPromptWithCompactStatus
|
|
746
928
|
api={api}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slkiser/opencode-quota",
|
|
3
|
-
"version": "4.6.
|
|
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",
|