@chenlongapps/opencode-token-usage 0.3.4 → 0.4.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/dist/tui.js CHANGED
@@ -1,50 +1,49 @@
1
+ import { memo as _$memo } from "@opentui/solid";
2
+ import { use as _$use } from "@opentui/solid";
3
+ import { insert as _$insert } from "@opentui/solid";
1
4
  import { createComponent as _$createComponent } from "@opentui/solid";
2
5
  import { effect as _$effect } from "@opentui/solid";
3
- import { insert as _$insert } from "@opentui/solid";
4
6
  import { createTextNode as _$createTextNode } from "@opentui/solid";
5
7
  import { insertNode as _$insertNode } from "@opentui/solid";
6
8
  import { setProp as _$setProp } from "@opentui/solid";
7
9
  import { createElement as _$createElement } from "@opentui/solid";
8
10
  import { Plugin } from "@opencode/plugin/tui";
11
+ import { useTerminalDimensions } from "@opentui/solid";
9
12
  import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";
10
13
  import { UsageController } from "./controller.js";
11
14
  import { PerformanceMonitor } from "./performance.js";
15
+ import { breakdownRows } from "./context-sources.js";
12
16
  import { createSource } from "./source.js";
13
- import { usageRows } from "./usage.js";
14
- function UsagePanel(props) {
15
- const [state, setState] = createSignal({
16
- status: "loading"
17
- });
18
- const controller = new UsageController(createSource(props.context.client), listener => props.context.data.listen(({
19
- details
20
- }) => listener(details)), setState, 80, 3_000, 100, props.performance);
21
- const unregister = props.register(controller);
22
- createEffect(() => controller.select(props.sessionID));
23
- onCleanup(() => {
24
- controller.dispose();
25
- unregister();
26
- });
27
- const rows = createMemo(() => usageRows(state().summary, state().context, state().performance));
17
+ import { bar, countLabel, formatCompact, formatEstimatedCost, formatTokens, requestRows, summaryRows, usageRows } from "./usage.js";
18
+ function UsageView(props) {
19
+ const rows = createMemo(() => usageRows(props.state().summary, props.state().context, props.state().performance));
28
20
  const performanceStart = createMemo(() => rows().findIndex(([label]) => label === "TPS" || label === "TTFT"));
29
21
  const status = () => ({
30
22
  loading: "Loading…",
31
23
  ready: "",
32
24
  stale: "Not updated · retrying…",
33
25
  unavailable: "Unavailable · retrying…"
34
- })[state().status];
26
+ })[props.state().status];
35
27
  return (() => {
36
- var _el$ = _$createElement("box"),
37
- _el$2 = _$createElement("text"),
38
- _el$3 = _$createElement("b");
39
- _$insertNode(_el$, _el$2);
28
+ var _el$ = _$createElement("box");
40
29
  _$setProp(_el$, "flexDirection", "column");
41
- _$setProp(_el$, "marginTop", 1);
42
30
  _$setProp(_el$, "flexShrink", 0);
43
- _$insertNode(_el$2, _el$3);
44
- _$insertNode(_el$3, _$createTextNode(`Token Usage`));
45
31
  _$insert(_el$, _$createComponent(Show, {
46
32
  get when() {
47
- return state().status !== "ready";
33
+ return !props.hideTitle;
34
+ },
35
+ get children() {
36
+ var _el$2 = _$createElement("text"),
37
+ _el$3 = _$createElement("b");
38
+ _$insertNode(_el$2, _el$3);
39
+ _$insertNode(_el$3, _$createTextNode(`Token Usage`));
40
+ _$effect(_$p => _$setProp(_el$2, "fg", props.context.theme.text.base, _$p));
41
+ return _el$2;
42
+ }
43
+ }), null);
44
+ _$insert(_el$, _$createComponent(Show, {
45
+ get when() {
46
+ return props.state().status !== "ready";
48
47
  },
49
48
  get children() {
50
49
  var _el$5 = _$createElement("text");
@@ -83,10 +82,750 @@ function UsagePanel(props) {
83
82
  return _el$6;
84
83
  })()
85
84
  }), null);
86
- _$effect(_$p => _$setProp(_el$2, "fg", props.context.theme.text.base, _$p));
85
+ _$effect(_$p => _$setProp(_el$, "marginTop", props.hideTitle ? 0 : 1, _$p));
87
86
  return _el$;
88
87
  })();
89
88
  }
89
+ function UsagePanel(props) {
90
+ const [state, setState] = createSignal({
91
+ status: "loading"
92
+ });
93
+ const controller = new UsageController(createSource(props.context.client), listener => props.context.data.listen(({
94
+ details
95
+ }) => listener(details)), setState, 80, 3_000, 100, props.performance);
96
+ const unregister = props.register(controller);
97
+ createEffect(() => controller.select(props.sessionID));
98
+ onCleanup(() => {
99
+ controller.dispose();
100
+ unregister();
101
+ });
102
+ return _$createComponent(UsageView, {
103
+ get context() {
104
+ return props.context;
105
+ },
106
+ state: state
107
+ });
108
+ }
109
+ function ChildUsageDialog(props) {
110
+ const dimensions = useTerminalDimensions();
111
+ let scroll;
112
+ props.context.keymap.layer(() => ({
113
+ mode: "modal",
114
+ commands: [{
115
+ bind: "up",
116
+ title: "Scroll usage up",
117
+ group: "Dialog",
118
+ run: () => scroll?.scrollBy(-1)
119
+ }, {
120
+ bind: "down",
121
+ title: "Scroll usage down",
122
+ group: "Dialog",
123
+ run: () => scroll?.scrollBy(1)
124
+ }, {
125
+ bind: "pageup",
126
+ title: "Previous usage page",
127
+ group: "Dialog",
128
+ run: () => scroll?.scrollBy(-10)
129
+ }, {
130
+ bind: "pagedown",
131
+ title: "Next usage page",
132
+ group: "Dialog",
133
+ run: () => scroll?.scrollBy(10)
134
+ }]
135
+ }));
136
+ return (() => {
137
+ var _el$9 = _$createElement("box"),
138
+ _el$0 = _$createElement("box"),
139
+ _el$1 = _$createElement("text"),
140
+ _el$10 = _$createElement("b"),
141
+ _el$12 = _$createElement("text"),
142
+ _el$14 = _$createElement("scrollbox"),
143
+ _el$15 = _$createElement("text");
144
+ _$insertNode(_el$9, _el$0);
145
+ _$insertNode(_el$9, _el$14);
146
+ _$insertNode(_el$9, _el$15);
147
+ _$setProp(_el$9, "paddingLeft", 2);
148
+ _$setProp(_el$9, "paddingRight", 2);
149
+ _$setProp(_el$9, "paddingBottom", 1);
150
+ _$setProp(_el$9, "gap", 1);
151
+ _$insertNode(_el$0, _el$1);
152
+ _$insertNode(_el$0, _el$12);
153
+ _$setProp(_el$0, "flexDirection", "row");
154
+ _$setProp(_el$0, "justifyContent", "space-between");
155
+ _$insertNode(_el$1, _el$10);
156
+ _$insertNode(_el$10, _$createTextNode(`Token Usage`));
157
+ _$insertNode(_el$12, _$createTextNode(`esc`));
158
+ _$setProp(_el$12, "onMouseUp", () => props.context.ui.dialog.clear());
159
+ _$use(value => {
160
+ scroll = value;
161
+ }, _el$14);
162
+ _$setProp(_el$14, "contentOptions", {
163
+ minHeight: 0
164
+ });
165
+ _$setProp(_el$14, "scrollbarOptions", {
166
+ visible: false
167
+ });
168
+ _$insert(_el$14, _$createComponent(UsageView, {
169
+ get context() {
170
+ return props.context;
171
+ },
172
+ get state() {
173
+ return props.state;
174
+ },
175
+ hideTitle: true
176
+ }));
177
+ _$insertNode(_el$15, _$createTextNode(`↑/↓ scroll · esc close`));
178
+ _$effect(_p$ => {
179
+ var _v$4 = props.context.theme.text.base,
180
+ _v$5 = props.context.theme.text.muted,
181
+ _v$6 = Math.max(3, dimensions().height - 8),
182
+ _v$7 = props.context.theme.text.muted;
183
+ _v$4 !== _p$.e && (_p$.e = _$setProp(_el$1, "fg", _v$4, _p$.e));
184
+ _v$5 !== _p$.t && (_p$.t = _$setProp(_el$12, "fg", _v$5, _p$.t));
185
+ _v$6 !== _p$.a && (_p$.a = _$setProp(_el$14, "maxHeight", _v$6, _p$.a));
186
+ _v$7 !== _p$.o && (_p$.o = _$setProp(_el$15, "fg", _v$7, _p$.o));
187
+ return _p$;
188
+ }, {
189
+ e: undefined,
190
+ t: undefined,
191
+ a: undefined,
192
+ o: undefined
193
+ });
194
+ return _el$9;
195
+ })();
196
+ }
197
+ function ChildUsageLauncher(props) {
198
+ const [state, setState] = createSignal({
199
+ status: "loading"
200
+ });
201
+ const controller = new UsageController(createSource(props.context.client), listener => props.context.data.listen(({
202
+ details
203
+ }) => listener(details)), setState, 80, 3_000, 100, props.performance);
204
+ const unregister = props.register(controller);
205
+ createEffect(() => controller.select(props.sessionID));
206
+ onCleanup(() => {
207
+ controller.dispose();
208
+ unregister();
209
+ });
210
+ const summary = createMemo(() => {
211
+ const current = state();
212
+ if (!current.summary) return `Token Usage · ${{
213
+ loading: "Loading…",
214
+ ready: "Loading…",
215
+ stale: "Unavailable",
216
+ unavailable: "Unavailable · retrying…"
217
+ }[current.status]}`;
218
+ const context = current.context ? `${formatTokens(current.context.used)} / ${formatTokens(current.context.limit)} (${current.context.percent.toFixed(1)}%)` : "—";
219
+ const cost = formatEstimatedCost(current.summary.cost, current.summary.costStatus) ?? "—";
220
+ const tps = current.performance?.tps !== undefined && Number.isFinite(current.performance.tps) ? `${current.performance.tpsEstimated ? "~" : ""}${Math.max(0, current.performance.tps).toFixed(1)} tok/s` : "—";
221
+ const stale = current.status === "stale" ? " · Not updated" : "";
222
+ return `Token Usage · Context ${context} · Total ${formatTokens(current.summary.total)} · Cost ${cost} · TPS ${tps}${stale}`;
223
+ });
224
+ return (() => {
225
+ var _el$17 = _$createElement("box"),
226
+ _el$18 = _$createElement("text");
227
+ _$insertNode(_el$17, _el$18);
228
+ _$setProp(_el$17, "flexDirection", "row");
229
+ _$setProp(_el$17, "justifyContent", "flex-end");
230
+ _$setProp(_el$17, "paddingRight", 2);
231
+ _$setProp(_el$17, "flexShrink", 0);
232
+ _$setProp(_el$18, "wrapMode", "none");
233
+ _$setProp(_el$18, "onMouseUp", event => {
234
+ if (event.button === 0) props.open(props.sessionID, state);
235
+ });
236
+ _$insert(_el$18, summary);
237
+ _$effect(_$p => _$setProp(_el$18, "fg", props.context.theme.text.muted, _$p));
238
+ return _el$17;
239
+ })();
240
+ }
241
+ function UsageDialog(props) {
242
+ const [state, setState] = createSignal({
243
+ status: "loading"
244
+ });
245
+ const [detailed, setDetailed] = createSignal(false);
246
+ const dimensions = useTerminalDimensions();
247
+ const controller = new UsageController(createSource(props.context.client), listener => props.context.data.listen(({
248
+ details
249
+ }) => listener(details)), setState, 80, 3_000, 100, props.performance, true);
250
+ const unregister = props.register(controller);
251
+ controller.select(props.sessionID);
252
+ onCleanup(() => {
253
+ controller.dispose();
254
+ unregister();
255
+ });
256
+ let scroll;
257
+ const toggleDetails = () => {
258
+ setDetailed(value => !value);
259
+ };
260
+ props.context.keymap.layer(() => ({
261
+ mode: "modal",
262
+ commands: [{
263
+ bind: "up",
264
+ title: "Scroll usage up",
265
+ group: "Dialog",
266
+ run: () => scroll?.scrollBy(-1)
267
+ }, {
268
+ bind: "down",
269
+ title: "Scroll usage down",
270
+ group: "Dialog",
271
+ run: () => scroll?.scrollBy(1)
272
+ }, {
273
+ bind: "pageup",
274
+ title: "Previous usage page",
275
+ group: "Dialog",
276
+ run: () => scroll?.scrollBy(-10)
277
+ }, {
278
+ bind: "pagedown",
279
+ title: "Next usage page",
280
+ group: "Dialog",
281
+ run: () => scroll?.scrollBy(10)
282
+ }, {
283
+ bind: "home",
284
+ title: "First usage row",
285
+ group: "Dialog",
286
+ run: () => scroll?.scrollTo(0)
287
+ }, {
288
+ bind: "end",
289
+ title: "Last usage row",
290
+ group: "Dialog",
291
+ run: () => scroll?.scrollTo(Infinity)
292
+ }, {
293
+ bind: "d",
294
+ title: "Toggle usage details",
295
+ group: "Dialog",
296
+ run: toggleDetails
297
+ }]
298
+ }));
299
+ const theme = () => props.context.theme.text;
300
+ const context = createMemo(() => state().details?.context);
301
+ const models = createMemo(() => state().details?.models ?? []);
302
+ const sources = createMemo(() => state().details?.sources);
303
+ // The host gives this dialog a fixed width; keep every row inside that content box.
304
+ const width = createMemo(() => Math.max(24, Math.min(88, dimensions().width - 2) - 4));
305
+ const windowBar = createMemo(() => Math.max(8, Math.min(48, width() - 16)));
306
+ const breakdownBar = createMemo(() => Math.max(6, Math.min(20, width() - 46)));
307
+ const pairColumns = createMemo(() => width() >= 44);
308
+ const compact = () => !detailed();
309
+ const number = value => compact() ? formatCompact(value) : formatTokens(value);
310
+ const status = () => ({
311
+ loading: "Loading…",
312
+ ready: "",
313
+ stale: "Not updated · retrying…",
314
+ unavailable: "Unavailable · retrying…"
315
+ })[state().status];
316
+ // The body never repeats the model name: one identity line carries the session and models.
317
+ const identity = createMemo(() => {
318
+ const title = state().details?.sessionTitle;
319
+ const active = models().length > 1 ? `${models().length} models` : state().model;
320
+ return [title, active].filter(Boolean).join(" · ");
321
+ });
322
+ const requestRowsList = rows => {
323
+ const half = Math.ceil(rows.length / 2);
324
+ const columns = [rows.slice(0, half), rows.slice(half)];
325
+ const cell = ([label, value]) => (() => {
326
+ var _el$19 = _$createElement("box"),
327
+ _el$20 = _$createElement("text"),
328
+ _el$21 = _$createElement("text");
329
+ _$insertNode(_el$19, _el$20);
330
+ _$insertNode(_el$19, _el$21);
331
+ _$setProp(_el$19, "flexDirection", "row");
332
+ _$setProp(_el$19, "justifyContent", "space-between");
333
+ _$setProp(_el$19, "gap", 2);
334
+ _$setProp(_el$20, "flexShrink", 0);
335
+ _$insert(_el$20, label);
336
+ _$insert(_el$21, value);
337
+ _$effect(_p$ => {
338
+ var _v$8 = theme().muted,
339
+ _v$9 = theme().base;
340
+ _v$8 !== _p$.e && (_p$.e = _$setProp(_el$20, "fg", _v$8, _p$.e));
341
+ _v$9 !== _p$.t && (_p$.t = _$setProp(_el$21, "fg", _v$9, _p$.t));
342
+ return _p$;
343
+ }, {
344
+ e: undefined,
345
+ t: undefined
346
+ });
347
+ return _el$19;
348
+ })();
349
+ return pairColumns() ? (() => {
350
+ var _el$22 = _$createElement("box");
351
+ _$setProp(_el$22, "flexDirection", "row");
352
+ _$setProp(_el$22, "gap", 2);
353
+ _$insert(_el$22, () => columns.map(column => (() => {
354
+ var _el$23 = _$createElement("box");
355
+ _$setProp(_el$23, "flexDirection", "column");
356
+ _$setProp(_el$23, "flexGrow", 1);
357
+ _$setProp(_el$23, "minWidth", 0);
358
+ _$insert(_el$23, () => column.map(cell));
359
+ return _el$23;
360
+ })()));
361
+ return _el$22;
362
+ })() : (() => {
363
+ var _el$24 = _$createElement("box");
364
+ _$setProp(_el$24, "flexDirection", "column");
365
+ _$insert(_el$24, () => rows.map(cell));
366
+ return _el$24;
367
+ })();
368
+ };
369
+ const barRow = (percent, size) => {
370
+ const cells = bar(percent, size);
371
+ return (() => {
372
+ var _el$25 = _$createElement("box"),
373
+ _el$26 = _$createElement("text"),
374
+ _el$27 = _$createElement("text");
375
+ _$insertNode(_el$25, _el$26);
376
+ _$insertNode(_el$25, _el$27);
377
+ _$setProp(_el$25, "flexDirection", "row");
378
+ _$setProp(_el$26, "wrapMode", "none");
379
+ _$insert(_el$26, () => "█".repeat(cells.filled));
380
+ _$setProp(_el$27, "wrapMode", "none");
381
+ _$insert(_el$27, () => "░".repeat(cells.empty));
382
+ _$effect(_p$ => {
383
+ var _v$0 = theme().base,
384
+ _v$1 = theme().muted;
385
+ _v$0 !== _p$.e && (_p$.e = _$setProp(_el$26, "fg", _v$0, _p$.e));
386
+ _v$1 !== _p$.t && (_p$.t = _$setProp(_el$27, "fg", _v$1, _p$.t));
387
+ return _p$;
388
+ }, {
389
+ e: undefined,
390
+ t: undefined
391
+ });
392
+ return _el$25;
393
+ })();
394
+ };
395
+ return (() => {
396
+ var _el$28 = _$createElement("box"),
397
+ _el$29 = _$createElement("box"),
398
+ _el$30 = _$createElement("text"),
399
+ _el$31 = _$createElement("b"),
400
+ _el$33 = _$createElement("text"),
401
+ _el$60 = _$createElement("box"),
402
+ _el$61 = _$createElement("text"),
403
+ _el$63 = _$createElement("text"),
404
+ _el$65 = _$createElement("text");
405
+ _$insertNode(_el$28, _el$29);
406
+ _$insertNode(_el$28, _el$60);
407
+ _$setProp(_el$28, "paddingLeft", 2);
408
+ _$setProp(_el$28, "paddingRight", 2);
409
+ _$setProp(_el$28, "paddingBottom", 1);
410
+ _$setProp(_el$28, "gap", 1);
411
+ _$insertNode(_el$29, _el$30);
412
+ _$insertNode(_el$29, _el$33);
413
+ _$setProp(_el$29, "flexDirection", "row");
414
+ _$setProp(_el$29, "justifyContent", "space-between");
415
+ _$setProp(_el$29, "gap", 2);
416
+ _$insertNode(_el$30, _el$31);
417
+ _$insertNode(_el$31, _$createTextNode(`Token Usage`));
418
+ _$insertNode(_el$33, _$createTextNode(`esc`));
419
+ _$setProp(_el$33, "onMouseUp", () => props.context.ui.dialog.clear());
420
+ _$insert(_el$28, _$createComponent(Show, {
421
+ get when() {
422
+ return identity();
423
+ },
424
+ get children() {
425
+ var _el$35 = _$createElement("text");
426
+ _$setProp(_el$35, "wrapMode", "word");
427
+ _$insert(_el$35, identity);
428
+ _$effect(_$p => _$setProp(_el$35, "fg", theme().muted, _$p));
429
+ return _el$35;
430
+ }
431
+ }), _el$60);
432
+ _$insert(_el$28, _$createComponent(Show, {
433
+ get when() {
434
+ return state().status !== "ready";
435
+ },
436
+ get children() {
437
+ var _el$36 = _$createElement("text");
438
+ _$insert(_el$36, status);
439
+ _$effect(_$p => _$setProp(_el$36, "fg", theme().muted, _$p));
440
+ return _el$36;
441
+ }
442
+ }), _el$60);
443
+ _$insert(_el$28, _$createComponent(Show, {
444
+ get when() {
445
+ return state().summary;
446
+ },
447
+ get children() {
448
+ var _el$37 = _$createElement("scrollbox"),
449
+ _el$38 = _$createElement("box"),
450
+ _el$39 = _$createElement("box"),
451
+ _el$40 = _$createElement("text"),
452
+ _el$41 = _$createElement("b"),
453
+ _el$43 = _$createElement("box"),
454
+ _el$44 = _$createElement("box"),
455
+ _el$45 = _$createElement("text"),
456
+ _el$46 = _$createElement("b"),
457
+ _el$48 = _$createElement("box"),
458
+ _el$49 = _$createElement("text"),
459
+ _el$50 = _$createElement("b"),
460
+ _el$52 = _$createElement("box"),
461
+ _el$53 = _$createElement("text"),
462
+ _el$54 = _$createElement("b"),
463
+ _el$56 = _$createElement("box"),
464
+ _el$57 = _$createElement("text"),
465
+ _el$58 = _$createElement("b");
466
+ _$insertNode(_el$37, _el$38);
467
+ _$use(value => {
468
+ scroll = value;
469
+ }, _el$37);
470
+ _$setProp(_el$37, "contentOptions", {
471
+ minHeight: 0
472
+ });
473
+ _$setProp(_el$37, "scrollbarOptions", {
474
+ visible: false
475
+ });
476
+ _$insertNode(_el$38, _el$39);
477
+ _$insertNode(_el$38, _el$43);
478
+ _$insertNode(_el$38, _el$48);
479
+ _$insertNode(_el$38, _el$52);
480
+ _$insertNode(_el$38, _el$56);
481
+ _$setProp(_el$38, "gap", 1);
482
+ _$insertNode(_el$39, _el$40);
483
+ _$insertNode(_el$40, _el$41);
484
+ _$insertNode(_el$41, _$createTextNode(`Context Window`));
485
+ _$insert(_el$39, _$createComponent(Show, {
486
+ get when() {
487
+ return context();
488
+ },
489
+ get fallback() {
490
+ return (() => {
491
+ var _el$67 = _$createElement("text");
492
+ _$insertNode(_el$67, _$createTextNode(`Context usage unavailable`));
493
+ _$effect(_$p => _$setProp(_el$67, "fg", theme().muted, _$p));
494
+ return _el$67;
495
+ })();
496
+ },
497
+ children: details => (() => {
498
+ var _el$69 = _$createElement("box"),
499
+ _el$70 = _$createElement("box"),
500
+ _el$71 = _$createElement("text"),
501
+ _el$73 = _$createElement("text"),
502
+ _el$74 = _$createTextNode(` / `),
503
+ _el$75 = _$createTextNode(` (`),
504
+ _el$76 = _$createTextNode(`%)`);
505
+ _$insertNode(_el$69, _el$70);
506
+ _$insertNode(_el$70, _el$71);
507
+ _$insertNode(_el$70, _el$73);
508
+ _$setProp(_el$70, "flexDirection", "row");
509
+ _$setProp(_el$70, "justifyContent", "space-between");
510
+ _$setProp(_el$70, "gap", 2);
511
+ _$insertNode(_el$71, _$createTextNode(`Used / Limit`));
512
+ _$setProp(_el$71, "flexShrink", 0);
513
+ _$insertNode(_el$73, _el$74);
514
+ _$insertNode(_el$73, _el$75);
515
+ _$insertNode(_el$73, _el$76);
516
+ _$setProp(_el$73, "wrapMode", "word");
517
+ _$setProp(_el$73, "minWidth", 0);
518
+ _$insert(_el$73, () => number(details().usage.used), _el$74);
519
+ _$insert(_el$73, () => number(details().usage.limit), _el$75);
520
+ _$insert(_el$73, () => details().usage.percent.toFixed(1), _el$76);
521
+ _$insert(_el$69, () => barRow(details().usage.percent, windowBar()), null);
522
+ _$effect(_p$ => {
523
+ var _v$21 = theme().muted,
524
+ _v$22 = theme().base;
525
+ _v$21 !== _p$.e && (_p$.e = _$setProp(_el$71, "fg", _v$21, _p$.e));
526
+ _v$22 !== _p$.t && (_p$.t = _$setProp(_el$73, "fg", _v$22, _p$.t));
527
+ return _p$;
528
+ }, {
529
+ e: undefined,
530
+ t: undefined
531
+ });
532
+ return _el$69;
533
+ })()
534
+ }), null);
535
+ _$insertNode(_el$43, _el$44);
536
+ _$insertNode(_el$44, _el$45);
537
+ _$setProp(_el$44, "flexDirection", "row");
538
+ _$setProp(_el$44, "justifyContent", "space-between");
539
+ _$setProp(_el$44, "gap", 2);
540
+ _$insertNode(_el$45, _el$46);
541
+ _$insertNode(_el$46, _$createTextNode(`Last Request`));
542
+ _$insert(_el$44, _$createComponent(Show, {
543
+ get when() {
544
+ return sources();
545
+ },
546
+ children: captured => (() => {
547
+ var _el$77 = _$createElement("text");
548
+ _$insert(_el$77, () => new Date(captured().capturedAt).toLocaleTimeString());
549
+ _$effect(_$p => _$setProp(_el$77, "fg", theme().muted, _$p));
550
+ return _el$77;
551
+ })()
552
+ }), null);
553
+ _$insert(_el$43, _$createComponent(Show, {
554
+ get when() {
555
+ return context();
556
+ },
557
+ get fallback() {
558
+ return (() => {
559
+ var _el$78 = _$createElement("text");
560
+ _$insertNode(_el$78, _$createTextNode(`Request composition unavailable`));
561
+ _$effect(_$p => _$setProp(_el$78, "fg", theme().muted, _$p));
562
+ return _el$78;
563
+ })();
564
+ },
565
+ children: details => (() => {
566
+ var _el$80 = _$createElement("box");
567
+ _$insert(_el$80, () => requestRowsList(requestRows(details(), compact())));
568
+ return _el$80;
569
+ })()
570
+ }), null);
571
+ _$insertNode(_el$48, _el$49);
572
+ _$insertNode(_el$49, _el$50);
573
+ _$insertNode(_el$50, _$createTextNode(`Context Breakdown`));
574
+ _$insert(_el$48, _$createComponent(Show, {
575
+ get when() {
576
+ return sources();
577
+ },
578
+ get fallback() {
579
+ return (() => {
580
+ var _el$81 = _$createElement("text");
581
+ _$insertNode(_el$81, _$createTextNode(`Source estimates unavailable (no request snapshot or RPC)`));
582
+ _$effect(_$p => _$setProp(_el$81, "fg", theme().muted, _$p));
583
+ return _el$81;
584
+ })();
585
+ },
586
+ children: captured => _$createComponent(For, {
587
+ get each() {
588
+ return breakdownRows(captured(), detailed());
589
+ },
590
+ children: row => (() => {
591
+ var _el$83 = _$createElement("box"),
592
+ _el$84 = _$createElement("box"),
593
+ _el$85 = _$createElement("text"),
594
+ _el$86 = _$createElement("box"),
595
+ _el$87 = _$createElement("text"),
596
+ _el$88 = _$createElement("text"),
597
+ _el$89 = _$createTextNode(`%`);
598
+ _$insertNode(_el$83, _el$84);
599
+ _$insertNode(_el$83, _el$86);
600
+ _$insertNode(_el$83, _el$87);
601
+ _$insertNode(_el$83, _el$88);
602
+ _$setProp(_el$83, "flexDirection", "row");
603
+ _$setProp(_el$83, "gap", 1);
604
+ _$insertNode(_el$84, _el$85);
605
+ _$setProp(_el$84, "width", 14);
606
+ _$setProp(_el$84, "flexShrink", 0);
607
+ _$insert(_el$85, () => row.label);
608
+ _$setProp(_el$86, "flexShrink", 0);
609
+ _$insert(_el$86, () => barRow(row.percent, breakdownBar()));
610
+ _$setProp(_el$87, "flexShrink", 0);
611
+ _$insert(_el$87, () => number(row.tokens));
612
+ _$insertNode(_el$88, _el$89);
613
+ _$setProp(_el$88, "flexShrink", 0);
614
+ _$insert(_el$88, () => row.percent.toFixed(1), _el$89);
615
+ _$effect(_p$ => {
616
+ var _v$23 = theme().muted,
617
+ _v$24 = breakdownBar(),
618
+ _v$25 = theme().base,
619
+ _v$26 = theme().muted;
620
+ _v$23 !== _p$.e && (_p$.e = _$setProp(_el$85, "fg", _v$23, _p$.e));
621
+ _v$24 !== _p$.t && (_p$.t = _$setProp(_el$86, "width", _v$24, _p$.t));
622
+ _v$25 !== _p$.a && (_p$.a = _$setProp(_el$87, "fg", _v$25, _p$.a));
623
+ _v$26 !== _p$.o && (_p$.o = _$setProp(_el$88, "fg", _v$26, _p$.o));
624
+ return _p$;
625
+ }, {
626
+ e: undefined,
627
+ t: undefined,
628
+ a: undefined,
629
+ o: undefined
630
+ });
631
+ return _el$83;
632
+ })()
633
+ })
634
+ }), null);
635
+ _$insertNode(_el$52, _el$53);
636
+ _$insertNode(_el$53, _el$54);
637
+ _$insertNode(_el$54, _$createTextNode(`Session`));
638
+ _$insert(_el$52, _$createComponent(Show, {
639
+ get when() {
640
+ return state().summary;
641
+ },
642
+ get fallback() {
643
+ return (() => {
644
+ var _el$90 = _$createElement("text");
645
+ _$insertNode(_el$90, _$createTextNode(`Session usage unavailable`));
646
+ _$effect(_$p => _$setProp(_el$90, "fg", theme().muted, _$p));
647
+ return _el$90;
648
+ })();
649
+ },
650
+ children: summary => _$createComponent(Show, {
651
+ get when() {
652
+ return detailed();
653
+ },
654
+ get fallback() {
655
+ return (() => {
656
+ var _el$92 = _$createElement("text"),
657
+ _el$93 = _$createTextNode(` · `),
658
+ _el$94 = _$createTextNode(` · `),
659
+ _el$95 = _$createTextNode(` tokens · `),
660
+ _el$96 = _$createTextNode(`% cached`);
661
+ _$insertNode(_el$92, _el$93);
662
+ _$insertNode(_el$92, _el$94);
663
+ _$insertNode(_el$92, _el$95);
664
+ _$insertNode(_el$92, _el$96);
665
+ _$setProp(_el$92, "wrapMode", "word");
666
+ _$insert(_el$92, () => countLabel(summary().steps, "step"), _el$93);
667
+ _$insert(_el$92, () => countLabel(summary().calls, "call"), _el$94);
668
+ _$insert(_el$92, () => formatCompact(summary().total), _el$95);
669
+ _$insert(_el$92, () => (summary().cacheRate * 100).toFixed(1), _el$96);
670
+ _$insert(_el$92, (() => {
671
+ var _c$ = _$memo(() => !!formatEstimatedCost(summary().cost, summary().costStatus));
672
+ return () => _c$() ? ` · ${formatEstimatedCost(summary().cost, summary().costStatus)}` : "";
673
+ })(), null);
674
+ _$effect(_$p => _$setProp(_el$92, "fg", theme().base, _$p));
675
+ return _el$92;
676
+ })();
677
+ },
678
+ get children() {
679
+ return _$createComponent(For, {
680
+ get each() {
681
+ return summaryRows(summary());
682
+ },
683
+ children: ([label, value]) => (() => {
684
+ var _el$97 = _$createElement("box"),
685
+ _el$98 = _$createElement("text"),
686
+ _el$99 = _$createElement("text");
687
+ _$insertNode(_el$97, _el$98);
688
+ _$insertNode(_el$97, _el$99);
689
+ _$setProp(_el$97, "flexDirection", "row");
690
+ _$setProp(_el$97, "justifyContent", "space-between");
691
+ _$setProp(_el$97, "gap", 2);
692
+ _$setProp(_el$98, "flexShrink", 0);
693
+ _$insert(_el$98, label);
694
+ _$insert(_el$99, value);
695
+ _$effect(_p$ => {
696
+ var _v$27 = theme().muted,
697
+ _v$28 = theme().base;
698
+ _v$27 !== _p$.e && (_p$.e = _$setProp(_el$98, "fg", _v$27, _p$.e));
699
+ _v$28 !== _p$.t && (_p$.t = _$setProp(_el$99, "fg", _v$28, _p$.t));
700
+ return _p$;
701
+ }, {
702
+ e: undefined,
703
+ t: undefined
704
+ });
705
+ return _el$97;
706
+ })()
707
+ });
708
+ }
709
+ })
710
+ }), null);
711
+ _$insertNode(_el$56, _el$57);
712
+ _$insertNode(_el$57, _el$58);
713
+ _$insertNode(_el$58, _$createTextNode(`By Model`));
714
+ _$insert(_el$56, _$createComponent(Show, {
715
+ get when() {
716
+ return models().length;
717
+ },
718
+ get fallback() {
719
+ return (() => {
720
+ var _el$100 = _$createElement("text");
721
+ _$insertNode(_el$100, _$createTextNode(`No model usage yet`));
722
+ _$effect(_$p => _$setProp(_el$100, "fg", theme().muted, _$p));
723
+ return _el$100;
724
+ })();
725
+ },
726
+ get children() {
727
+ return _$createComponent(For, {
728
+ get each() {
729
+ return models();
730
+ },
731
+ children: model => (() => {
732
+ var _el$102 = _$createElement("box"),
733
+ _el$103 = _$createElement("box"),
734
+ _el$104 = _$createElement("text"),
735
+ _el$105 = _$createElement("text"),
736
+ _el$106 = _$createElement("text"),
737
+ _el$107 = _$createTextNode(` tokens · `);
738
+ _$insertNode(_el$102, _el$103);
739
+ _$insertNode(_el$102, _el$106);
740
+ _$insertNode(_el$103, _el$104);
741
+ _$insertNode(_el$103, _el$105);
742
+ _$setProp(_el$103, "flexDirection", "row");
743
+ _$setProp(_el$103, "justifyContent", "space-between");
744
+ _$setProp(_el$103, "gap", 2);
745
+ _$setProp(_el$104, "wrapMode", "word");
746
+ _$setProp(_el$104, "minWidth", 0);
747
+ _$setProp(_el$104, "flexGrow", 1);
748
+ _$insert(_el$104, () => model.model);
749
+ _$setProp(_el$105, "flexShrink", 0);
750
+ _$insert(_el$105, () => formatEstimatedCost(model.cost, model.costStatus) ?? "");
751
+ _$insertNode(_el$106, _el$107);
752
+ _$insert(_el$106, () => number(model.tokens), _el$107);
753
+ _$insert(_el$106, () => countLabel(model.calls, "call"), null);
754
+ _$effect(_p$ => {
755
+ var _v$29 = theme().base,
756
+ _v$30 = theme().base,
757
+ _v$31 = theme().muted;
758
+ _v$29 !== _p$.e && (_p$.e = _$setProp(_el$104, "fg", _v$29, _p$.e));
759
+ _v$30 !== _p$.t && (_p$.t = _$setProp(_el$105, "fg", _v$30, _p$.t));
760
+ _v$31 !== _p$.a && (_p$.a = _$setProp(_el$106, "fg", _v$31, _p$.a));
761
+ return _p$;
762
+ }, {
763
+ e: undefined,
764
+ t: undefined,
765
+ a: undefined
766
+ });
767
+ return _el$102;
768
+ })()
769
+ });
770
+ }
771
+ }), null);
772
+ _$effect(_p$ => {
773
+ var _v$10 = Math.max(3, dimensions().height - Math.floor(dimensions().height / 4) - 8),
774
+ _v$11 = theme().base,
775
+ _v$12 = theme().base,
776
+ _v$13 = theme().base,
777
+ _v$14 = theme().base,
778
+ _v$15 = theme().base;
779
+ _v$10 !== _p$.e && (_p$.e = _$setProp(_el$37, "maxHeight", _v$10, _p$.e));
780
+ _v$11 !== _p$.t && (_p$.t = _$setProp(_el$40, "fg", _v$11, _p$.t));
781
+ _v$12 !== _p$.a && (_p$.a = _$setProp(_el$45, "fg", _v$12, _p$.a));
782
+ _v$13 !== _p$.o && (_p$.o = _$setProp(_el$49, "fg", _v$13, _p$.o));
783
+ _v$14 !== _p$.i && (_p$.i = _$setProp(_el$53, "fg", _v$14, _p$.i));
784
+ _v$15 !== _p$.n && (_p$.n = _$setProp(_el$57, "fg", _v$15, _p$.n));
785
+ return _p$;
786
+ }, {
787
+ e: undefined,
788
+ t: undefined,
789
+ a: undefined,
790
+ o: undefined,
791
+ i: undefined,
792
+ n: undefined
793
+ });
794
+ return _el$37;
795
+ }
796
+ }), _el$60);
797
+ _$insertNode(_el$60, _el$61);
798
+ _$insertNode(_el$60, _el$63);
799
+ _$insertNode(_el$60, _el$65);
800
+ _$setProp(_el$60, "flexDirection", "row");
801
+ _$insertNode(_el$61, _$createTextNode(`↑/↓ scroll · `));
802
+ _$insertNode(_el$63, _$createTextNode(`d details`));
803
+ _$setProp(_el$63, "onMouseUp", event => {
804
+ if (event.button === 0) toggleDetails();
805
+ });
806
+ _$insertNode(_el$65, _$createTextNode(` · esc close`));
807
+ _$effect(_p$ => {
808
+ var _v$16 = theme().base,
809
+ _v$17 = theme().muted,
810
+ _v$18 = theme().muted,
811
+ _v$19 = theme().muted,
812
+ _v$20 = theme().muted;
813
+ _v$16 !== _p$.e && (_p$.e = _$setProp(_el$30, "fg", _v$16, _p$.e));
814
+ _v$17 !== _p$.t && (_p$.t = _$setProp(_el$33, "fg", _v$17, _p$.t));
815
+ _v$18 !== _p$.a && (_p$.a = _$setProp(_el$61, "fg", _v$18, _p$.a));
816
+ _v$19 !== _p$.o && (_p$.o = _$setProp(_el$63, "fg", _v$19, _p$.o));
817
+ _v$20 !== _p$.i && (_p$.i = _$setProp(_el$65, "fg", _v$20, _p$.i));
818
+ return _p$;
819
+ }, {
820
+ e: undefined,
821
+ t: undefined,
822
+ a: undefined,
823
+ o: undefined,
824
+ i: undefined
825
+ });
826
+ return _el$28;
827
+ })();
828
+ }
90
829
  export default Plugin.define({
91
830
  id: "opencode-token-usage.tui",
92
831
  setup(context) {
@@ -111,8 +850,17 @@ export default Plugin.define({
111
850
  register: register
112
851
  })
113
852
  });
114
- // 2.0.9 SessionFrame never mounts the sidebar for a child session.
115
- // Keep the same full panel visible through the supported composer slot.
853
+ const openChildUsage = (sessionID, state) => {
854
+ context.ui.dialog.show(() => _$createComponent(ChildUsageDialog, {
855
+ context: context,
856
+ state: state
857
+ }));
858
+ context.ui.dialog.set({
859
+ size: "medium",
860
+ centered: true
861
+ });
862
+ };
863
+ // Child sessions do not mount the sidebar; keep one live summary above the composer.
116
864
  const removeChild = context.ui.slot({
117
865
  append: "session.composer.top",
118
866
  render: props => _$createComponent(Show, {
@@ -120,33 +868,59 @@ export default Plugin.define({
120
868
  return context.data.session.get(props.sessionID)?.parentID;
121
869
  },
122
870
  get children() {
123
- var _el$9 = _$createElement("box"),
124
- _el$0 = _$createElement("box");
125
- _$insertNode(_el$9, _el$0);
126
- _$setProp(_el$9, "flexDirection", "row");
127
- _$setProp(_el$9, "justifyContent", "flex-end");
128
- _$setProp(_el$9, "paddingRight", 2);
129
- _$setProp(_el$9, "flexShrink", 0);
130
- _$setProp(_el$0, "width", 36);
131
- _$setProp(_el$0, "maxWidth", "100%");
132
- _$setProp(_el$0, "flexDirection", "column");
133
- _$insert(_el$0, _$createComponent(UsagePanel, {
871
+ return _$createComponent(ChildUsageLauncher, {
134
872
  context: context,
135
873
  get sessionID() {
136
874
  return props.sessionID;
137
875
  },
138
876
  performance: performance,
139
- register: register
140
- }));
141
- return _el$9;
877
+ register: register,
878
+ open: openChildUsage
879
+ });
142
880
  }
143
881
  })
144
882
  });
883
+ const removeCommand = context.ui.slot({
884
+ append: "app",
885
+ render: () => {
886
+ context.keymap.layer(() => ({
887
+ mode: "global",
888
+ commands: [{
889
+ id: "opencode-token-usage.show",
890
+ title: "Token usage",
891
+ group: "Session",
892
+ palette: true,
893
+ slash: {
894
+ name: "usage"
895
+ },
896
+ enabled: () => context.ui.router.current().type === "session",
897
+ run: () => {
898
+ const route = context.ui.router.current();
899
+ if (route.type !== "session") return;
900
+ context.ui.dialog.show(() => _$createComponent(UsageDialog, {
901
+ context: context,
902
+ get sessionID() {
903
+ return route.sessionID;
904
+ },
905
+ performance: performance,
906
+ register: register
907
+ }));
908
+ context.ui.dialog.set({
909
+ size: "large",
910
+ centered: true
911
+ });
912
+ }
913
+ }]
914
+ }));
915
+ return null;
916
+ }
917
+ });
145
918
  return () => {
146
919
  for (const controller of controllers) controller.dispose();
147
920
  controllers.clear();
148
921
  remove();
149
922
  removeChild();
923
+ removeCommand();
150
924
  performance.dispose();
151
925
  };
152
926
  }