opencode-tui-stats 0.4.2 → 0.4.3
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 +949 -0
- package/package.json +5 -4
- package/tracker.ts +0 -768
- package/tui.tsx +0 -215
package/dist/tui.js
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// tui.tsx
|
|
3
|
+
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
4
|
+
import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
5
|
+
import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
6
|
+
import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
7
|
+
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
8
|
+
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
9
|
+
import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
10
|
+
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
11
|
+
import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "opentui:runtime-module:solid-js";
|
|
12
|
+
|
|
13
|
+
// tracker.ts
|
|
14
|
+
var STREAM_WINDOW_MS = 5000;
|
|
15
|
+
var LIVE_STALE_MS = 1500;
|
|
16
|
+
var SINGLE_SAMPLE_MS = 1000;
|
|
17
|
+
var PART_CACHE_LIMIT = 1024;
|
|
18
|
+
var DEDUP_WINDOW_LIMIT = 64;
|
|
19
|
+
var MESSAGE_MODEL_LIMIT = 256;
|
|
20
|
+
var SESSION_LIMIT = 64;
|
|
21
|
+
var SUBTREE_LIMIT = 64;
|
|
22
|
+
var MODEL_ROW_LIMIT = 4;
|
|
23
|
+
function emptyStats() {
|
|
24
|
+
return {
|
|
25
|
+
cacheRead: 0,
|
|
26
|
+
cachePrompt: 0,
|
|
27
|
+
cost: 0,
|
|
28
|
+
steps: 0,
|
|
29
|
+
retries: 0,
|
|
30
|
+
lastAttempt: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function emptyAggregate() {
|
|
34
|
+
return {
|
|
35
|
+
cacheRead: 0,
|
|
36
|
+
cachePrompt: 0,
|
|
37
|
+
cost: 0,
|
|
38
|
+
steps: 0,
|
|
39
|
+
totalTokens: 0,
|
|
40
|
+
totalDurationMs: 0,
|
|
41
|
+
totalTtftMs: 0,
|
|
42
|
+
stepCount: 0,
|
|
43
|
+
lastAt: 0
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function modelKey(providerID, modelID) {
|
|
47
|
+
return `${providerID}/${modelID}`;
|
|
48
|
+
}
|
|
49
|
+
function estimateTokens(delta) {
|
|
50
|
+
return Math.max(1, Math.ceil(Buffer.byteLength(delta, "utf8") / 5));
|
|
51
|
+
}
|
|
52
|
+
function formatRate(value) {
|
|
53
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
54
|
+
return;
|
|
55
|
+
if (value >= 100)
|
|
56
|
+
return Math.round(value).toString();
|
|
57
|
+
if (value >= 10)
|
|
58
|
+
return value.toFixed(1);
|
|
59
|
+
return value.toFixed(2);
|
|
60
|
+
}
|
|
61
|
+
function formatTtft(value) {
|
|
62
|
+
if (!Number.isFinite(value) || value < 0)
|
|
63
|
+
return;
|
|
64
|
+
return `${value.toFixed(1)}s`;
|
|
65
|
+
}
|
|
66
|
+
function activeDuration(samples, tailAt) {
|
|
67
|
+
if (samples.length === 0)
|
|
68
|
+
return 0;
|
|
69
|
+
if (samples.length === 1) {
|
|
70
|
+
const tailDuration = tailAt ? Math.max(0, tailAt - samples[0].at) : SINGLE_SAMPLE_MS;
|
|
71
|
+
return Math.min(Math.max(tailDuration, 250), SINGLE_SAMPLE_MS);
|
|
72
|
+
}
|
|
73
|
+
let duration = 0;
|
|
74
|
+
for (let index = 1;index < samples.length; index++) {
|
|
75
|
+
duration += Math.max(0, samples[index].at - samples[index - 1].at);
|
|
76
|
+
}
|
|
77
|
+
if (tailAt)
|
|
78
|
+
duration += Math.max(0, tailAt - samples[samples.length - 1].at);
|
|
79
|
+
return Math.max(duration, SINGLE_SAMPLE_MS);
|
|
80
|
+
}
|
|
81
|
+
function averageRate(average) {
|
|
82
|
+
if (!average || average.totalTokens <= 0 || average.totalDurationMs <= 0)
|
|
83
|
+
return;
|
|
84
|
+
return formatRate(average.totalTokens / (average.totalDurationMs / 1000));
|
|
85
|
+
}
|
|
86
|
+
function averageTtft(average) {
|
|
87
|
+
if (!average || average.stepCount <= 0)
|
|
88
|
+
return;
|
|
89
|
+
return formatTtft(average.totalTtftMs / average.stepCount / 1000);
|
|
90
|
+
}
|
|
91
|
+
function cacheText(stats) {
|
|
92
|
+
if (!stats || stats.cachePrompt <= 0)
|
|
93
|
+
return;
|
|
94
|
+
return `${(stats.cacheRead / stats.cachePrompt * 100).toFixed(1)}%`;
|
|
95
|
+
}
|
|
96
|
+
function costText(stats) {
|
|
97
|
+
if (!stats || stats.steps <= 0)
|
|
98
|
+
return;
|
|
99
|
+
return `$${stats.cost.toFixed(4)}`;
|
|
100
|
+
}
|
|
101
|
+
function boundedAdd(set, key, limit) {
|
|
102
|
+
if (set.has(key))
|
|
103
|
+
return;
|
|
104
|
+
if (set.size >= limit) {
|
|
105
|
+
const oldest = set.values().next().value;
|
|
106
|
+
if (oldest !== undefined)
|
|
107
|
+
set.delete(oldest);
|
|
108
|
+
}
|
|
109
|
+
set.add(key);
|
|
110
|
+
}
|
|
111
|
+
function boundedSet(map, key, value, limit) {
|
|
112
|
+
if (map.has(key))
|
|
113
|
+
return;
|
|
114
|
+
if (map.size >= limit) {
|
|
115
|
+
const oldest = map.keys().next().value;
|
|
116
|
+
if (oldest !== undefined)
|
|
117
|
+
map.delete(oldest);
|
|
118
|
+
}
|
|
119
|
+
map.set(key, value);
|
|
120
|
+
}
|
|
121
|
+
var lastInternals;
|
|
122
|
+
function createTracker(api, onChange) {
|
|
123
|
+
const tracker = {
|
|
124
|
+
samples: {},
|
|
125
|
+
timings: {},
|
|
126
|
+
averages: {},
|
|
127
|
+
stats: {},
|
|
128
|
+
dedup: {},
|
|
129
|
+
models: {},
|
|
130
|
+
parentOf: {}
|
|
131
|
+
};
|
|
132
|
+
const partDecisions = new Map;
|
|
133
|
+
const mounted = new Set;
|
|
134
|
+
const order = [];
|
|
135
|
+
const listeners = [];
|
|
136
|
+
const bump = () => onChange();
|
|
137
|
+
const clearLive = (sessionID) => {
|
|
138
|
+
if (!tracker.samples[sessionID]?.length)
|
|
139
|
+
return;
|
|
140
|
+
delete tracker.samples[sessionID];
|
|
141
|
+
bump();
|
|
142
|
+
};
|
|
143
|
+
const clearTimings = (sessionID) => {
|
|
144
|
+
let changed = false;
|
|
145
|
+
for (const [messageID, timing] of Object.entries(tracker.timings)) {
|
|
146
|
+
if (timing.sessionID !== sessionID)
|
|
147
|
+
continue;
|
|
148
|
+
delete tracker.timings[messageID];
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
if (changed)
|
|
152
|
+
bump();
|
|
153
|
+
};
|
|
154
|
+
const evictSession = (sessionID) => {
|
|
155
|
+
delete tracker.stats[sessionID];
|
|
156
|
+
delete tracker.averages[sessionID];
|
|
157
|
+
delete tracker.samples[sessionID];
|
|
158
|
+
delete tracker.dedup[sessionID];
|
|
159
|
+
delete tracker.models[sessionID];
|
|
160
|
+
delete tracker.parentOf[sessionID];
|
|
161
|
+
clearTimings(sessionID);
|
|
162
|
+
};
|
|
163
|
+
const touchSession = (sessionID) => {
|
|
164
|
+
const at = order.indexOf(sessionID);
|
|
165
|
+
if (at !== -1)
|
|
166
|
+
order.splice(at, 1);
|
|
167
|
+
order.push(sessionID);
|
|
168
|
+
let index = 0;
|
|
169
|
+
while (order.length > SESSION_LIMIT && index < order.length) {
|
|
170
|
+
const candidate = order[index];
|
|
171
|
+
if (mounted.has(candidate)) {
|
|
172
|
+
index += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
order.splice(index, 1);
|
|
176
|
+
evictSession(candidate);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const touchModel = (models, key) => {
|
|
180
|
+
const at = models.order.indexOf(key);
|
|
181
|
+
if (at !== -1)
|
|
182
|
+
models.order.splice(at, 1);
|
|
183
|
+
models.order.push(key);
|
|
184
|
+
if (!models.aggregates[key])
|
|
185
|
+
models.aggregates[key] = emptyAggregate();
|
|
186
|
+
};
|
|
187
|
+
const prune = (now = Date.now()) => {
|
|
188
|
+
let changed = false;
|
|
189
|
+
for (const [sessionID, samples] of Object.entries(tracker.samples)) {
|
|
190
|
+
const next = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);
|
|
191
|
+
if (next.length === samples.length)
|
|
192
|
+
continue;
|
|
193
|
+
changed = true;
|
|
194
|
+
if (next.length > 0)
|
|
195
|
+
tracker.samples[sessionID] = next;
|
|
196
|
+
else
|
|
197
|
+
delete tracker.samples[sessionID];
|
|
198
|
+
}
|
|
199
|
+
if (changed)
|
|
200
|
+
bump();
|
|
201
|
+
};
|
|
202
|
+
const appendSample = (sessionID, messageID, sample) => {
|
|
203
|
+
touchSession(sessionID);
|
|
204
|
+
tracker.samples[sessionID] = [...(tracker.samples[sessionID] ?? []).filter((item) => sample.at - item.at <= STREAM_WINDOW_MS), sample];
|
|
205
|
+
const timing = tracker.timings[messageID];
|
|
206
|
+
if (timing && timing.firstResponseAt === undefined)
|
|
207
|
+
timing.firstResponseAt = sample.at;
|
|
208
|
+
bump();
|
|
209
|
+
};
|
|
210
|
+
const sessionLiveRate = (sessionID, key) => {
|
|
211
|
+
const frozen = {
|
|
212
|
+
value: tracker.models[sessionID]?.lastRate[key],
|
|
213
|
+
live: false
|
|
214
|
+
};
|
|
215
|
+
const status = api.state.session.status(sessionID);
|
|
216
|
+
if (!status || status.type === "idle")
|
|
217
|
+
return frozen;
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
const mapping = tracker.models[sessionID]?.messageModels;
|
|
220
|
+
const samples = (tracker.samples[sessionID] ?? []).filter((sample) => now - sample.at <= STREAM_WINDOW_MS && mapping?.get(sample.messageID) === key);
|
|
221
|
+
const last = samples[samples.length - 1];
|
|
222
|
+
if (!last || now - last.at > LIVE_STALE_MS)
|
|
223
|
+
return frozen;
|
|
224
|
+
const tokens = samples.reduce((sum, sample) => sum + sample.tokens, 0);
|
|
225
|
+
const value = formatRate(tokens / (activeDuration(samples, now) / 1000));
|
|
226
|
+
if (value === undefined)
|
|
227
|
+
return frozen;
|
|
228
|
+
const models = tracker.models[sessionID];
|
|
229
|
+
if (models)
|
|
230
|
+
models.lastRate[key] = value;
|
|
231
|
+
return {
|
|
232
|
+
value,
|
|
233
|
+
live: true
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
const resolveParent = (sessionID) => {
|
|
237
|
+
if (sessionID in tracker.parentOf)
|
|
238
|
+
return;
|
|
239
|
+
const info = api.state.session.get(sessionID);
|
|
240
|
+
if (!info)
|
|
241
|
+
return;
|
|
242
|
+
tracker.parentOf[sessionID] = info.parentID;
|
|
243
|
+
};
|
|
244
|
+
const rememberParent = (sessionID, parentID) => {
|
|
245
|
+
if (sessionID in tracker.parentOf && tracker.parentOf[sessionID] === parentID)
|
|
246
|
+
return;
|
|
247
|
+
tracker.parentOf[sessionID] = parentID;
|
|
248
|
+
bump();
|
|
249
|
+
};
|
|
250
|
+
const knownSessions = () => {
|
|
251
|
+
const ids = new Set(Object.keys(tracker.models));
|
|
252
|
+
for (const id of Object.keys(tracker.stats))
|
|
253
|
+
ids.add(id);
|
|
254
|
+
for (const id of Object.keys(tracker.averages))
|
|
255
|
+
ids.add(id);
|
|
256
|
+
return ids;
|
|
257
|
+
};
|
|
258
|
+
const descendants = (sessionID) => {
|
|
259
|
+
const children = new Map;
|
|
260
|
+
for (const [child, parent] of Object.entries(tracker.parentOf)) {
|
|
261
|
+
if (!parent)
|
|
262
|
+
continue;
|
|
263
|
+
const siblings = children.get(parent);
|
|
264
|
+
if (siblings)
|
|
265
|
+
siblings.push(child);
|
|
266
|
+
else
|
|
267
|
+
children.set(parent, [child]);
|
|
268
|
+
}
|
|
269
|
+
const seen = new Set([sessionID]);
|
|
270
|
+
const found = [];
|
|
271
|
+
const queue = [sessionID];
|
|
272
|
+
while (queue.length > 0 && found.length < SUBTREE_LIMIT) {
|
|
273
|
+
const current = queue.shift();
|
|
274
|
+
for (const child of children.get(current) ?? []) {
|
|
275
|
+
if (seen.has(child))
|
|
276
|
+
continue;
|
|
277
|
+
seen.add(child);
|
|
278
|
+
found.push(child);
|
|
279
|
+
queue.push(child);
|
|
280
|
+
if (found.length >= SUBTREE_LIMIT)
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return found;
|
|
285
|
+
};
|
|
286
|
+
const mergeAggregate = (target, source) => {
|
|
287
|
+
target.cacheRead += source.cacheRead;
|
|
288
|
+
target.cachePrompt += source.cachePrompt;
|
|
289
|
+
target.cost += source.cost;
|
|
290
|
+
target.steps += source.steps;
|
|
291
|
+
target.totalTokens += source.totalTokens;
|
|
292
|
+
target.totalDurationMs += source.totalDurationMs;
|
|
293
|
+
target.totalTtftMs += source.totalTtftMs;
|
|
294
|
+
target.stepCount += source.stepCount;
|
|
295
|
+
target.lastAt = Math.max(target.lastAt, source.lastAt);
|
|
296
|
+
};
|
|
297
|
+
const sessionModelAt = (sessionID) => {
|
|
298
|
+
const models = tracker.models[sessionID];
|
|
299
|
+
if (!models)
|
|
300
|
+
return 0;
|
|
301
|
+
let at = 0;
|
|
302
|
+
for (const key of models.order) {
|
|
303
|
+
const aggregate = models.aggregates[key];
|
|
304
|
+
if (aggregate && aggregate.lastAt > at)
|
|
305
|
+
at = aggregate.lastAt;
|
|
306
|
+
}
|
|
307
|
+
return at;
|
|
308
|
+
};
|
|
309
|
+
const liveRateFor = (sessions, key) => {
|
|
310
|
+
let latest;
|
|
311
|
+
for (const sessionID of sessions) {
|
|
312
|
+
const aggregate = tracker.models[sessionID]?.aggregates[key];
|
|
313
|
+
if (!aggregate)
|
|
314
|
+
continue;
|
|
315
|
+
if (!latest || aggregate.lastAt >= latest.at)
|
|
316
|
+
latest = {
|
|
317
|
+
sessionID,
|
|
318
|
+
at: aggregate.lastAt
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (!latest)
|
|
322
|
+
return {
|
|
323
|
+
live: false
|
|
324
|
+
};
|
|
325
|
+
return sessionLiveRate(latest.sessionID, key);
|
|
326
|
+
};
|
|
327
|
+
const modelRows = (sessionID) => {
|
|
328
|
+
for (const id of knownSessions())
|
|
329
|
+
resolveParent(id);
|
|
330
|
+
const subtree = [sessionID, ...descendants(sessionID)];
|
|
331
|
+
const retries = tracker.stats[sessionID]?.retries ?? 0;
|
|
332
|
+
let cacheRead = 0;
|
|
333
|
+
let cachePrompt = 0;
|
|
334
|
+
for (const id of subtree) {
|
|
335
|
+
const stats = tracker.stats[id];
|
|
336
|
+
if (!stats)
|
|
337
|
+
continue;
|
|
338
|
+
cacheRead += stats.cacheRead;
|
|
339
|
+
cachePrompt += stats.cachePrompt;
|
|
340
|
+
}
|
|
341
|
+
const cache = cacheText({
|
|
342
|
+
cacheRead,
|
|
343
|
+
cachePrompt
|
|
344
|
+
});
|
|
345
|
+
const sessions = subtree.filter((id) => tracker.models[id]).sort((left, right) => sessionModelAt(left) - sessionModelAt(right));
|
|
346
|
+
if (sessions.length === 0)
|
|
347
|
+
return {
|
|
348
|
+
rows: [],
|
|
349
|
+
overflow: 0,
|
|
350
|
+
retries,
|
|
351
|
+
cache
|
|
352
|
+
};
|
|
353
|
+
const order2 = [];
|
|
354
|
+
const aggregates = {};
|
|
355
|
+
const subagentModels = new Set;
|
|
356
|
+
for (const id of sessions) {
|
|
357
|
+
const models = tracker.models[id];
|
|
358
|
+
for (const key of models.order) {
|
|
359
|
+
const aggregate = models.aggregates[key];
|
|
360
|
+
if (!aggregate)
|
|
361
|
+
continue;
|
|
362
|
+
let merged = aggregates[key];
|
|
363
|
+
if (!merged) {
|
|
364
|
+
merged = emptyAggregate();
|
|
365
|
+
aggregates[key] = merged;
|
|
366
|
+
order2.push(key);
|
|
367
|
+
}
|
|
368
|
+
mergeAggregate(merged, aggregate);
|
|
369
|
+
if (id !== sessionID)
|
|
370
|
+
subagentModels.add(key);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
let active;
|
|
374
|
+
for (const id of sessions)
|
|
375
|
+
active = tracker.models[id].active ?? active;
|
|
376
|
+
const recency = [...order2].reverse();
|
|
377
|
+
const ordered = active && active in aggregates ? [active, ...recency.filter((key) => key !== active)] : recency;
|
|
378
|
+
const visible = ordered.slice(0, MODEL_ROW_LIMIT);
|
|
379
|
+
const overflow = Math.max(0, ordered.length - visible.length);
|
|
380
|
+
const rows = visible.map((key) => {
|
|
381
|
+
const aggregate = aggregates[key];
|
|
382
|
+
const isActive = key === active;
|
|
383
|
+
const tps = isActive ? liveRateFor(subtree, key) : {
|
|
384
|
+
value: undefined,
|
|
385
|
+
live: false
|
|
386
|
+
};
|
|
387
|
+
return {
|
|
388
|
+
key,
|
|
389
|
+
label: key,
|
|
390
|
+
active: isActive,
|
|
391
|
+
subagent: subagentModels.has(key),
|
|
392
|
+
tps: tps.value,
|
|
393
|
+
tpsLive: tps.live,
|
|
394
|
+
avg: averageRate(aggregate),
|
|
395
|
+
ttft: averageTtft(aggregate),
|
|
396
|
+
cache: cacheText(aggregate),
|
|
397
|
+
cost: costText(aggregate)
|
|
398
|
+
};
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
rows,
|
|
402
|
+
overflow,
|
|
403
|
+
retries,
|
|
404
|
+
cache
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
listeners.push(api.event.on("message.part.delta", (event) => {
|
|
408
|
+
if (event.properties.field !== "text")
|
|
409
|
+
return;
|
|
410
|
+
const {
|
|
411
|
+
sessionID,
|
|
412
|
+
messageID,
|
|
413
|
+
partID
|
|
414
|
+
} = event.properties;
|
|
415
|
+
let counted = partDecisions.get(partID);
|
|
416
|
+
if (counted === undefined) {
|
|
417
|
+
const part = api.state.part(messageID).find((item) => item.id === partID);
|
|
418
|
+
if (!part)
|
|
419
|
+
return;
|
|
420
|
+
counted = part.type === "text" || part.type === "reasoning";
|
|
421
|
+
boundedSet(partDecisions, partID, counted, PART_CACHE_LIMIT);
|
|
422
|
+
}
|
|
423
|
+
if (!counted)
|
|
424
|
+
return;
|
|
425
|
+
appendSample(sessionID, messageID, {
|
|
426
|
+
at: Date.now(),
|
|
427
|
+
messageID,
|
|
428
|
+
tokens: estimateTokens(event.properties.delta)
|
|
429
|
+
});
|
|
430
|
+
}));
|
|
431
|
+
listeners.push(api.event.on("message.updated", (event) => {
|
|
432
|
+
const info = event.properties.info;
|
|
433
|
+
if (info.role !== "assistant")
|
|
434
|
+
return;
|
|
435
|
+
const sessionID = event.properties.sessionID;
|
|
436
|
+
const key = modelKey(info.providerID, info.modelID);
|
|
437
|
+
const models = tracker.models[sessionID] ?? (tracker.models[sessionID] = {
|
|
438
|
+
order: [],
|
|
439
|
+
aggregates: {},
|
|
440
|
+
messageModels: new Map,
|
|
441
|
+
lastRate: {}
|
|
442
|
+
});
|
|
443
|
+
touchModel(models, key);
|
|
444
|
+
models.active = key;
|
|
445
|
+
models.aggregates[key] = {
|
|
446
|
+
...models.aggregates[key],
|
|
447
|
+
lastAt: Date.now()
|
|
448
|
+
};
|
|
449
|
+
boundedSet(models.messageModels, info.id, key, MESSAGE_MODEL_LIMIT);
|
|
450
|
+
if (!info.time.completed) {
|
|
451
|
+
const existing = tracker.timings[info.id];
|
|
452
|
+
tracker.timings[info.id] = {
|
|
453
|
+
sessionID,
|
|
454
|
+
requestStartAt: info.time.created,
|
|
455
|
+
firstResponseAt: existing?.firstResponseAt,
|
|
456
|
+
lastToolCallAt: existing?.lastToolCallAt
|
|
457
|
+
};
|
|
458
|
+
bump();
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const seen = tracker.dedup[sessionID] ?? (tracker.dedup[sessionID] = new Set);
|
|
462
|
+
if (!seen.has(info.id)) {
|
|
463
|
+
boundedAdd(seen, info.id, DEDUP_WINDOW_LIMIT);
|
|
464
|
+
touchSession(sessionID);
|
|
465
|
+
const stats = tracker.stats[sessionID] ?? emptyStats();
|
|
466
|
+
const cacheRead = info.tokens.cache.read;
|
|
467
|
+
const cacheWrite = info.tokens.cache.write;
|
|
468
|
+
tracker.stats[sessionID] = {
|
|
469
|
+
...stats,
|
|
470
|
+
cacheRead: stats.cacheRead + cacheRead,
|
|
471
|
+
cachePrompt: stats.cachePrompt + info.tokens.input + cacheRead + cacheWrite,
|
|
472
|
+
cost: stats.cost + info.cost,
|
|
473
|
+
steps: stats.steps + 1
|
|
474
|
+
};
|
|
475
|
+
const aggregate = models.aggregates[key] ?? emptyAggregate();
|
|
476
|
+
models.aggregates[key] = {
|
|
477
|
+
...aggregate,
|
|
478
|
+
cacheRead: aggregate.cacheRead + cacheRead,
|
|
479
|
+
cachePrompt: aggregate.cachePrompt + info.tokens.input + cacheRead + cacheWrite,
|
|
480
|
+
cost: aggregate.cost + info.cost,
|
|
481
|
+
steps: aggregate.steps + 1
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const timing = tracker.timings[info.id];
|
|
485
|
+
if (timing?.sessionID === sessionID && timing.firstResponseAt !== undefined) {
|
|
486
|
+
const tokens = info.tokens.output + info.tokens.reasoning;
|
|
487
|
+
const endAt = info.finish === "tool-calls" ? timing.lastToolCallAt : info.time.completed;
|
|
488
|
+
const durationMs = endAt !== undefined ? Math.max(endAt - timing.firstResponseAt, 1) : undefined;
|
|
489
|
+
if (tokens > 0 && durationMs !== undefined) {
|
|
490
|
+
const totals = tracker.averages[sessionID] ?? {
|
|
491
|
+
totalTokens: 0,
|
|
492
|
+
totalDurationMs: 0,
|
|
493
|
+
totalTtftMs: 0,
|
|
494
|
+
stepCount: 0
|
|
495
|
+
};
|
|
496
|
+
tracker.averages[sessionID] = {
|
|
497
|
+
totalTokens: totals.totalTokens + tokens,
|
|
498
|
+
totalDurationMs: totals.totalDurationMs + durationMs,
|
|
499
|
+
totalTtftMs: totals.totalTtftMs + Math.max(timing.firstResponseAt - timing.requestStartAt, 0),
|
|
500
|
+
stepCount: totals.stepCount + 1
|
|
501
|
+
};
|
|
502
|
+
const aggregate = models.aggregates[key] ?? emptyAggregate();
|
|
503
|
+
models.aggregates[key] = {
|
|
504
|
+
...aggregate,
|
|
505
|
+
totalTokens: aggregate.totalTokens + tokens,
|
|
506
|
+
totalDurationMs: aggregate.totalDurationMs + durationMs,
|
|
507
|
+
totalTtftMs: aggregate.totalTtftMs + Math.max(timing.firstResponseAt - timing.requestStartAt, 0),
|
|
508
|
+
stepCount: aggregate.stepCount + 1
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
delete tracker.timings[info.id];
|
|
513
|
+
prune(info.time.completed);
|
|
514
|
+
bump();
|
|
515
|
+
}));
|
|
516
|
+
listeners.push(api.event.on("message.part.updated", (event) => {
|
|
517
|
+
const part = event.properties.part;
|
|
518
|
+
if (part.type !== "tool")
|
|
519
|
+
return;
|
|
520
|
+
if (part.state.status === "running" || part.state.status === "completed" || part.state.status === "error") {
|
|
521
|
+
clearLive(part.sessionID);
|
|
522
|
+
}
|
|
523
|
+
const timing = tracker.timings[part.messageID];
|
|
524
|
+
if (!timing)
|
|
525
|
+
return;
|
|
526
|
+
if (part.state.status === "pending") {
|
|
527
|
+
if (timing.firstResponseAt === undefined)
|
|
528
|
+
timing.firstResponseAt = Date.now();
|
|
529
|
+
bump();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (part.state.status === "running") {
|
|
533
|
+
timing.lastToolCallAt = part.state.time.start;
|
|
534
|
+
bump();
|
|
535
|
+
}
|
|
536
|
+
}));
|
|
537
|
+
listeners.push(api.event.on("session.status", (event) => {
|
|
538
|
+
const {
|
|
539
|
+
sessionID,
|
|
540
|
+
status
|
|
541
|
+
} = event.properties;
|
|
542
|
+
const type = status.type;
|
|
543
|
+
if (type === "idle")
|
|
544
|
+
clearTimings(sessionID);
|
|
545
|
+
const stats = tracker.stats[sessionID] ?? emptyStats();
|
|
546
|
+
if (type === "retry") {
|
|
547
|
+
const attempt = status.attempt;
|
|
548
|
+
if (attempt <= stats.lastAttempt)
|
|
549
|
+
return;
|
|
550
|
+
touchSession(sessionID);
|
|
551
|
+
tracker.stats[sessionID] = {
|
|
552
|
+
...stats,
|
|
553
|
+
retries: stats.retries + (attempt - stats.lastAttempt),
|
|
554
|
+
lastAttempt: attempt
|
|
555
|
+
};
|
|
556
|
+
bump();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (stats.lastAttempt === 0)
|
|
560
|
+
return;
|
|
561
|
+
touchSession(sessionID);
|
|
562
|
+
tracker.stats[sessionID] = {
|
|
563
|
+
...stats,
|
|
564
|
+
lastAttempt: 0
|
|
565
|
+
};
|
|
566
|
+
bump();
|
|
567
|
+
}));
|
|
568
|
+
listeners.push(api.event.on("session.idle", (event) => {
|
|
569
|
+
clearTimings(event.properties.sessionID);
|
|
570
|
+
}));
|
|
571
|
+
listeners.push(api.event.on("session.created", (event) => {
|
|
572
|
+
rememberParent(event.properties.info.id, event.properties.info.parentID);
|
|
573
|
+
}));
|
|
574
|
+
listeners.push(api.event.on("session.updated", (event) => {
|
|
575
|
+
rememberParent(event.properties.info.id, event.properties.info.parentID);
|
|
576
|
+
}));
|
|
577
|
+
const timer = setInterval(() => {
|
|
578
|
+
prune();
|
|
579
|
+
bump();
|
|
580
|
+
}, 1000);
|
|
581
|
+
const internals = {
|
|
582
|
+
sizes: () => ({
|
|
583
|
+
parts: partDecisions.size,
|
|
584
|
+
dedup: Object.values(tracker.dedup).reduce((sum, set) => sum + set.size, 0),
|
|
585
|
+
stats: Object.keys(tracker.stats).length,
|
|
586
|
+
averages: Object.keys(tracker.averages).length,
|
|
587
|
+
timings: Object.keys(tracker.timings).length,
|
|
588
|
+
mounted: mounted.size
|
|
589
|
+
}),
|
|
590
|
+
partDecision: (partID) => partDecisions.get(partID),
|
|
591
|
+
hasStats: (sessionID) => (sessionID in tracker.stats),
|
|
592
|
+
dedupSize: (sessionID) => tracker.dedup[sessionID]?.size ?? 0,
|
|
593
|
+
retries: (sessionID) => tracker.stats[sessionID]?.retries ?? 0,
|
|
594
|
+
mounted: () => [...mounted],
|
|
595
|
+
activeModel: (sessionID) => tracker.models[sessionID]?.active
|
|
596
|
+
};
|
|
597
|
+
lastInternals = internals;
|
|
598
|
+
return {
|
|
599
|
+
modelRows,
|
|
600
|
+
mountSession(sessionID) {
|
|
601
|
+
mounted.add(sessionID);
|
|
602
|
+
},
|
|
603
|
+
unmountSession(sessionID) {
|
|
604
|
+
mounted.delete(sessionID);
|
|
605
|
+
},
|
|
606
|
+
prune,
|
|
607
|
+
internals,
|
|
608
|
+
dispose() {
|
|
609
|
+
for (const unsubscribe of listeners)
|
|
610
|
+
unsubscribe();
|
|
611
|
+
listeners.length = 0;
|
|
612
|
+
clearInterval(timer);
|
|
613
|
+
mounted.clear();
|
|
614
|
+
if (lastInternals === internals)
|
|
615
|
+
lastInternals = undefined;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
var lastPanelController;
|
|
620
|
+
function setLastPanelController(controller) {
|
|
621
|
+
lastPanelController = controller;
|
|
622
|
+
}
|
|
623
|
+
function clearLastPanelController(controller) {
|
|
624
|
+
if (lastPanelController === controller)
|
|
625
|
+
lastPanelController = undefined;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// tui.tsx
|
|
629
|
+
var DETAIL_INDENT = " ";
|
|
630
|
+
var RATE_CELL = 4;
|
|
631
|
+
var TTFT_CELL = 6;
|
|
632
|
+
var CACHE_CELL = 6;
|
|
633
|
+
function splitLabel(label) {
|
|
634
|
+
const slash = label.indexOf("/");
|
|
635
|
+
if (slash <= 0)
|
|
636
|
+
return {
|
|
637
|
+
provider: "",
|
|
638
|
+
model: label
|
|
639
|
+
};
|
|
640
|
+
const provider = label.slice(0, slash);
|
|
641
|
+
const model = label.slice(slash + 1);
|
|
642
|
+
if (model.toLowerCase().startsWith(provider.toLowerCase()))
|
|
643
|
+
return {
|
|
644
|
+
provider: "",
|
|
645
|
+
model
|
|
646
|
+
};
|
|
647
|
+
return {
|
|
648
|
+
provider: `${provider}/`,
|
|
649
|
+
model
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function rateCell(value) {
|
|
653
|
+
return value === undefined ? "\u2014" : `${value.padStart(RATE_CELL)}t/s`;
|
|
654
|
+
}
|
|
655
|
+
function ttftCell(value) {
|
|
656
|
+
return (value ?? "\u2014").padEnd(TTFT_CELL);
|
|
657
|
+
}
|
|
658
|
+
function cacheCell(value) {
|
|
659
|
+
if (value === undefined)
|
|
660
|
+
return "\u2014".padStart(CACHE_CELL);
|
|
661
|
+
const percent = value.endsWith("%") ? value.slice(0, -1) : value;
|
|
662
|
+
return `${percent.padStart(5)}%`;
|
|
663
|
+
}
|
|
664
|
+
function ModelRowView(props) {
|
|
665
|
+
let pressed;
|
|
666
|
+
const handleMouseDown = (event) => {
|
|
667
|
+
pressed = {
|
|
668
|
+
x: event.x,
|
|
669
|
+
y: event.y
|
|
670
|
+
};
|
|
671
|
+
};
|
|
672
|
+
const handleMouseUp = (event) => {
|
|
673
|
+
const start = pressed;
|
|
674
|
+
pressed = undefined;
|
|
675
|
+
if (start && (start.x !== event.x || start.y !== event.y))
|
|
676
|
+
return;
|
|
677
|
+
props.onToggle();
|
|
678
|
+
};
|
|
679
|
+
const theme = () => props.api.theme.current;
|
|
680
|
+
const parts = () => splitLabel(props.row.label);
|
|
681
|
+
return (() => {
|
|
682
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("span"), _el$4 = _$createElement("span"), _el$8 = _$createElement("span");
|
|
683
|
+
_$insertNode(_el$, _el$2);
|
|
684
|
+
_$setProp(_el$, "flexDirection", "column");
|
|
685
|
+
_$setProp(_el$, "onMouseDown", handleMouseDown);
|
|
686
|
+
_$setProp(_el$, "onMouseUp", handleMouseUp);
|
|
687
|
+
_$insertNode(_el$2, _el$3);
|
|
688
|
+
_$insertNode(_el$2, _el$4);
|
|
689
|
+
_$insertNode(_el$2, _el$8);
|
|
690
|
+
_$setProp(_el$2, "wrapMode", "none");
|
|
691
|
+
_$setProp(_el$2, "truncate", true);
|
|
692
|
+
_$insert(_el$3, () => props.row.active ? "\u25CF " : " ");
|
|
693
|
+
_$insert(_el$4, () => `${props.expanded ? "\u25BC" : "\u25B6"} `);
|
|
694
|
+
_$insert(_el$2, _$createComponent(Show, {
|
|
695
|
+
get when() {
|
|
696
|
+
return props.row.subagent;
|
|
697
|
+
},
|
|
698
|
+
get children() {
|
|
699
|
+
var _el$5 = _$createElement("span");
|
|
700
|
+
_$insertNode(_el$5, _$createTextNode(`\u21B3 `));
|
|
701
|
+
_$effect((_$p) => _$setProp(_el$5, "style", {
|
|
702
|
+
fg: theme().textMuted
|
|
703
|
+
}, _$p));
|
|
704
|
+
return _el$5;
|
|
705
|
+
}
|
|
706
|
+
}), _el$8);
|
|
707
|
+
_$insert(_el$2, _$createComponent(Show, {
|
|
708
|
+
get when() {
|
|
709
|
+
return parts().provider;
|
|
710
|
+
},
|
|
711
|
+
get children() {
|
|
712
|
+
var _el$7 = _$createElement("span");
|
|
713
|
+
_$insert(_el$7, () => parts().provider);
|
|
714
|
+
_$effect((_$p) => _$setProp(_el$7, "style", {
|
|
715
|
+
fg: theme().text,
|
|
716
|
+
dim: true
|
|
717
|
+
}, _$p));
|
|
718
|
+
return _el$7;
|
|
719
|
+
}
|
|
720
|
+
}), _el$8);
|
|
721
|
+
_$insert(_el$8, () => parts().model);
|
|
722
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
723
|
+
get when() {
|
|
724
|
+
return props.expanded;
|
|
725
|
+
},
|
|
726
|
+
get children() {
|
|
727
|
+
return [(() => {
|
|
728
|
+
var _el$9 = _$createElement("text"), _el$0 = _$createElement("span");
|
|
729
|
+
_$insertNode(_el$9, _el$0);
|
|
730
|
+
_$setProp(_el$9, "wrapMode", "none");
|
|
731
|
+
_$setProp(_el$9, "truncate", true);
|
|
732
|
+
_$insert(_el$0, () => `${DETAIL_INDENT}AVG ${rateCell(props.row.avg)}`);
|
|
733
|
+
_$insert(_el$9, _$createComponent(Show, {
|
|
734
|
+
get when() {
|
|
735
|
+
return props.row.tps !== undefined;
|
|
736
|
+
},
|
|
737
|
+
get children() {
|
|
738
|
+
return [(() => {
|
|
739
|
+
var _el$1 = _$createElement("span");
|
|
740
|
+
_$insertNode(_el$1, _$createTextNode(` \xB7 TPS `));
|
|
741
|
+
_$effect((_$p) => _$setProp(_el$1, "style", {
|
|
742
|
+
fg: theme().textMuted
|
|
743
|
+
}, _$p));
|
|
744
|
+
return _el$1;
|
|
745
|
+
})(), (() => {
|
|
746
|
+
var _el$11 = _$createElement("span");
|
|
747
|
+
_$insert(_el$11, () => rateCell(props.row.tps));
|
|
748
|
+
_$effect((_$p) => _$setProp(_el$11, "style", {
|
|
749
|
+
fg: theme().textMuted,
|
|
750
|
+
dim: !props.row.tpsLive
|
|
751
|
+
}, _$p));
|
|
752
|
+
return _el$11;
|
|
753
|
+
})()];
|
|
754
|
+
}
|
|
755
|
+
}), null);
|
|
756
|
+
_$effect((_p$) => {
|
|
757
|
+
var _v$ = theme().textMuted, _v$2 = {
|
|
758
|
+
fg: theme().textMuted
|
|
759
|
+
};
|
|
760
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$9, "fg", _v$, _p$.e));
|
|
761
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$0, "style", _v$2, _p$.t));
|
|
762
|
+
return _p$;
|
|
763
|
+
}, {
|
|
764
|
+
e: undefined,
|
|
765
|
+
t: undefined
|
|
766
|
+
});
|
|
767
|
+
return _el$9;
|
|
768
|
+
})(), (() => {
|
|
769
|
+
var _el$12 = _$createElement("text");
|
|
770
|
+
_$setProp(_el$12, "wrapMode", "none");
|
|
771
|
+
_$setProp(_el$12, "truncate", true);
|
|
772
|
+
_$insert(_el$12, () => `${DETAIL_INDENT}TTFT ${ttftCell(props.row.ttft)} \xB7 CACHE ${cacheCell(props.row.cache)}`);
|
|
773
|
+
_$effect((_$p) => _$setProp(_el$12, "fg", theme().textMuted, _$p));
|
|
774
|
+
return _el$12;
|
|
775
|
+
})(), (() => {
|
|
776
|
+
var _el$13 = _$createElement("text");
|
|
777
|
+
_$setProp(_el$13, "wrapMode", "none");
|
|
778
|
+
_$setProp(_el$13, "truncate", true);
|
|
779
|
+
_$insert(_el$13, () => `${DETAIL_INDENT}${props.row.cost ?? "\u2014"}`);
|
|
780
|
+
_$effect((_$p) => _$setProp(_el$13, "fg", theme().textMuted, _$p));
|
|
781
|
+
return _el$13;
|
|
782
|
+
})()];
|
|
783
|
+
}
|
|
784
|
+
}), null);
|
|
785
|
+
_$effect((_p$) => {
|
|
786
|
+
var _v$3 = {
|
|
787
|
+
fg: theme().success
|
|
788
|
+
}, _v$4 = {
|
|
789
|
+
fg: theme().textMuted
|
|
790
|
+
}, _v$5 = {
|
|
791
|
+
fg: theme().text
|
|
792
|
+
};
|
|
793
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$3, _p$.e));
|
|
794
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$4, _p$.t));
|
|
795
|
+
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$8, "style", _v$5, _p$.a));
|
|
796
|
+
return _p$;
|
|
797
|
+
}, {
|
|
798
|
+
e: undefined,
|
|
799
|
+
t: undefined,
|
|
800
|
+
a: undefined
|
|
801
|
+
});
|
|
802
|
+
return _el$;
|
|
803
|
+
})();
|
|
804
|
+
}
|
|
805
|
+
function ModelPanelView(props) {
|
|
806
|
+
let mounted;
|
|
807
|
+
const initialActive = props.tracker.internals.activeModel(props.sessionID);
|
|
808
|
+
const [expanded, setExpanded] = createSignal(new Set(initialActive ? [initialActive] : []));
|
|
809
|
+
let seededActive = initialActive;
|
|
810
|
+
let lastSession = props.sessionID;
|
|
811
|
+
const panel = createMemo(() => {
|
|
812
|
+
props.revision();
|
|
813
|
+
const sessionID = props.sessionID;
|
|
814
|
+
if (mounted !== sessionID) {
|
|
815
|
+
if (mounted !== undefined)
|
|
816
|
+
props.tracker.unmountSession(mounted);
|
|
817
|
+
props.tracker.mountSession(sessionID);
|
|
818
|
+
mounted = sessionID;
|
|
819
|
+
}
|
|
820
|
+
return props.tracker.modelRows(sessionID);
|
|
821
|
+
});
|
|
822
|
+
createEffect(() => {
|
|
823
|
+
props.revision();
|
|
824
|
+
const sessionID = props.sessionID;
|
|
825
|
+
const active = props.tracker.internals.activeModel(sessionID);
|
|
826
|
+
if (sessionID !== lastSession) {
|
|
827
|
+
lastSession = sessionID;
|
|
828
|
+
seededActive = active;
|
|
829
|
+
setExpanded(new Set(active ? [active] : []));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (active && active !== seededActive) {
|
|
833
|
+
seededActive = active;
|
|
834
|
+
setExpanded(new Set([active]));
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
const toggle = (key) => {
|
|
838
|
+
setExpanded((prev) => {
|
|
839
|
+
const next = new Set(prev);
|
|
840
|
+
if (next.has(key))
|
|
841
|
+
next.delete(key);
|
|
842
|
+
else
|
|
843
|
+
next.add(key);
|
|
844
|
+
return next;
|
|
845
|
+
});
|
|
846
|
+
props.api.renderer?.requestRender?.();
|
|
847
|
+
};
|
|
848
|
+
const controller = {
|
|
849
|
+
toggle,
|
|
850
|
+
isExpanded: (key) => expanded().has(key)
|
|
851
|
+
};
|
|
852
|
+
setLastPanelController(controller);
|
|
853
|
+
onCleanup(() => {
|
|
854
|
+
if (mounted !== undefined)
|
|
855
|
+
props.tracker.unmountSession(mounted);
|
|
856
|
+
clearLastPanelController(controller);
|
|
857
|
+
});
|
|
858
|
+
return (() => {
|
|
859
|
+
var _el$14 = _$createElement("box"), _el$15 = _$createElement("text"), _el$16 = _$createElement("b"), _el$21 = _$createElement("text");
|
|
860
|
+
_$insertNode(_el$14, _el$15);
|
|
861
|
+
_$insertNode(_el$14, _el$21);
|
|
862
|
+
_$setProp(_el$14, "flexDirection", "column");
|
|
863
|
+
_$insertNode(_el$15, _el$16);
|
|
864
|
+
_$setProp(_el$15, "wrapMode", "none");
|
|
865
|
+
_$setProp(_el$15, "truncate", true);
|
|
866
|
+
_$insertNode(_el$16, _$createTextNode(`Model-Performance \xB7 observed`));
|
|
867
|
+
_$insert(_el$14, _$createComponent(Show, {
|
|
868
|
+
get when() {
|
|
869
|
+
return panel().rows.length === 0;
|
|
870
|
+
},
|
|
871
|
+
get children() {
|
|
872
|
+
var _el$18 = _$createElement("text");
|
|
873
|
+
_$insertNode(_el$18, _$createTextNode(`\u2014`));
|
|
874
|
+
_$setProp(_el$18, "wrapMode", "none");
|
|
875
|
+
_$setProp(_el$18, "truncate", true);
|
|
876
|
+
_$effect((_$p) => _$setProp(_el$18, "fg", props.api.theme.current.textMuted, _$p));
|
|
877
|
+
return _el$18;
|
|
878
|
+
}
|
|
879
|
+
}), _el$21);
|
|
880
|
+
_$insert(_el$14, _$createComponent(For, {
|
|
881
|
+
get each() {
|
|
882
|
+
return panel().rows;
|
|
883
|
+
},
|
|
884
|
+
children: (row) => _$createComponent(ModelRowView, {
|
|
885
|
+
get api() {
|
|
886
|
+
return props.api;
|
|
887
|
+
},
|
|
888
|
+
row,
|
|
889
|
+
get expanded() {
|
|
890
|
+
return expanded().has(row.key);
|
|
891
|
+
},
|
|
892
|
+
onToggle: () => toggle(row.key)
|
|
893
|
+
})
|
|
894
|
+
}), _el$21);
|
|
895
|
+
_$insert(_el$14, _$createComponent(Show, {
|
|
896
|
+
get when() {
|
|
897
|
+
return panel().overflow > 0;
|
|
898
|
+
},
|
|
899
|
+
get children() {
|
|
900
|
+
var _el$20 = _$createElement("text");
|
|
901
|
+
_$setProp(_el$20, "wrapMode", "none");
|
|
902
|
+
_$setProp(_el$20, "truncate", true);
|
|
903
|
+
_$insert(_el$20, () => `+${panel().overflow} more models`);
|
|
904
|
+
_$effect((_$p) => _$setProp(_el$20, "fg", props.api.theme.current.textMuted, _$p));
|
|
905
|
+
return _el$20;
|
|
906
|
+
}
|
|
907
|
+
}), _el$21);
|
|
908
|
+
_$setProp(_el$21, "wrapMode", "none");
|
|
909
|
+
_$setProp(_el$21, "truncate", true);
|
|
910
|
+
_$insert(_el$21, () => `Session CACHE ${panel().cache ?? "\u2014"}${panel().retries > 0 ? ` \xB7 retries ${panel().retries}` : ""}`);
|
|
911
|
+
_$effect((_p$) => {
|
|
912
|
+
var _v$6 = props.api.theme.current.text, _v$7 = props.api.theme.current.textMuted;
|
|
913
|
+
_v$6 !== _p$.e && (_p$.e = _$setProp(_el$15, "fg", _v$6, _p$.e));
|
|
914
|
+
_v$7 !== _p$.t && (_p$.t = _$setProp(_el$21, "fg", _v$7, _p$.t));
|
|
915
|
+
return _p$;
|
|
916
|
+
}, {
|
|
917
|
+
e: undefined,
|
|
918
|
+
t: undefined
|
|
919
|
+
});
|
|
920
|
+
return _el$14;
|
|
921
|
+
})();
|
|
922
|
+
}
|
|
923
|
+
var tui = async (api) => {
|
|
924
|
+
const [revision, setRevision] = createSignal(0);
|
|
925
|
+
const tracker = createTracker(api, () => setRevision((value) => value + 1));
|
|
926
|
+
api.lifecycle.onDispose(() => tracker.dispose());
|
|
927
|
+
api.slots.register({
|
|
928
|
+
slots: {
|
|
929
|
+
sidebar_footer(_context, props) {
|
|
930
|
+
return _$createComponent(ModelPanelView, {
|
|
931
|
+
api,
|
|
932
|
+
get sessionID() {
|
|
933
|
+
return props.session_id;
|
|
934
|
+
},
|
|
935
|
+
tracker,
|
|
936
|
+
revision
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
};
|
|
942
|
+
var plugin = {
|
|
943
|
+
id: "opencode-tui-stats",
|
|
944
|
+
tui
|
|
945
|
+
};
|
|
946
|
+
var tui_default = plugin;
|
|
947
|
+
export {
|
|
948
|
+
tui_default as default
|
|
949
|
+
};
|