dsh-context 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/client.js +131 -43
- package/lib/index.js +41 -9
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -72,6 +72,8 @@ pnpm test # typecheck + functional tests for both halves
|
|
|
72
72
|
|
|
73
73
|
`build.mjs` also smoke-checks the outputs (both halves must parse; the host half must import with the `name`/`inject`/`apply` plugin shape).
|
|
74
74
|
|
|
75
|
+
Commits are guarded by a [husky](https://typicode.github.io/husky/) pre-commit hook that runs `pnpm run typecheck` (`tsc --noEmit`, strict).
|
|
76
|
+
|
|
75
77
|
## Files
|
|
76
78
|
|
|
77
79
|
| File | Role |
|
package/lib/client.js
CHANGED
|
@@ -18,14 +18,17 @@ const DICT_ZH = {
|
|
|
18
18
|
"overview.title": "\u5F53\u524D\u6784\u6210",
|
|
19
19
|
"overview.ofWindow": "tokens\uFF08\u7EA6 {p}%\uFF09",
|
|
20
20
|
"overview.estimate": "tokens\uFF08\u4F30\u7B97\uFF09",
|
|
21
|
+
"overview.actual": "\xB7 \u4E0A\u6B21\u5B9E\u9645 prompt {n}",
|
|
21
22
|
"tools.top": "\u5DE5\u5177\u5B9A\u4E49 Top\uFF1A",
|
|
22
23
|
"tools.more": "\u7B49 {n} \u4E2A",
|
|
23
24
|
"trend.title": "\u5386\u53F2\u8D8B\u52BF",
|
|
24
25
|
"gran.step": "\u6B65\u9AA4",
|
|
25
26
|
"gran.turn": "\u8F6E\u6B21",
|
|
26
|
-
"trend.hint": "\u6BCF\u6B21\u6A21\u578B\u8BF7\u6C42\u4E00\u6BB5\uFF1B\u70B9\u51FB\u67F1\u5B50\u67E5\u770B\u8BE6\u60C5\uFF0C\u2702 \u8868\u793A\u538B\u7F29/\u526A\u679D",
|
|
27
|
+
"trend.hint": "\u6BCF\u6B21\u6A21\u578B\u8BF7\u6C42\u4E00\u6BB5\uFF1B\u70B9\u51FB\u67F1\u5B50\u67E5\u770B\u8BE6\u60C5\uFF0C\u2702 \u8868\u793A\u538B\u7F29/\u526A\u679D\uFF1B\u5DE6\u53F3\u6ED1\u52A8\u53EF\u56DE\u770B\u66F4\u65E9",
|
|
27
28
|
"trend.empty": "\u53D1\u8D77\u4E00\u8F6E\u5BF9\u8BDD\u540E\uFF0C\u8FD9\u91CC\u4F1A\u5C55\u793A\u6BCF\u6B21\u6A21\u578B\u8BF7\u6C42\u7684\u4E0A\u4E0B\u6587\u6784\u6210",
|
|
28
29
|
"detail.step": "T{t} \xB7 \u7B2C {s} \u6B65",
|
|
30
|
+
"detail.turn": "T{t} \xB7 \u5171 {n} \u6B65",
|
|
31
|
+
"detail.lastStep": "\u672B\u6B65",
|
|
29
32
|
"detail.estTotal": "\u4F30\u7B97\u5408\u8BA1 \u2248 {n}",
|
|
30
33
|
"detail.actual": "\u5B9E\u9645 prompt {n}",
|
|
31
34
|
"detail.output": "\u8F93\u51FA {n}",
|
|
@@ -39,6 +42,7 @@ const DICT_ZH = {
|
|
|
39
42
|
"error": "\u4E0A\u4E0B\u6587\u6570\u636E\u8BFB\u53D6\u5931\u8D25\uFF1A",
|
|
40
43
|
"footer": "\u4F30\u7B97\u53E3\u5F84\uFF1A\u4E0E dsh \u5185\u7F6E tokenMeter \u76F8\u540C\u7684\u56FA\u5B9A\u5BC6\u5EA6\u542F\u53D1\u5F0F\uFF08\u7EA6 4 \u5B57\u7B26 \u2248 1 token\uFF09\uFF1B\u300C\u5B9E\u9645\u300D\u4E3A\u4F9B\u5E94\u5546\u4E0A\u62A5\u7528\u91CF\u3002",
|
|
41
44
|
"tip.step": "T{t} \xB7 \u7B2C{s}\u6B65",
|
|
45
|
+
"tip.turn": "T{t} \xB7 \u5171 {n} \u6B65",
|
|
42
46
|
"tip.total": "\u5408\u8BA1 \u2248 {n}",
|
|
43
47
|
"tip.actual": "\uFF08\u5B9E\u9645 {n}\uFF09",
|
|
44
48
|
"ev.compaction": "\u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u6458\u8981\u66FF\u6362 {n} \u6761\u6D88\u606F\uFF09",
|
|
@@ -69,14 +73,17 @@ const DICT_EN = {
|
|
|
69
73
|
"overview.title": "Current composition",
|
|
70
74
|
"overview.ofWindow": "tokens (~{p}%)",
|
|
71
75
|
"overview.estimate": "tokens (estimated)",
|
|
76
|
+
"overview.actual": "\xB7 last actual prompt {n}",
|
|
72
77
|
"tools.top": "Top tool schemas:",
|
|
73
78
|
"tools.more": "of {n}",
|
|
74
79
|
"trend.title": "History",
|
|
75
80
|
"gran.step": "Step",
|
|
76
81
|
"gran.turn": "Turn",
|
|
77
|
-
"trend.hint": "one bar per model request; click a bar for details, \u2702 marks compaction/prune",
|
|
82
|
+
"trend.hint": "one bar per model request; click a bar for details, \u2702 marks compaction/prune; scroll sideways to see earlier",
|
|
78
83
|
"trend.empty": "Send a message and each model request\u2019s context makeup shows up here",
|
|
79
84
|
"detail.step": "T{t} \xB7 step {s}",
|
|
85
|
+
"detail.turn": "T{t} \xB7 {n} steps",
|
|
86
|
+
"detail.lastStep": "last step",
|
|
80
87
|
"detail.estTotal": "estimated \u2248 {n}",
|
|
81
88
|
"detail.actual": "actual prompt {n}",
|
|
82
89
|
"detail.output": "output {n}",
|
|
@@ -90,6 +97,7 @@ const DICT_EN = {
|
|
|
90
97
|
"error": "Failed to read context data: ",
|
|
91
98
|
"footer": "Estimate: same fixed-density heuristic as dsh\u2019s built-in tokenMeter (~4 chars \u2248 1 token); \u201Cactual\u201D is provider-reported usage.",
|
|
92
99
|
"tip.step": "T{t} \xB7 step {s}",
|
|
100
|
+
"tip.turn": "T{t} \xB7 {n} steps",
|
|
93
101
|
"tip.total": "total \u2248 {n}",
|
|
94
102
|
"tip.actual": " (actual {n})",
|
|
95
103
|
"ev.compaction": "Context compacted (summary replaced {n} messages)",
|
|
@@ -124,10 +132,16 @@ function fmtTime(t) {
|
|
|
124
132
|
}
|
|
125
133
|
function aggregateByTurn(requests) {
|
|
126
134
|
const out = [];
|
|
135
|
+
let runSteps = 0;
|
|
127
136
|
for (const req of requests) {
|
|
128
137
|
const last = out.length > 0 ? out[out.length - 1] : null;
|
|
129
|
-
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0))
|
|
130
|
-
|
|
138
|
+
if (last !== null && (last.turn ?? 0) === (req.turn ?? 0)) {
|
|
139
|
+
runSteps++;
|
|
140
|
+
out[out.length - 1] = { ...req, stepCount: runSteps };
|
|
141
|
+
} else {
|
|
142
|
+
runSteps = 1;
|
|
143
|
+
out.push({ ...req, stepCount: 1 });
|
|
144
|
+
}
|
|
131
145
|
}
|
|
132
146
|
return out;
|
|
133
147
|
}
|
|
@@ -172,18 +186,21 @@ function makeView(ctx, t) {
|
|
|
172
186
|
let total = 0;
|
|
173
187
|
for (const p of props.parts) total += p.value;
|
|
174
188
|
const scale = props.max !== void 0 && props.max > total ? props.max : total;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
let tip = null;
|
|
190
|
+
if (props.hoverKey !== null && props.hoverKey !== void 0) {
|
|
191
|
+
let acc = 0;
|
|
192
|
+
for (const p of props.parts) {
|
|
193
|
+
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
194
|
+
if (p.key === props.hoverKey && p.value > 0) {
|
|
195
|
+
tip = {
|
|
196
|
+
text: catLabel(p.key) + " " + fmt(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
|
|
197
|
+
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
198
|
+
};
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
acc += pct;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
187
204
|
return h(
|
|
188
205
|
"div",
|
|
189
206
|
{ className: "lc-stacked-wrap" },
|
|
@@ -192,26 +209,24 @@ function makeView(ctx, t) {
|
|
|
192
209
|
{
|
|
193
210
|
className: "lc-stacked",
|
|
194
211
|
style: { height: (props.height || 14) + "px" },
|
|
195
|
-
ref: wrapRef,
|
|
196
212
|
onMouseLeave: () => {
|
|
197
|
-
|
|
213
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
198
214
|
}
|
|
199
215
|
},
|
|
200
216
|
total <= 0 ? null : props.parts.map((p) => {
|
|
201
217
|
if (!p.value) return null;
|
|
218
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
202
219
|
return h("div", {
|
|
203
220
|
key: p.key,
|
|
221
|
+
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
204
222
|
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
205
|
-
onMouseEnter: (
|
|
206
|
-
|
|
207
|
-
},
|
|
208
|
-
onMouseMove: (e) => {
|
|
209
|
-
showTip(e, p);
|
|
223
|
+
onMouseEnter: () => {
|
|
224
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
210
225
|
}
|
|
211
226
|
});
|
|
212
227
|
})
|
|
213
228
|
),
|
|
214
|
-
tip ? h("div", { className: "lc-bar-tip", style: { left: tip.
|
|
229
|
+
tip ? h("div", { className: "lc-bar-tip", style: { left: tip.leftPct + "%" } }, tip.text) : null
|
|
215
230
|
);
|
|
216
231
|
}
|
|
217
232
|
function Legend(props) {
|
|
@@ -221,9 +236,19 @@ function makeView(ctx, t) {
|
|
|
221
236
|
"div",
|
|
222
237
|
{ className: "lc-legend" },
|
|
223
238
|
props.parts.map((p) => {
|
|
239
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
224
240
|
return h(
|
|
225
241
|
"span",
|
|
226
|
-
{
|
|
242
|
+
{
|
|
243
|
+
key: p.key,
|
|
244
|
+
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
245
|
+
onMouseEnter: () => {
|
|
246
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
247
|
+
},
|
|
248
|
+
onMouseLeave: () => {
|
|
249
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
227
252
|
h("i", { style: { background: p.color } }),
|
|
228
253
|
catLabel(p.key) + " " + fmt(p.value),
|
|
229
254
|
total > 0 ? h("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
@@ -257,17 +282,36 @@ function makeView(ctx, t) {
|
|
|
257
282
|
const groups = [];
|
|
258
283
|
for (const req of requests) {
|
|
259
284
|
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
260
|
-
if (grp === null || grp.turn !== req.turn) {
|
|
261
|
-
grp = { turn: req.turn ?? 0, count: 0 };
|
|
285
|
+
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
286
|
+
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
262
287
|
groups.push(grp);
|
|
263
288
|
}
|
|
264
289
|
grp.count++;
|
|
290
|
+
grp.span += req.stepCount ?? 1;
|
|
265
291
|
}
|
|
266
292
|
const scrollRef = React.useRef(null);
|
|
267
|
-
React.
|
|
293
|
+
const scrolledOnce = React.useRef(false);
|
|
294
|
+
const lastGranRef = React.useRef(props.granularity);
|
|
295
|
+
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
296
|
+
const updateEdges = (el) => {
|
|
297
|
+
const left = el.scrollLeft > 4;
|
|
298
|
+
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
299
|
+
setEdges((prev) => prev.left === left && prev.right === right ? prev : { left, right });
|
|
300
|
+
};
|
|
301
|
+
React.useLayoutEffect(() => {
|
|
268
302
|
const el = scrollRef.current;
|
|
269
303
|
if (el === null) return;
|
|
270
|
-
if (
|
|
304
|
+
if (props.granularity !== lastGranRef.current) {
|
|
305
|
+
lastGranRef.current = props.granularity;
|
|
306
|
+
scrolledOnce.current = false;
|
|
307
|
+
}
|
|
308
|
+
if (!scrolledOnce.current) {
|
|
309
|
+
scrolledOnce.current = true;
|
|
310
|
+
el.scrollLeft = el.scrollWidth;
|
|
311
|
+
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
312
|
+
el.scrollLeft = el.scrollWidth;
|
|
313
|
+
}
|
|
314
|
+
updateEdges(el);
|
|
271
315
|
});
|
|
272
316
|
return h(
|
|
273
317
|
"div",
|
|
@@ -281,7 +325,16 @@ function makeView(ctx, t) {
|
|
|
281
325
|
),
|
|
282
326
|
h(
|
|
283
327
|
"div",
|
|
284
|
-
{
|
|
328
|
+
{
|
|
329
|
+
className: "lc-chart-scroll",
|
|
330
|
+
ref: scrollRef,
|
|
331
|
+
onScroll: (e) => {
|
|
332
|
+
updateEdges(e.currentTarget);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
// Edge fades: visible whenever more history sits beyond the viewport,
|
|
336
|
+
// so the horizontal scroll affordance is obvious.
|
|
337
|
+
edges.left ? h("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
285
338
|
// Hovering a bar previews it in the detail below; leaving the plot
|
|
286
339
|
// clears the preview (a pinned selection, if any, takes over again).
|
|
287
340
|
h(
|
|
@@ -295,12 +348,15 @@ function makeView(ctx, t) {
|
|
|
295
348
|
const selected = props.selectedSeq === req.seq;
|
|
296
349
|
const hovered = props.hoveredSeq === req.seq;
|
|
297
350
|
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
298
|
-
const tip = tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 }) + " \xB7 " + fmtTime(req.time) + "\n" + tr("tip.total", { n: fmt(req.total) }) + (req.prompt !== void 0 ? tr("tip.actual", { n: fmt(req.prompt) }) : "") + "\n" + CATS.map((c) => catLabel(c.key) + " " + fmt(req[c.key] || 0)).join(" / ");
|
|
351
|
+
const tip = (req.stepCount !== void 0 && req.stepCount > 1 ? tr("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 })) + " \xB7 " + fmtTime(req.time) + "\n" + tr("tip.total", { n: fmt(req.total) }) + (req.prompt !== void 0 ? tr("tip.actual", { n: fmt(req.prompt) }) : "") + "\n" + CATS.map((c) => catLabel(c.key) + " " + fmt(req[c.key] || 0)).join(" / ");
|
|
299
352
|
return h(
|
|
300
353
|
"div",
|
|
301
354
|
{
|
|
302
355
|
key: req.seq,
|
|
356
|
+
// Uniform column width in BOTH granularities: turn aggregates
|
|
357
|
+
// keep the same fixed width as step bars.
|
|
303
358
|
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
359
|
+
style: { width: BAR_W + "px" },
|
|
304
360
|
title: tip,
|
|
305
361
|
onClick: () => {
|
|
306
362
|
props.onSelect(selected ? null : req.seq);
|
|
@@ -334,11 +390,12 @@ function makeView(ctx, t) {
|
|
|
334
390
|
} },
|
|
335
391
|
groups.map((grp, gi) => {
|
|
336
392
|
const on = props.activeTurn === grp.turn;
|
|
393
|
+
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
337
394
|
return h("span", {
|
|
338
395
|
key: "turn-" + gi,
|
|
339
396
|
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
340
397
|
style: {
|
|
341
|
-
width:
|
|
398
|
+
width: blockW + "px",
|
|
342
399
|
background: TURN_COLORS[gi % TURN_COLORS.length]
|
|
343
400
|
},
|
|
344
401
|
title: "T" + grp.turn,
|
|
@@ -347,20 +404,24 @@ function makeView(ctx, t) {
|
|
|
347
404
|
}
|
|
348
405
|
}, "T" + grp.turn);
|
|
349
406
|
})
|
|
350
|
-
)
|
|
407
|
+
),
|
|
408
|
+
edges.right ? h("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null
|
|
351
409
|
)
|
|
352
410
|
);
|
|
353
411
|
}
|
|
354
412
|
function RequestDetail(props) {
|
|
355
413
|
const req = props.request;
|
|
356
414
|
if (!req) return null;
|
|
415
|
+
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
416
|
+
const head = isTurn ? tr("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
|
|
357
417
|
return h(
|
|
358
418
|
"div",
|
|
359
419
|
{ className: "lc-detail" },
|
|
360
420
|
h(
|
|
361
421
|
"div",
|
|
362
422
|
{ className: "lc-detail-head" },
|
|
363
|
-
h("b", null,
|
|
423
|
+
h("b", null, head),
|
|
424
|
+
isTurn ? h("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null,
|
|
364
425
|
h("span", null, fmtTime(req.time)),
|
|
365
426
|
h("span", null, tr("detail.estTotal", { n: fmt(req.total) })),
|
|
366
427
|
req.prompt !== void 0 ? h("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt(req.prompt) })) : null,
|
|
@@ -432,6 +493,8 @@ function makeView(ctx, t) {
|
|
|
432
493
|
{ key: n.seq, className: "lc-node" },
|
|
433
494
|
h("i", { style: { background: catColor[n.cat] || "#999" } }),
|
|
434
495
|
h("span", { className: "lc-node-preview", title: text }, text),
|
|
496
|
+
// Timestamp when the host event carried one.
|
|
497
|
+
typeof n.time === "number" ? h("span", { className: "lc-node-time" }, fmtTime(n.time)) : null,
|
|
435
498
|
h("span", { className: "lc-node-tokens" }, fmt(n.tokens))
|
|
436
499
|
);
|
|
437
500
|
})
|
|
@@ -446,6 +509,7 @@ function makeView(ctx, t) {
|
|
|
446
509
|
const [hoverTurn, setHoverTurn] = React.useState(null);
|
|
447
510
|
const [tick, setTick] = React.useState(0);
|
|
448
511
|
const [granularity, setGranularity] = React.useState("step");
|
|
512
|
+
const [hoverCat, setHoverCat] = React.useState(null);
|
|
449
513
|
React.useEffect(() => {
|
|
450
514
|
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
451
515
|
let alive = true;
|
|
@@ -521,10 +585,19 @@ function makeView(ctx, t) {
|
|
|
521
585
|
"div",
|
|
522
586
|
{ className: "lc-overview-num" },
|
|
523
587
|
h("b", null, fmt(current.total)),
|
|
524
|
-
h("span", null, data.contextWindow ? " / " + fmt(data.contextWindow) + " " + tr("overview.ofWindow", { p: windowPct ?? 0 }) : " " + t("overview.estimate"))
|
|
588
|
+
h("span", null, data.contextWindow ? " / " + fmt(data.contextWindow) + " " + tr("overview.ofWindow", { p: windowPct ?? 0 }) : " " + t("overview.estimate")),
|
|
589
|
+
// The provider-reported prompt of the last request is the best
|
|
590
|
+
// ground truth for what the model actually received; the fixed
|
|
591
|
+
// density heuristic can undercount CJK-heavy content, so show the
|
|
592
|
+
// real number alongside the estimate.
|
|
593
|
+
displayRequests.length > 0 && displayRequests[displayRequests.length - 1].prompt !== void 0 ? h(
|
|
594
|
+
"span",
|
|
595
|
+
{ className: "lc-actual" },
|
|
596
|
+
tr("overview.actual", { n: fmt(displayRequests[displayRequests.length - 1].prompt ?? 0) })
|
|
597
|
+
) : null
|
|
525
598
|
),
|
|
526
|
-
h(StackedBar, { parts: partsOf(current), height: 16, max: data.contextWindow }),
|
|
527
|
-
h(Legend, { parts: partsOf(current) }),
|
|
599
|
+
h(StackedBar, { parts: partsOf(current), height: 16, max: data.contextWindow, hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
600
|
+
h(Legend, { parts: partsOf(current), hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
528
601
|
data.toolList && data.toolList.length > 0 ? h(
|
|
529
602
|
"div",
|
|
530
603
|
{ className: "lc-tools" },
|
|
@@ -565,11 +638,17 @@ function makeView(ctx, t) {
|
|
|
565
638
|
"div",
|
|
566
639
|
null,
|
|
567
640
|
h(TrendChart, {
|
|
568
|
-
|
|
641
|
+
// Remount per session: switching sessions re-anchors the chart
|
|
642
|
+
// at the newest bars instead of inheriting stale scroll state.
|
|
643
|
+
key: sessionId,
|
|
644
|
+
// The host caps the log at 160 requests; render them ALL so
|
|
645
|
+
// earlier turns/steps stay reachable via horizontal scroll.
|
|
646
|
+
requests: displayRequests,
|
|
569
647
|
events,
|
|
570
648
|
selectedSeq: pinnedReq ? pinnedReq.seq : null,
|
|
571
649
|
hoveredSeq,
|
|
572
650
|
activeTurn,
|
|
651
|
+
granularity,
|
|
573
652
|
onSelect: setSelectedSeq,
|
|
574
653
|
onHover: setHoveredSeq,
|
|
575
654
|
onHoverTurn: setHoverTurn
|
|
@@ -612,7 +691,7 @@ const STYLES = [
|
|
|
612
691
|
".lc-gran { margin-left: auto; display: flex; gap: 2px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 1px; }",
|
|
613
692
|
".lc-gran-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; }",
|
|
614
693
|
".lc-gran-btn:hover { color: var(--dsw-alias-label-primary); }",
|
|
615
|
-
".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-
|
|
694
|
+
".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }",
|
|
616
695
|
".lc-overview-num { margin-bottom: 8px; }",
|
|
617
696
|
".lc-overview-num b { font-size: 20px; }",
|
|
618
697
|
".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
|
|
@@ -620,10 +699,13 @@ const STYLES = [
|
|
|
620
699
|
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); }",
|
|
621
700
|
".lc-bar-tip { position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%); z-index: 5; white-space: nowrap; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.18); pointer-events: none; }",
|
|
622
701
|
".lc-stacked > div { height: 100%; }",
|
|
702
|
+
".lc-stacked-seg-on { filter: brightness(1.18); }",
|
|
623
703
|
".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
|
|
624
704
|
".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }",
|
|
625
705
|
".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
|
|
626
706
|
".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
|
|
707
|
+
".lc-chip-on { font-weight: 600; }",
|
|
708
|
+
".lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
627
709
|
".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
|
|
628
710
|
".lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border-radius: 4px; padding: 1px 7px; font-size: 12px; color: var(--dsw-alias-label-primary); }",
|
|
629
711
|
".lc-chartrow { display: flex; gap: 6px; align-items: stretch; }",
|
|
@@ -632,10 +714,14 @@ const STYLES = [
|
|
|
632
714
|
".lc-axis-top { top: 13px; }",
|
|
633
715
|
".lc-axis-mid { top: 69px; }",
|
|
634
716
|
".lc-axis-bot { top: 125px; }",
|
|
635
|
-
".lc-chart-scroll { flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
|
|
636
|
-
".lc-chart-scroll::-webkit-scrollbar { height:
|
|
637
|
-
".lc-chart-scroll::-webkit-scrollbar-thumb { background: var(--dsw-alias-
|
|
717
|
+
".lc-chart-scroll { position: relative; flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
|
|
718
|
+
".lc-chart-scroll::-webkit-scrollbar { height: 8px; }",
|
|
719
|
+
".lc-chart-scroll::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--dsw-alias-label-secondary) 45%, transparent); border-radius: 4px; }",
|
|
720
|
+
".lc-chart-scroll::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary) 70%, transparent); }",
|
|
638
721
|
".lc-chart-scroll::-webkit-scrollbar-track { background: transparent; }",
|
|
722
|
+
".lc-chart-fade { position: absolute; top: 0; bottom: 0; width: 26px; pointer-events: none; z-index: 2; }",
|
|
723
|
+
".lc-chart-fade-l { left: 0; background: linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
724
|
+
".lc-chart-fade-r { right: 0; background: linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
639
725
|
".lc-chart { position: relative; display: flex; align-items: flex-end; gap: 2px; height: 130px; padding-top: 18px; box-sizing: border-box; width: max-content; min-width: 100%; }",
|
|
640
726
|
".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
|
|
641
727
|
".lc-grid-top { top: 18px; }",
|
|
@@ -649,12 +735,13 @@ const STYLES = [
|
|
|
649
735
|
".lc-bar-stack > div { width: 100%; }",
|
|
650
736
|
".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
|
|
651
737
|
".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }",
|
|
652
|
-
".lc-turn { flex: none;
|
|
738
|
+
".lc-turn { flex: none; box-sizing: border-box; text-align: center; font-size: 10px; line-height: 14px; font-weight: 600; color: #fff; border-radius: 3px; height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; transition: filter 120ms; }",
|
|
653
739
|
".lc-turn-on { filter: brightness(1.35); box-shadow: 0 0 0 1px rgba(255,255,255,0.4); }",
|
|
654
740
|
".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
|
|
655
741
|
".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
656
742
|
".lc-detail-head b { color: var(--dsw-alias-label-primary); }",
|
|
657
743
|
".lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
744
|
+
".lc-detail-tag { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); }",
|
|
658
745
|
".lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }",
|
|
659
746
|
".lc-detail-row { display: flex; align-items: center; gap: 8px; }",
|
|
660
747
|
".lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }",
|
|
@@ -675,6 +762,7 @@ const STYLES = [
|
|
|
675
762
|
".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
676
763
|
".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
677
764
|
".lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }",
|
|
765
|
+
".lc-node-time { color: var(--dsw-alias-label-secondary); font-size: 12px; min-width: 54px; text-align: right; }",
|
|
678
766
|
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
679
767
|
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
680
768
|
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
package/lib/index.js
CHANGED
|
@@ -25,7 +25,10 @@ function estimateBlocks(blocks) {
|
|
|
25
25
|
}
|
|
26
26
|
return tokens;
|
|
27
27
|
}
|
|
28
|
-
function estimateMessage(message) {
|
|
28
|
+
function estimateMessage(message, emptyIsZero = false) {
|
|
29
|
+
if (emptyIsZero && (message === null || message === void 0 || !Array.isArray(message.content) || message.content.length === 0)) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
29
32
|
return estimateBlocks(message?.content) + ROLE_OVERHEAD;
|
|
30
33
|
}
|
|
31
34
|
function estimateSystem(text) {
|
|
@@ -55,6 +58,23 @@ function toolCallNames(blocks) {
|
|
|
55
58
|
function isInjection(source) {
|
|
56
59
|
return source !== null && typeof source === "object" && (source.kind === "plugin" || source.kind === "skill-invocation" || typeof source.form === "string");
|
|
57
60
|
}
|
|
61
|
+
const MAX_REQUEST_STEPS = 1500;
|
|
62
|
+
const MAX_KEPT_TURNS = 300;
|
|
63
|
+
function trimToLastTurns(requests, maxTurns) {
|
|
64
|
+
let runs = 0;
|
|
65
|
+
let start = requests.length;
|
|
66
|
+
let prevTurn;
|
|
67
|
+
for (let i = requests.length - 1; i >= 0; i--) {
|
|
68
|
+
const turn = requests[i].turn;
|
|
69
|
+
if (turn !== prevTurn) {
|
|
70
|
+
if (runs >= maxTurns) break;
|
|
71
|
+
runs++;
|
|
72
|
+
prevTurn = turn;
|
|
73
|
+
}
|
|
74
|
+
start = i;
|
|
75
|
+
}
|
|
76
|
+
return requests.slice(start);
|
|
77
|
+
}
|
|
58
78
|
function createFold() {
|
|
59
79
|
return {
|
|
60
80
|
n: 0,
|
|
@@ -86,7 +106,14 @@ function categoryOf(type, message) {
|
|
|
86
106
|
}
|
|
87
107
|
function applySurface(st, ev, type, data, message) {
|
|
88
108
|
const cat = categoryOf(type, message);
|
|
89
|
-
const node = {
|
|
109
|
+
const node = {
|
|
110
|
+
seq: ev.seq,
|
|
111
|
+
time: ev.time,
|
|
112
|
+
cat,
|
|
113
|
+
// Empty assistant messages project to no model message (usage-only), so
|
|
114
|
+
// they price 0 — mirroring dsh's deriveEventMessage/estimate.
|
|
115
|
+
tokens: estimateMessage(message, type === "assistant/message")
|
|
116
|
+
};
|
|
90
117
|
const source = message?.source;
|
|
91
118
|
const form = source?.form;
|
|
92
119
|
if (typeof form === "string") node.form = form;
|
|
@@ -151,8 +178,7 @@ function foldInto(st, events) {
|
|
|
151
178
|
name: typeof t.name === "string" ? t.name : "?",
|
|
152
179
|
tokens: estimateToolSchema(t)
|
|
153
180
|
}));
|
|
154
|
-
st.toolsTokens =
|
|
155
|
-
if (tools.length > 0) st.toolsTokens += BLOCK_OVERHEAD;
|
|
181
|
+
st.toolsTokens = tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
|
|
156
182
|
st.systemTokens = estimateSystem(header.system);
|
|
157
183
|
if (header.config && typeof header.config.model === "string") st.model = header.config.model;
|
|
158
184
|
if (header.config && typeof header.config.provider === "string") st.provider = header.config.provider;
|
|
@@ -192,9 +218,11 @@ function foldInto(st, events) {
|
|
|
192
218
|
}
|
|
193
219
|
break;
|
|
194
220
|
}
|
|
195
|
-
case "tool/result":
|
|
196
|
-
|
|
221
|
+
case "tool/result": {
|
|
222
|
+
const toolMsg = data?.message ?? null;
|
|
223
|
+
applySurface(st, ev, ev.type, data, toolMsg);
|
|
197
224
|
break;
|
|
225
|
+
}
|
|
198
226
|
case "assistant/message": {
|
|
199
227
|
const usage = data?.usage;
|
|
200
228
|
const total = st.systemTokens + st.toolsTokens + st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
|
|
@@ -216,7 +244,8 @@ function foldInto(st, events) {
|
|
|
216
244
|
if (typeof usage.outputTokens === "number") record.output = usage.outputTokens;
|
|
217
245
|
}
|
|
218
246
|
st.requests.push(record);
|
|
219
|
-
|
|
247
|
+
const asstMsg = data?.message ?? null;
|
|
248
|
+
applySurface(st, ev, ev.type, data, asstMsg);
|
|
220
249
|
break;
|
|
221
250
|
}
|
|
222
251
|
case "compaction/summary":
|
|
@@ -241,8 +270,11 @@ function foldInto(st, events) {
|
|
|
241
270
|
}
|
|
242
271
|
}
|
|
243
272
|
st.n = events.length;
|
|
244
|
-
if (st.requests.length >
|
|
245
|
-
|
|
273
|
+
if (st.requests.length > MAX_REQUEST_STEPS) {
|
|
274
|
+
st.requests = trimToLastTurns(st.requests, MAX_KEPT_TURNS);
|
|
275
|
+
if (st.requests.length > MAX_REQUEST_STEPS) st.requests = st.requests.slice(-MAX_REQUEST_STEPS);
|
|
276
|
+
}
|
|
277
|
+
if (st.events.length > 400) st.events = st.events.slice(-400);
|
|
246
278
|
}
|
|
247
279
|
function buildResult(st) {
|
|
248
280
|
const surfaceTotal = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Context insight panel for DeepSeek Harness: a Context tab (beside Chat/Trajectory) showing what the model's context window is made of and how it evolves — composition, per-request history, compactions, injections, and model switches.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"build": "node scripts/build.mjs",
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
|
30
30
|
"test": "npm run typecheck && node scripts/test-host.mjs && node scripts/test-client.mjs",
|
|
31
|
-
"release": "bash scripts/publish.sh"
|
|
31
|
+
"release": "bash scripts/publish.sh",
|
|
32
|
+
"prepare": "husky"
|
|
32
33
|
},
|
|
33
34
|
"dsh": {
|
|
34
35
|
"bundle": {
|
|
@@ -58,6 +59,10 @@
|
|
|
58
59
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
59
60
|
"@types/react": "^18.3.31",
|
|
60
61
|
"esbuild": "^0.28.2",
|
|
62
|
+
"husky": "^9.1.7",
|
|
63
|
+
"jsdom": "^30.0.1",
|
|
64
|
+
"react": "^18.3.1",
|
|
65
|
+
"react-dom": "^18.3.1",
|
|
61
66
|
"typescript": "^7.0.2"
|
|
62
67
|
},
|
|
63
68
|
"packageManager": "pnpm@11.9.0"
|