@slkiser/opencode-quota 3.11.0 → 3.11.2

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 (38) hide show
  1. package/README.md +61 -43
  2. package/dist/bin/opencode-quota.d.ts.map +1 -1
  3. package/dist/bin/opencode-quota.js +8 -0
  4. package/dist/bin/opencode-quota.js.map +1 -1
  5. package/dist/lib/anthropic.d.ts.map +1 -1
  6. package/dist/lib/anthropic.js +106 -3
  7. package/dist/lib/anthropic.js.map +1 -1
  8. package/dist/lib/atomic-json.d.ts +1 -0
  9. package/dist/lib/atomic-json.d.ts.map +1 -1
  10. package/dist/lib/atomic-json.js +5 -2
  11. package/dist/lib/atomic-json.js.map +1 -1
  12. package/dist/lib/init-installer.d.ts.map +1 -1
  13. package/dist/lib/init-installer.js +40 -18
  14. package/dist/lib/init-installer.js.map +1 -1
  15. package/dist/lib/openai.d.ts +9 -12
  16. package/dist/lib/openai.d.ts.map +1 -1
  17. package/dist/lib/openai.js +69 -40
  18. package/dist/lib/openai.js.map +1 -1
  19. package/dist/lib/quota-state.js +1 -1
  20. package/dist/lib/quota-state.js.map +1 -1
  21. package/dist/lib/scoped-update.d.ts +63 -0
  22. package/dist/lib/scoped-update.d.ts.map +1 -0
  23. package/dist/lib/scoped-update.js +301 -0
  24. package/dist/lib/scoped-update.js.map +1 -0
  25. package/dist/lib/tui-runtime.d.ts.map +1 -1
  26. package/dist/lib/tui-runtime.js +7 -5
  27. package/dist/lib/tui-runtime.js.map +1 -1
  28. package/dist/providers/minimax-coding-plan.d.ts +1 -1
  29. package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
  30. package/dist/providers/minimax-coding-plan.js +7 -4
  31. package/dist/providers/minimax-coding-plan.js.map +1 -1
  32. package/dist/providers/openai.d.ts.map +1 -1
  33. package/dist/providers/openai.js +1 -0
  34. package/dist/providers/openai.js.map +1 -1
  35. package/dist/tui.d.ts.map +1 -1
  36. package/dist/tui.js +504 -0
  37. package/dist/tui.tsx +5 -246
  38. package/package.json +8 -4
package/dist/tui.js ADDED
@@ -0,0 +1,504 @@
1
+ import { createComponent as _$createComponent } from "@opentui/solid";
2
+ import { effect as _$effect } from "@opentui/solid";
3
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
4
+ import { insertNode as _$insertNode } from "@opentui/solid";
5
+ import { insert as _$insert } from "@opentui/solid";
6
+ import { memo as _$memo } from "@opentui/solid";
7
+ import { setProp as _$setProp } from "@opentui/solid";
8
+ import { createElement as _$createElement } from "@opentui/solid";
9
+ /** @jsxImportSource @opentui/solid */
10
+
11
+ import { Show, createEffect, createSignal, onCleanup } from "solid-js";
12
+ import { getCompactStatusText, getHomeBottomAnnouncementText, getSidebarPanelLines, getSidebarPanelLinesExpanded, shouldRenderCompactStatus, shouldRenderHomeBottom, shouldRenderSidebarPanel } from "./lib/tui-panel-state.js";
13
+ import { getSidebarBodyLineColor } from "./lib/tui-line-style.js";
14
+ import { loadTuiHomeBottomStatus, loadTuiSessionQuotaSurfaces, resolveTuiSurfaceRegistration, writeTuiQuotaExportIfEnabled } from "./lib/tui-runtime.js";
15
+ const id = "@slkiser/opencode-quota";
16
+ // Place Quota near the top so variable-height built-in sections
17
+ // (MCP/LSP/Todo/Files) do not push it below the visible fold.
18
+ const SIDEBAR_ORDER = 150;
19
+ const COMPACT_ORDER = 90;
20
+ const REFRESH_INTERVAL_MS = 60_000;
21
+ const EVENT_REFRESH_DELAYS_MS = [150, 600];
22
+ const MOUNT_RECOVERY_DELAYS_MS = [500, 1_500, 4_000];
23
+ const sessionResources = new WeakMap();
24
+ const homeResources = new WeakMap();
25
+ function getSessionResourceMap(api) {
26
+ const existing = sessionResources.get(api);
27
+ if (existing) return existing;
28
+ const next = new Map();
29
+ sessionResources.set(api, next);
30
+ return next;
31
+ }
32
+ function createSessionQuotaResource(api, sessionID) {
33
+ const [sidebar, setSidebar] = createSignal({
34
+ status: "loading",
35
+ lines: []
36
+ });
37
+ const [compact, setCompact] = createSignal({
38
+ status: "loading"
39
+ });
40
+ let refCount = 0;
41
+ let disposed = false;
42
+ let loadVersion = 0;
43
+ let inFlight = false;
44
+ let queued = false;
45
+ const timers = new Set();
46
+ const reload = () => {
47
+ if (disposed) return;
48
+ if (inFlight) {
49
+ queued = true;
50
+ loadVersion += 1;
51
+ return;
52
+ }
53
+ inFlight = true;
54
+ const currentVersion = ++loadVersion;
55
+ void loadTuiSessionQuotaSurfaces({
56
+ api,
57
+ sessionID
58
+ }).then(next => {
59
+ if (disposed || currentVersion !== loadVersion) return;
60
+ setSidebar(next.sidebar);
61
+ setCompact(next.compact);
62
+ }).catch(() => {
63
+ if (disposed || currentVersion !== loadVersion) return;
64
+ }).finally(() => {
65
+ if (disposed) return;
66
+ inFlight = false;
67
+ if (queued) {
68
+ queued = false;
69
+ reload();
70
+ }
71
+ });
72
+ };
73
+ const queueRefresh = delay => {
74
+ if (disposed) return;
75
+ const timer = setTimeout(() => {
76
+ timers.delete(timer);
77
+ reload();
78
+ }, delay);
79
+ timers.add(timer);
80
+ };
81
+ const scheduleRefresh = () => {
82
+ for (const delay of EVENT_REFRESH_DELAYS_MS) queueRefresh(delay);
83
+ };
84
+
85
+ // TUI/session state can hydrate asynchronously after mount or session switch,
86
+ // so retry a few times to recover from empty first-load reads.
87
+ const scheduleMountRecovery = () => {
88
+ for (const delay of MOUNT_RECOVERY_DELAYS_MS) queueRefresh(delay);
89
+ };
90
+ const interval = setInterval(reload, REFRESH_INTERVAL_MS);
91
+ const unsubscribers = [api.event.on("session.updated", event => {
92
+ if (event.properties?.info?.id === sessionID) {
93
+ scheduleRefresh();
94
+ }
95
+ }), api.event.on("message.updated", event => {
96
+ if (event.properties?.info?.sessionID === sessionID) {
97
+ scheduleRefresh();
98
+ }
99
+ }), api.event.on("message.removed", event => {
100
+ if (event.properties?.sessionID === sessionID) {
101
+ scheduleRefresh();
102
+ }
103
+ }), api.event.on("tui.session.select", event => {
104
+ if (event.properties?.sessionID === sessionID) {
105
+ scheduleRefresh();
106
+ }
107
+ })];
108
+ const dispose = () => {
109
+ if (disposed) return;
110
+ disposed = true;
111
+ clearInterval(interval);
112
+ for (const timer of timers) clearTimeout(timer);
113
+ timers.clear();
114
+ for (const unsubscribe of unsubscribers) unsubscribe();
115
+ getSessionResourceMap(api).delete(sessionID);
116
+ };
117
+ const resource = {
118
+ sessionID,
119
+ sidebar,
120
+ compact,
121
+ retain: () => {
122
+ refCount += 1;
123
+ return resource;
124
+ },
125
+ release: () => {
126
+ refCount -= 1;
127
+ if (refCount <= 0) dispose();
128
+ }
129
+ };
130
+ reload();
131
+ scheduleMountRecovery();
132
+ return resource;
133
+ }
134
+ function acquireSessionQuotaResource(api, sessionID) {
135
+ const resources = getSessionResourceMap(api);
136
+ const existing = resources.get(sessionID);
137
+ if (existing) return existing.retain();
138
+ const next = createSessionQuotaResource(api, sessionID).retain();
139
+ resources.set(sessionID, next);
140
+ return next;
141
+ }
142
+ function createHomeBottomResource(api) {
143
+ const [bottom, setBottom] = createSignal({
144
+ status: "loading",
145
+ compact: {
146
+ status: "loading"
147
+ }
148
+ });
149
+ let refCount = 0;
150
+ let disposed = false;
151
+ let loadVersion = 0;
152
+ let inFlight = false;
153
+ let queued = false;
154
+ const timers = new Set();
155
+ const reload = () => {
156
+ if (disposed) return;
157
+ if (inFlight) {
158
+ queued = true;
159
+ loadVersion += 1;
160
+ return;
161
+ }
162
+ inFlight = true;
163
+ const currentVersion = ++loadVersion;
164
+ void loadTuiHomeBottomStatus({
165
+ api
166
+ }).then(next => {
167
+ if (disposed || currentVersion !== loadVersion) return;
168
+ setBottom(next);
169
+ // Fire-and-forget: write export file if enabled. A failed write must
170
+ // never affect TUI rendering, so log a warning and continue.
171
+ void writeTuiQuotaExportIfEnabled({
172
+ api
173
+ }).catch(err => {
174
+ console.warn(`[opencode-quota] quota export write failed: ${String(err)}`);
175
+ });
176
+ }).catch(() => {
177
+ if (disposed || currentVersion !== loadVersion) return;
178
+ }).finally(() => {
179
+ if (disposed) return;
180
+ inFlight = false;
181
+ if (queued) {
182
+ queued = false;
183
+ reload();
184
+ }
185
+ });
186
+ };
187
+ const queueRefresh = delay => {
188
+ if (disposed) return;
189
+ const timer = setTimeout(() => {
190
+ timers.delete(timer);
191
+ reload();
192
+ }, delay);
193
+ timers.add(timer);
194
+ };
195
+ const scheduleRefresh = () => {
196
+ for (const delay of EVENT_REFRESH_DELAYS_MS) queueRefresh(delay);
197
+ };
198
+ const interval = setInterval(reload, REFRESH_INTERVAL_MS);
199
+ const unsubscribers = [api.event.on("session.updated", scheduleRefresh), api.event.on("message.updated", scheduleRefresh), api.event.on("message.removed", scheduleRefresh), api.event.on("tui.session.select", scheduleRefresh)];
200
+ const dispose = () => {
201
+ if (disposed) return;
202
+ disposed = true;
203
+ clearInterval(interval);
204
+ for (const timer of timers) clearTimeout(timer);
205
+ timers.clear();
206
+ for (const unsubscribe of unsubscribers) unsubscribe();
207
+ homeResources.delete(api);
208
+ };
209
+ const resource = {
210
+ bottom,
211
+ retain: () => {
212
+ refCount += 1;
213
+ return resource;
214
+ },
215
+ release: () => {
216
+ refCount -= 1;
217
+ if (refCount <= 0) dispose();
218
+ }
219
+ };
220
+ reload();
221
+ return resource;
222
+ }
223
+ function acquireHomeBottomResource(api) {
224
+ const existing = homeResources.get(api);
225
+ if (existing) return existing.retain();
226
+ const next = createHomeBottomResource(api).retain();
227
+ homeResources.set(api, next);
228
+ return next;
229
+ }
230
+ function useSessionQuotaResource(api, sessionID) {
231
+ let current = acquireSessionQuotaResource(api, sessionID());
232
+ const [resource, setResource] = createSignal(current);
233
+ createEffect(() => {
234
+ const nextSessionID = sessionID();
235
+ if (current.sessionID === nextSessionID) return;
236
+ const previous = current;
237
+ current = acquireSessionQuotaResource(api, nextSessionID);
238
+ setResource(current);
239
+ previous.release();
240
+ });
241
+ onCleanup(() => {
242
+ current.release();
243
+ });
244
+ return resource;
245
+ }
246
+ function SidebarContentView(props) {
247
+ const resource = useSessionQuotaResource(props.api, () => props.sessionID);
248
+ const panel = () => resource().sidebar();
249
+ const lines = () => getSidebarPanelLines(panel());
250
+ const hasDetailLines = () => Boolean(panel().linesExpanded?.length);
251
+ const [collapsed, setCollapsed] = createSignal(props.api.kv?.get("quota-sidebar-collapsed", true) ?? true);
252
+ const toggleCollapsed = () => {
253
+ if (!hasDetailLines()) return;
254
+ const next = !collapsed();
255
+ setCollapsed(next);
256
+ props.api.kv?.set("quota-sidebar-collapsed", next);
257
+ };
258
+ const displayLines = () => {
259
+ if (!hasDetailLines()) return lines();
260
+ return collapsed() ? lines() : getSidebarPanelLinesExpanded(panel());
261
+ };
262
+ const toggleIcon = () => collapsed() ? "▶" : "▼";
263
+ const providerCount = () => panel().providerCount ?? 0;
264
+ return _$createComponent(Show, {
265
+ get when() {
266
+ return shouldRenderSidebarPanel(panel());
267
+ },
268
+ get children() {
269
+ var _el$ = _$createElement("box"),
270
+ _el$2 = _$createElement("box"),
271
+ _el$3 = _$createElement("text"),
272
+ _el$4 = _$createElement("b"),
273
+ _el$8 = _$createElement("box");
274
+ _$insertNode(_el$, _el$2);
275
+ _$insertNode(_el$, _el$8);
276
+ _$setProp(_el$, "gap", 0);
277
+ _$insertNode(_el$2, _el$3);
278
+ _$setProp(_el$2, "flexDirection", "row");
279
+ _$insertNode(_el$3, _el$4);
280
+ _$setProp(_el$3, "onMouseDown", toggleCollapsed);
281
+ _$insert(_el$4, (() => {
282
+ var _c$ = _$memo(() => !!hasDetailLines());
283
+ return () => _c$() ? `${toggleIcon()} Quota` : "Quota";
284
+ })());
285
+ _$insert(_el$2, _$createComponent(Show, {
286
+ get when() {
287
+ return _$memo(() => !!collapsed())() && providerCount() > 0;
288
+ },
289
+ get children() {
290
+ var _el$5 = _$createElement("text"),
291
+ _el$6 = _$createTextNode(` (`),
292
+ _el$7 = _$createTextNode(` providers)`);
293
+ _$insertNode(_el$5, _el$6);
294
+ _$insertNode(_el$5, _el$7);
295
+ _$insert(_el$5, providerCount, _el$7);
296
+ _$effect(_$p => _$setProp(_el$5, "fg", props.api.theme.current.textMuted, _$p));
297
+ return _el$5;
298
+ }
299
+ }), null);
300
+ _$setProp(_el$8, "gap", 0);
301
+ _$insert(_el$8, () => displayLines().map(line => (() => {
302
+ var _el$9 = _$createElement("text");
303
+ _$setProp(_el$9, "wrapMode", "none");
304
+ _$insert(_el$9, line || " ");
305
+ _$effect(_$p => _$setProp(_el$9, "fg", getSidebarBodyLineColor(line, props.api.theme.current), _$p));
306
+ return _el$9;
307
+ })()));
308
+ _$effect(_$p => _$setProp(_el$3, "fg", props.api.theme.current.text, _$p));
309
+ return _el$;
310
+ }
311
+ });
312
+ }
313
+ function CompactStatusLine(props) {
314
+ const text = () => {
315
+ const panel = props.panel();
316
+ if (!shouldRenderCompactStatus(panel)) return "";
317
+ return getCompactStatusText(panel);
318
+ };
319
+ const line = () => (() => {
320
+ var _el$0 = _$createElement("box"),
321
+ _el$1 = _$createElement("text");
322
+ _$insertNode(_el$0, _el$1);
323
+ _$setProp(_el$0, "flexDirection", "row");
324
+ _$setProp(_el$1, "wrapMode", "none");
325
+ _$insert(_el$1, text);
326
+ _$effect(_p$ => {
327
+ var _v$ = props.justifyContent,
328
+ _v$2 = props.api.theme.current.textMuted;
329
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$0, "justifyContent", _v$, _p$.e));
330
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$1, "fg", _v$2, _p$.t));
331
+ return _p$;
332
+ }, {
333
+ e: undefined,
334
+ t: undefined
335
+ });
336
+ return _el$0;
337
+ })();
338
+ return _$createComponent(Show, {
339
+ get when() {
340
+ return text();
341
+ },
342
+ get children() {
343
+ return _$createComponent(Show, {
344
+ get when() {
345
+ return props.blankLineBefore;
346
+ },
347
+ get fallback() {
348
+ return line();
349
+ },
350
+ get children() {
351
+ var _el$10 = _$createElement("box"),
352
+ _el$11 = _$createElement("text");
353
+ _$insertNode(_el$10, _el$11);
354
+ _$setProp(_el$10, "gap", 0);
355
+ _$insertNode(_el$11, _$createTextNode(` `));
356
+ _$insert(_el$10, line, null);
357
+ return _el$10;
358
+ }
359
+ });
360
+ }
361
+ });
362
+ }
363
+ function SessionPromptWithCompactStatus(props) {
364
+ const resource = useSessionQuotaResource(props.api, () => props.sessionID);
365
+ const panel = () => resource().compact();
366
+ return (() => {
367
+ var _el$13 = _$createElement("box");
368
+ _$setProp(_el$13, "gap", 0);
369
+ _$insert(_el$13, _$createComponent(props.api.ui.Prompt, {
370
+ get sessionID() {
371
+ return props.sessionID;
372
+ },
373
+ get visible() {
374
+ return props.visible;
375
+ },
376
+ get disabled() {
377
+ return props.disabled;
378
+ },
379
+ get onSubmit() {
380
+ return props.onSubmit;
381
+ },
382
+ ref(r$) {
383
+ var _ref$ = props.promptRef;
384
+ typeof _ref$ === "function" ? _ref$(r$) : props.promptRef = r$;
385
+ }
386
+ }), null);
387
+ _$insert(_el$13, _$createComponent(CompactStatusLine, {
388
+ get api() {
389
+ return props.api;
390
+ },
391
+ panel: panel,
392
+ justifyContent: "flex-end"
393
+ }), null);
394
+ return _el$13;
395
+ })();
396
+ }
397
+ function HomeBottomView(props) {
398
+ const resource = acquireHomeBottomResource(props.api);
399
+ onCleanup(() => resource.release());
400
+ const announcement = () => getHomeBottomAnnouncementText(resource.bottom());
401
+ const compact = () => resource.bottom().compact;
402
+ return _$createComponent(Show, {
403
+ get when() {
404
+ return shouldRenderHomeBottom(resource.bottom());
405
+ },
406
+ get children() {
407
+ var _el$14 = _$createElement("box"),
408
+ _el$15 = _$createElement("text");
409
+ _$insertNode(_el$14, _el$15);
410
+ _$setProp(_el$14, "gap", 0);
411
+ _$insertNode(_el$15, _$createTextNode(` `));
412
+ _$insert(_el$14, _$createComponent(Show, {
413
+ get when() {
414
+ return announcement();
415
+ },
416
+ get children() {
417
+ var _el$17 = _$createElement("box"),
418
+ _el$18 = _$createElement("text");
419
+ _$insertNode(_el$17, _el$18);
420
+ _$setProp(_el$17, "flexDirection", "row");
421
+ _$setProp(_el$17, "justifyContent", "center");
422
+ _$setProp(_el$18, "wrapMode", "none");
423
+ _$insert(_el$18, announcement);
424
+ _$effect(_$p => _$setProp(_el$18, "fg", props.api.theme.current.textMuted, _$p));
425
+ return _el$17;
426
+ }
427
+ }), null);
428
+ _$insert(_el$14, _$createComponent(CompactStatusLine, {
429
+ get api() {
430
+ return props.api;
431
+ },
432
+ panel: compact,
433
+ justifyContent: "center"
434
+ }), null);
435
+ return _el$14;
436
+ }
437
+ });
438
+ }
439
+ function registerSidebarSlots(api) {
440
+ api.slots.register({
441
+ order: SIDEBAR_ORDER,
442
+ slots: {
443
+ sidebar_content(_ctx, props) {
444
+ return _$createComponent(SidebarContentView, {
445
+ api: api,
446
+ get sessionID() {
447
+ return props.session_id;
448
+ }
449
+ });
450
+ }
451
+ }
452
+ });
453
+ }
454
+ const tui = async api => {
455
+ let surfaceRegistration;
456
+ try {
457
+ surfaceRegistration = await resolveTuiSurfaceRegistration(api);
458
+ } catch {
459
+ registerSidebarSlots(api);
460
+ return;
461
+ }
462
+ if (surfaceRegistration.sidebar.enabled) {
463
+ registerSidebarSlots(api);
464
+ }
465
+ const compactRegistration = surfaceRegistration.compact;
466
+ if (!compactRegistration.enabled && !surfaceRegistration.homeBottom) return;
467
+ const compactSlots = {};
468
+ if (compactRegistration.sessionPrompt) {
469
+ compactSlots.session_prompt = (_ctx, props) => _$createComponent(SessionPromptWithCompactStatus, {
470
+ api: api,
471
+ get sessionID() {
472
+ return props.session_id;
473
+ },
474
+ get visible() {
475
+ return props.visible;
476
+ },
477
+ get disabled() {
478
+ return props.disabled;
479
+ },
480
+ get onSubmit() {
481
+ return props.on_submit;
482
+ },
483
+ get promptRef() {
484
+ return props.ref;
485
+ }
486
+ });
487
+ }
488
+ if (surfaceRegistration.homeBottom) {
489
+ compactSlots.home_bottom = () => _$createComponent(HomeBottomView, {
490
+ api: api
491
+ });
492
+ }
493
+ if (Object.keys(compactSlots).length > 0) {
494
+ api.slots.register({
495
+ order: COMPACT_ORDER,
496
+ slots: compactSlots
497
+ });
498
+ }
499
+ };
500
+ const pluginModule = {
501
+ id,
502
+ tui
503
+ };
504
+ export default pluginModule;