@slkiser/opencode-quota 4.5.0 → 4.6.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 (43) hide show
  1. package/README.md +1 -0
  2. package/dist/data/modelsdev-pricing.min.json +5 -0
  3. package/dist/lib/config.d.ts +1 -1
  4. package/dist/lib/config.d.ts.map +1 -1
  5. package/dist/lib/config.js +51 -0
  6. package/dist/lib/config.js.map +1 -1
  7. package/dist/lib/maintainer-announcements.d.ts +3 -0
  8. package/dist/lib/maintainer-announcements.d.ts.map +1 -1
  9. package/dist/lib/maintainer-announcements.js +13 -0
  10. package/dist/lib/maintainer-announcements.js.map +1 -1
  11. package/dist/lib/quota-export.d.ts +1 -0
  12. package/dist/lib/quota-export.d.ts.map +1 -1
  13. package/dist/lib/quota-export.js +4 -0
  14. package/dist/lib/quota-export.js.map +1 -1
  15. package/dist/lib/quota-render-data.d.ts +2 -0
  16. package/dist/lib/quota-render-data.d.ts.map +1 -1
  17. package/dist/lib/quota-render-data.js +7 -0
  18. package/dist/lib/quota-render-data.js.map +1 -1
  19. package/dist/lib/quota-reset-notifications.d.ts +20 -0
  20. package/dist/lib/quota-reset-notifications.d.ts.map +1 -0
  21. package/dist/lib/quota-reset-notifications.js +328 -0
  22. package/dist/lib/quota-reset-notifications.js.map +1 -0
  23. package/dist/lib/quota-stats.d.ts.map +1 -1
  24. package/dist/lib/quota-stats.js +4 -0
  25. package/dist/lib/quota-stats.js.map +1 -1
  26. package/dist/lib/tui-refresh-lifecycle.d.ts.map +1 -1
  27. package/dist/lib/tui-refresh-lifecycle.js +0 -1
  28. package/dist/lib/tui-refresh-lifecycle.js.map +1 -1
  29. package/dist/lib/tui-runtime.d.ts +8 -1
  30. package/dist/lib/tui-runtime.d.ts.map +1 -1
  31. package/dist/lib/tui-runtime.js +36 -5
  32. package/dist/lib/tui-runtime.js.map +1 -1
  33. package/dist/lib/types.d.ts +9 -0
  34. package/dist/lib/types.d.ts.map +1 -1
  35. package/dist/lib/types.js +4 -0
  36. package/dist/lib/types.js.map +1 -1
  37. package/dist/plugin.d.ts.map +1 -1
  38. package/dist/plugin.js +38 -1
  39. package/dist/plugin.js.map +1 -1
  40. package/dist/tui.d.ts.map +1 -1
  41. package/dist/tui.js +171 -70
  42. package/dist/tui.tsx +220 -69
  43. package/package.json +1 -1
package/dist/tui.tsx CHANGED
@@ -40,6 +40,8 @@ import {
40
40
  loadTuiSessionQuotaSurfaces,
41
41
  normalizeTuiSessionID,
42
42
  resolveTuiSurfaceRegistration,
43
+ type TuiInitialRuntimeSeed,
44
+ type TuiSurfaceRegistration,
43
45
  writeTuiQuotaExportIfEnabled,
44
46
  } from "./lib/tui-runtime.js";
45
47
  import type { TuiCommandDisplay } from "./lib/types.js";
@@ -73,6 +75,77 @@ type HomeBottomResource = {
73
75
  release: () => void;
74
76
  };
75
77
 
78
+ type TuiInitialLoadCoordinator = {
79
+ takeInitialSession: () => TuiInitialRuntimeSeed | undefined;
80
+ takeInitialHome: () => TuiInitialRuntimeSeed | undefined;
81
+ };
82
+
83
+ type TuiRegistrationState =
84
+ | { status: "pending" }
85
+ | {
86
+ status: "active";
87
+ registration: TuiSurfaceRegistration;
88
+ initialLoads?: TuiInitialLoadCoordinator;
89
+ }
90
+ | { status: "disposed" };
91
+
92
+ type TuiRegistrationGate = {
93
+ current: () => TuiRegistrationState;
94
+ activate: (
95
+ registration: TuiSurfaceRegistration,
96
+ initialLoads?: TuiInitialLoadCoordinator,
97
+ ) => void;
98
+ dispose: () => void;
99
+ };
100
+
101
+ const FALLBACK_SURFACE_REGISTRATION: TuiSurfaceRegistration = {
102
+ commandDisplay: "inline",
103
+ sidebar: { enabled: true },
104
+ compact: {
105
+ enabled: false,
106
+ homeBottom: false,
107
+ sessionPrompt: false,
108
+ hasNativeProviderQuota: false,
109
+ suppressedByNativeProviderQuota: false,
110
+ },
111
+ announcements: { homeBottom: false },
112
+ homeBottom: false,
113
+ };
114
+
115
+ function createTuiInitialLoadCoordinator(seed: TuiInitialRuntimeSeed): TuiInitialLoadCoordinator {
116
+ let sessionAvailable = true;
117
+ let homeAvailable = true;
118
+
119
+ return {
120
+ takeInitialSession() {
121
+ if (!sessionAvailable) return undefined;
122
+ sessionAvailable = false;
123
+ return seed;
124
+ },
125
+ takeInitialHome() {
126
+ if (!homeAvailable) return undefined;
127
+ homeAvailable = false;
128
+ return seed;
129
+ },
130
+ };
131
+ }
132
+
133
+ function createTuiRegistrationGate(): TuiRegistrationGate {
134
+ const [current, setCurrent] = createSignal<TuiRegistrationState>({ status: "pending" });
135
+
136
+ return {
137
+ current,
138
+ activate(registration, initialLoads) {
139
+ if (current().status !== "pending") return;
140
+ setCurrent({ status: "active", registration, initialLoads });
141
+ },
142
+ dispose() {
143
+ if (current().status === "disposed") return;
144
+ setCurrent({ status: "disposed" });
145
+ },
146
+ };
147
+ }
148
+
76
149
  const sessionResources = new WeakMap<TuiPluginApi, Map<string, SessionQuotaResource>>();
77
150
  const homeResources = new WeakMap<TuiPluginApi, HomeBottomResource>();
78
151
 
@@ -85,15 +158,28 @@ function getSessionResourceMap(api: TuiPluginApi): Map<string, SessionQuotaResou
85
158
  return next;
86
159
  }
87
160
 
88
- function createSessionQuotaResource(api: TuiPluginApi, sessionID: string): SessionQuotaResource {
161
+ function createSessionQuotaResource(
162
+ api: TuiPluginApi,
163
+ sessionID: string,
164
+ initialLoads?: TuiInitialLoadCoordinator,
165
+ ): SessionQuotaResource {
89
166
  const [sidebar, setSidebar] = createSignal<SidebarPanelState>({
90
167
  status: "loading",
91
168
  lines: [],
92
169
  });
93
170
  const [compact, setCompact] = createSignal<CompactStatusState>({ status: "loading" });
94
171
 
172
+ let loadOrdinal = 0;
95
173
  const lifecycle = createTuiRefreshLifecycle({
96
- load: () => loadTuiSessionQuotaSurfaces({ api, sessionID }),
174
+ load: () => {
175
+ const initialRuntimeSeed = loadOrdinal === 0 ? initialLoads?.takeInitialSession() : undefined;
176
+ loadOrdinal += 1;
177
+ return loadTuiSessionQuotaSurfaces({
178
+ api,
179
+ sessionID,
180
+ ...(initialRuntimeSeed ? { initialRuntimeSeed } : {}),
181
+ });
182
+ },
97
183
  apply: (next) => {
98
184
  setSidebar(next.sidebar);
99
185
  setCompact(next.compact);
@@ -144,12 +230,16 @@ function createSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sessi
144
230
  return resource;
145
231
  }
146
232
 
147
- function acquireSessionQuotaResource(api: TuiPluginApi, sessionID: string): SessionQuotaResource {
233
+ function acquireSessionQuotaResource(
234
+ api: TuiPluginApi,
235
+ sessionID: string,
236
+ initialLoads?: TuiInitialLoadCoordinator,
237
+ ): SessionQuotaResource {
148
238
  const resources = getSessionResourceMap(api);
149
239
  const existing = resources.get(sessionID);
150
240
  if (existing) return existing.retain();
151
241
 
152
- const next = createSessionQuotaResource(api, sessionID).retain();
242
+ const next = createSessionQuotaResource(api, sessionID, initialLoads).retain();
153
243
  resources.set(sessionID, next);
154
244
  return next;
155
245
  }
@@ -157,14 +247,23 @@ function acquireSessionQuotaResource(api: TuiPluginApi, sessionID: string): Sess
157
247
  function createHomeBottomResource(
158
248
  api: TuiPluginApi,
159
249
  compactHomeBottomEnabled: boolean,
250
+ initialLoads?: TuiInitialLoadCoordinator,
160
251
  ): HomeBottomResource {
161
252
  const [bottom, setBottom] = createSignal<HomeBottomState>({
162
253
  status: "loading",
163
254
  compact: compactHomeBottomEnabled ? { status: "loading" } : { status: "disabled" },
164
255
  });
165
256
 
257
+ let loadOrdinal = 0;
166
258
  const lifecycle = createTuiRefreshLifecycle({
167
- load: () => loadTuiHomeBottomStatus({ api }),
259
+ load: () => {
260
+ const initialRuntimeSeed = loadOrdinal === 0 ? initialLoads?.takeInitialHome() : undefined;
261
+ loadOrdinal += 1;
262
+ return loadTuiHomeBottomStatus({
263
+ api,
264
+ ...(initialRuntimeSeed ? { initialRuntimeSeed } : {}),
265
+ });
266
+ },
168
267
  apply: setBottom,
169
268
  afterApply: () => {
170
269
  // Fire-and-forget: write export file if enabled. A failed write must
@@ -201,11 +300,12 @@ function createHomeBottomResource(
201
300
  function acquireHomeBottomResource(
202
301
  api: TuiPluginApi,
203
302
  compactHomeBottomEnabled: boolean,
303
+ initialLoads?: TuiInitialLoadCoordinator,
204
304
  ): HomeBottomResource {
205
305
  const existing = homeResources.get(api);
206
306
  if (existing) return existing.retain();
207
307
 
208
- const next = createHomeBottomResource(api, compactHomeBottomEnabled).retain();
308
+ const next = createHomeBottomResource(api, compactHomeBottomEnabled, initialLoads).retain();
209
309
  homeResources.set(api, next);
210
310
  return next;
211
311
  }
@@ -213,8 +313,9 @@ function acquireHomeBottomResource(
213
313
  function useSessionQuotaResource(
214
314
  api: TuiPluginApi,
215
315
  sessionID: () => string,
316
+ initialLoads?: TuiInitialLoadCoordinator,
216
317
  ): () => SessionQuotaResource {
217
- let current = acquireSessionQuotaResource(api, sessionID());
318
+ let current = acquireSessionQuotaResource(api, sessionID(), initialLoads);
218
319
  const [resource, setResource] = createSignal(current);
219
320
 
220
321
  createEffect(() => {
@@ -222,7 +323,7 @@ function useSessionQuotaResource(
222
323
  if (current.sessionID === nextSessionID) return;
223
324
 
224
325
  const previous = current;
225
- current = acquireSessionQuotaResource(api, nextSessionID);
326
+ current = acquireSessionQuotaResource(api, nextSessionID, initialLoads);
226
327
  setResource(current);
227
328
  previous.release();
228
329
  });
@@ -234,8 +335,12 @@ function useSessionQuotaResource(
234
335
  return resource;
235
336
  }
236
337
 
237
- function SidebarContentView(props: { api: TuiPluginApi; sessionID: string }) {
238
- const resource = useSessionQuotaResource(props.api, () => props.sessionID);
338
+ function SidebarContentView(props: {
339
+ api: TuiPluginApi;
340
+ sessionID: string;
341
+ initialLoads?: TuiInitialLoadCoordinator;
342
+ }) {
343
+ const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
239
344
  const panel = () => resource().sidebar();
240
345
 
241
346
  const lines = () => getSidebarPanelLines(panel());
@@ -319,12 +424,13 @@ function CompactStatusLine(props: {
319
424
  function SessionPromptWithCompactStatus(props: {
320
425
  api: TuiPluginApi;
321
426
  sessionID: string;
427
+ initialLoads?: TuiInitialLoadCoordinator;
322
428
  visible?: boolean;
323
429
  disabled?: boolean;
324
430
  onSubmit?: () => void;
325
431
  promptRef?: TuiPromptRefCallback;
326
432
  }) {
327
- const resource = useSessionQuotaResource(props.api, () => props.sessionID);
433
+ const resource = useSessionQuotaResource(props.api, () => props.sessionID, props.initialLoads);
328
434
  const panel = () => resource().compact();
329
435
 
330
436
  return (
@@ -341,8 +447,16 @@ function SessionPromptWithCompactStatus(props: {
341
447
  );
342
448
  }
343
449
 
344
- function HomeBottomView(props: { api: TuiPluginApi; compactHomeBottomEnabled: boolean }) {
345
- const resource = acquireHomeBottomResource(props.api, props.compactHomeBottomEnabled);
450
+ function HomeBottomView(props: {
451
+ api: TuiPluginApi;
452
+ compactHomeBottomEnabled: boolean;
453
+ initialLoads?: TuiInitialLoadCoordinator;
454
+ }) {
455
+ const resource = acquireHomeBottomResource(
456
+ props.api,
457
+ props.compactHomeBottomEnabled,
458
+ props.initialLoads,
459
+ );
346
460
  onCleanup(() => resource.release());
347
461
 
348
462
  const announcement = () => getHomeBottomAnnouncementText(resource.bottom());
@@ -566,7 +680,7 @@ async function runQuotaDialogCommandAsync(
566
680
  }
567
681
  }
568
682
 
569
- function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiCommandDisplay): void {
683
+ function registerQuotaDialogCommands(api: TuiPluginApi, gate: TuiRegistrationGate): void {
570
684
  const commandState: QuotaDialogCommandState = {};
571
685
  const dispose = api.keymap.registerLayer({
572
686
  commands: QUOTA_DIALOG_COMMANDS.map((spec) => ({
@@ -577,7 +691,15 @@ function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiComma
577
691
  category: "OpenCode Quota",
578
692
  slashName: spec.slashName,
579
693
  run(input?: unknown) {
580
- void runQuotaDialogCommandAsync(api, spec.id, commandDisplay, input, commandState);
694
+ const state = gate.current();
695
+ if (state.status !== "active") return;
696
+ void runQuotaDialogCommandAsync(
697
+ api,
698
+ spec.id,
699
+ state.registration.commandDisplay,
700
+ input,
701
+ commandState,
702
+ );
581
703
  },
582
704
  })),
583
705
  bindings: [],
@@ -586,76 +708,105 @@ function registerQuotaDialogCommands(api: TuiPluginApi, commandDisplay: TuiComma
586
708
  api.lifecycle.onDispose(dispose);
587
709
  }
588
710
 
589
- function registerSidebarSlots(api: TuiPluginApi): void {
711
+ function registerStableTuiSlots(api: TuiPluginApi, current: () => TuiRegistrationState): void {
590
712
  api.slots.register({
591
713
  order: SIDEBAR_ORDER,
592
714
  slots: {
593
715
  sidebar_content(_ctx, props: { session_id: string }) {
594
- return <SidebarContentView api={api} sessionID={props.session_id} />;
716
+ const state = current();
717
+ if (state.status !== "active" || !state.registration.sidebar.enabled) return null;
718
+ return (
719
+ <SidebarContentView
720
+ api={api}
721
+ sessionID={props.session_id}
722
+ initialLoads={state.initialLoads}
723
+ />
724
+ );
595
725
  },
596
726
  },
597
727
  });
598
- }
599
728
 
600
- const tui: TuiPlugin = async (api) => {
601
- api.lifecycle.onDispose(() => {
602
- disposeQuotaTelemetryOwner(createTuiQuotaClient(api));
729
+ api.slots.register({
730
+ order: COMPACT_ORDER,
731
+ slots: {
732
+ session_prompt(
733
+ _ctx,
734
+ props: {
735
+ session_id: string;
736
+ visible?: boolean;
737
+ disabled?: boolean;
738
+ on_submit?: () => void;
739
+ ref?: TuiPromptRefCallback;
740
+ },
741
+ ) {
742
+ const state = current();
743
+ if (state.status !== "active" || !state.registration.compact.sessionPrompt) return null;
744
+ return (
745
+ <SessionPromptWithCompactStatus
746
+ api={api}
747
+ sessionID={props.session_id}
748
+ initialLoads={state.initialLoads}
749
+ visible={props.visible}
750
+ disabled={props.disabled}
751
+ onSubmit={props.on_submit}
752
+ promptRef={props.ref}
753
+ />
754
+ );
755
+ },
756
+ home_bottom() {
757
+ const state = current();
758
+ if (state.status !== "active" || !state.registration.homeBottom) return null;
759
+ return (
760
+ <HomeBottomView
761
+ api={api}
762
+ compactHomeBottomEnabled={state.registration.compact.homeBottom}
763
+ initialLoads={state.initialLoads}
764
+ />
765
+ );
766
+ },
767
+ },
603
768
  });
769
+ }
604
770
 
605
- let surfaceRegistration;
771
+ async function initializeTuiRegistration(
772
+ api: TuiPluginApi,
773
+ gate: TuiRegistrationGate,
774
+ ): Promise<void> {
775
+ let initialRuntimeSeed: TuiInitialRuntimeSeed | undefined;
776
+ let surfaceRegistration: Promise<{
777
+ registration: TuiSurfaceRegistration;
778
+ initialRuntimeSeed?: TuiInitialRuntimeSeed;
779
+ }>;
606
780
  try {
607
- surfaceRegistration = await resolveTuiSurfaceRegistration(api);
781
+ surfaceRegistration = resolveTuiSurfaceRegistration(api, {
782
+ captureInitialRuntime(seed) {
783
+ initialRuntimeSeed = seed;
784
+ },
785
+ })
786
+ .then((registration) => ({ registration, initialRuntimeSeed }))
787
+ .catch(() => ({ registration: FALLBACK_SURFACE_REGISTRATION }));
608
788
  } catch {
609
- registerQuotaDialogCommands(api, "inline");
610
- registerSidebarSlots(api);
611
- return;
612
- }
613
-
614
- registerQuotaDialogCommands(api, surfaceRegistration.commandDisplay);
615
-
616
- if (surfaceRegistration.sidebar.enabled) {
617
- registerSidebarSlots(api);
789
+ surfaceRegistration = Promise.resolve({ registration: FALLBACK_SURFACE_REGISTRATION });
618
790
  }
619
791
 
620
- const compactRegistration = surfaceRegistration.compact;
621
- if (!compactRegistration.enabled && !surfaceRegistration.homeBottom) return;
622
-
623
- const compactSlots: Record<string, (ctx: any, props: any) => JSX.Element | null> = {};
624
-
625
- if (compactRegistration.sessionPrompt) {
626
- compactSlots.session_prompt = (
627
- _ctx,
628
- props: {
629
- session_id: string;
630
- visible?: boolean;
631
- disabled?: boolean;
632
- on_submit?: () => void;
633
- ref?: TuiPromptRefCallback;
634
- },
635
- ) => (
636
- <SessionPromptWithCompactStatus
637
- api={api}
638
- sessionID={props.session_id}
639
- visible={props.visible}
640
- disabled={props.disabled}
641
- onSubmit={props.on_submit}
642
- promptRef={props.ref}
643
- />
644
- );
645
- }
792
+ registerQuotaDialogCommands(api, gate);
793
+ void surfaceRegistration.then(({ registration, initialRuntimeSeed }) =>
794
+ gate.activate(
795
+ registration,
796
+ initialRuntimeSeed ? createTuiInitialLoadCoordinator(initialRuntimeSeed) : undefined,
797
+ ),
798
+ );
799
+ registerStableTuiSlots(api, gate.current);
800
+ }
646
801
 
647
- if (surfaceRegistration.homeBottom) {
648
- compactSlots.home_bottom = () => (
649
- <HomeBottomView api={api} compactHomeBottomEnabled={surfaceRegistration.compact.homeBottom} />
650
- );
651
- }
802
+ const tui: TuiPlugin = async (api) => {
803
+ const registrationGate = createTuiRegistrationGate();
804
+ api.lifecycle.onDispose(() => {
805
+ registrationGate.dispose();
806
+ disposeQuotaTelemetryOwner(createTuiQuotaClient(api));
807
+ });
652
808
 
653
- if (Object.keys(compactSlots).length > 0) {
654
- api.slots.register({
655
- order: COMPACT_ORDER,
656
- slots: compactSlots,
657
- });
658
- }
809
+ void initializeTuiRegistration(api, registrationGate).catch(() => {});
659
810
  };
660
811
 
661
812
  const pluginModule: TuiPluginModule & { id: string } = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slkiser/opencode-quota",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
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",