dsh-context 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/client.js +111 -25
- package/lib/index.js +41 -9
- package/package.json +4 -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
|
}
|
|
@@ -183,6 +197,11 @@ function makeView(ctx, t) {
|
|
|
183
197
|
text: catLabel(p.key) + " " + fmt(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
|
|
184
198
|
left: Math.max(48, Math.min(left, width - 48))
|
|
185
199
|
});
|
|
200
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
201
|
+
};
|
|
202
|
+
const clearHover = () => {
|
|
203
|
+
setTip(null);
|
|
204
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
186
205
|
};
|
|
187
206
|
return h(
|
|
188
207
|
"div",
|
|
@@ -193,14 +212,14 @@ function makeView(ctx, t) {
|
|
|
193
212
|
className: "lc-stacked",
|
|
194
213
|
style: { height: (props.height || 14) + "px" },
|
|
195
214
|
ref: wrapRef,
|
|
196
|
-
onMouseLeave:
|
|
197
|
-
setTip(null);
|
|
198
|
-
}
|
|
215
|
+
onMouseLeave: clearHover
|
|
199
216
|
},
|
|
200
217
|
total <= 0 ? null : props.parts.map((p) => {
|
|
201
218
|
if (!p.value) return null;
|
|
219
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
202
220
|
return h("div", {
|
|
203
221
|
key: p.key,
|
|
222
|
+
className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : ""),
|
|
204
223
|
style: { width: p.value / scale * 100 + "%", background: p.color },
|
|
205
224
|
onMouseEnter: (e) => {
|
|
206
225
|
showTip(e, p);
|
|
@@ -221,9 +240,19 @@ function makeView(ctx, t) {
|
|
|
221
240
|
"div",
|
|
222
241
|
{ className: "lc-legend" },
|
|
223
242
|
props.parts.map((p) => {
|
|
243
|
+
const on = props.hoverKey !== void 0 && props.hoverKey === p.key;
|
|
224
244
|
return h(
|
|
225
245
|
"span",
|
|
226
|
-
{
|
|
246
|
+
{
|
|
247
|
+
key: p.key,
|
|
248
|
+
className: "lc-chip" + (on ? " lc-chip-on" : ""),
|
|
249
|
+
onMouseEnter: () => {
|
|
250
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
251
|
+
},
|
|
252
|
+
onMouseLeave: () => {
|
|
253
|
+
if (props.onHoverKey !== void 0) props.onHoverKey(null);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
227
256
|
h("i", { style: { background: p.color } }),
|
|
228
257
|
catLabel(p.key) + " " + fmt(p.value),
|
|
229
258
|
total > 0 ? h("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
@@ -257,17 +286,31 @@ function makeView(ctx, t) {
|
|
|
257
286
|
const groups = [];
|
|
258
287
|
for (const req of requests) {
|
|
259
288
|
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 };
|
|
289
|
+
if (grp === null || grp.turn !== (req.turn ?? 0)) {
|
|
290
|
+
grp = { turn: req.turn ?? 0, count: 0, span: 0, agg: req.stepCount !== void 0 };
|
|
262
291
|
groups.push(grp);
|
|
263
292
|
}
|
|
264
293
|
grp.count++;
|
|
294
|
+
grp.span += req.stepCount ?? 1;
|
|
265
295
|
}
|
|
266
296
|
const scrollRef = React.useRef(null);
|
|
267
|
-
React.
|
|
297
|
+
const scrolledOnce = React.useRef(false);
|
|
298
|
+
const [edges, setEdges] = React.useState({ left: false, right: false });
|
|
299
|
+
const updateEdges = (el) => {
|
|
300
|
+
const left = el.scrollLeft > 4;
|
|
301
|
+
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
|
|
302
|
+
setEdges((prev) => prev.left === left && prev.right === right ? prev : { left, right });
|
|
303
|
+
};
|
|
304
|
+
React.useLayoutEffect(() => {
|
|
268
305
|
const el = scrollRef.current;
|
|
269
306
|
if (el === null) return;
|
|
270
|
-
if (
|
|
307
|
+
if (!scrolledOnce.current) {
|
|
308
|
+
scrolledOnce.current = true;
|
|
309
|
+
el.scrollLeft = el.scrollWidth;
|
|
310
|
+
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) {
|
|
311
|
+
el.scrollLeft = el.scrollWidth;
|
|
312
|
+
}
|
|
313
|
+
updateEdges(el);
|
|
271
314
|
});
|
|
272
315
|
return h(
|
|
273
316
|
"div",
|
|
@@ -281,7 +324,16 @@ function makeView(ctx, t) {
|
|
|
281
324
|
),
|
|
282
325
|
h(
|
|
283
326
|
"div",
|
|
284
|
-
{
|
|
327
|
+
{
|
|
328
|
+
className: "lc-chart-scroll",
|
|
329
|
+
ref: scrollRef,
|
|
330
|
+
onScroll: (e) => {
|
|
331
|
+
updateEdges(e.currentTarget);
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
// Edge fades: visible whenever more history sits beyond the viewport,
|
|
335
|
+
// so the horizontal scroll affordance is obvious.
|
|
336
|
+
edges.left ? h("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null,
|
|
285
337
|
// Hovering a bar previews it in the detail below; leaving the plot
|
|
286
338
|
// clears the preview (a pinned selection, if any, takes over again).
|
|
287
339
|
h(
|
|
@@ -295,12 +347,15 @@ function makeView(ctx, t) {
|
|
|
295
347
|
const selected = props.selectedSeq === req.seq;
|
|
296
348
|
const hovered = props.hoveredSeq === req.seq;
|
|
297
349
|
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(" / ");
|
|
350
|
+
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
351
|
return h(
|
|
300
352
|
"div",
|
|
301
353
|
{
|
|
302
354
|
key: req.seq,
|
|
355
|
+
// Uniform column width in BOTH granularities: turn aggregates
|
|
356
|
+
// keep the same fixed width as step bars.
|
|
303
357
|
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
358
|
+
style: { width: BAR_W + "px" },
|
|
304
359
|
title: tip,
|
|
305
360
|
onClick: () => {
|
|
306
361
|
props.onSelect(selected ? null : req.seq);
|
|
@@ -334,11 +389,12 @@ function makeView(ctx, t) {
|
|
|
334
389
|
} },
|
|
335
390
|
groups.map((grp, gi) => {
|
|
336
391
|
const on = props.activeTurn === grp.turn;
|
|
392
|
+
const blockW = grp.agg ? BAR_W : grp.span * (BAR_W + BAR_GAP) - BAR_GAP;
|
|
337
393
|
return h("span", {
|
|
338
394
|
key: "turn-" + gi,
|
|
339
395
|
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
340
396
|
style: {
|
|
341
|
-
width:
|
|
397
|
+
width: blockW + "px",
|
|
342
398
|
background: TURN_COLORS[gi % TURN_COLORS.length]
|
|
343
399
|
},
|
|
344
400
|
title: "T" + grp.turn,
|
|
@@ -347,20 +403,24 @@ function makeView(ctx, t) {
|
|
|
347
403
|
}
|
|
348
404
|
}, "T" + grp.turn);
|
|
349
405
|
})
|
|
350
|
-
)
|
|
406
|
+
),
|
|
407
|
+
edges.right ? h("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null
|
|
351
408
|
)
|
|
352
409
|
);
|
|
353
410
|
}
|
|
354
411
|
function RequestDetail(props) {
|
|
355
412
|
const req = props.request;
|
|
356
413
|
if (!req) return null;
|
|
414
|
+
const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
|
|
415
|
+
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
416
|
return h(
|
|
358
417
|
"div",
|
|
359
418
|
{ className: "lc-detail" },
|
|
360
419
|
h(
|
|
361
420
|
"div",
|
|
362
421
|
{ className: "lc-detail-head" },
|
|
363
|
-
h("b", null,
|
|
422
|
+
h("b", null, head),
|
|
423
|
+
isTurn ? h("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null,
|
|
364
424
|
h("span", null, fmtTime(req.time)),
|
|
365
425
|
h("span", null, tr("detail.estTotal", { n: fmt(req.total) })),
|
|
366
426
|
req.prompt !== void 0 ? h("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt(req.prompt) })) : null,
|
|
@@ -432,6 +492,8 @@ function makeView(ctx, t) {
|
|
|
432
492
|
{ key: n.seq, className: "lc-node" },
|
|
433
493
|
h("i", { style: { background: catColor[n.cat] || "#999" } }),
|
|
434
494
|
h("span", { className: "lc-node-preview", title: text }, text),
|
|
495
|
+
// Timestamp when the host event carried one.
|
|
496
|
+
typeof n.time === "number" ? h("span", { className: "lc-node-time" }, fmtTime(n.time)) : null,
|
|
435
497
|
h("span", { className: "lc-node-tokens" }, fmt(n.tokens))
|
|
436
498
|
);
|
|
437
499
|
})
|
|
@@ -446,6 +508,7 @@ function makeView(ctx, t) {
|
|
|
446
508
|
const [hoverTurn, setHoverTurn] = React.useState(null);
|
|
447
509
|
const [tick, setTick] = React.useState(0);
|
|
448
510
|
const [granularity, setGranularity] = React.useState("step");
|
|
511
|
+
const [hoverCat, setHoverCat] = React.useState(null);
|
|
449
512
|
React.useEffect(() => {
|
|
450
513
|
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
451
514
|
let alive = true;
|
|
@@ -521,10 +584,19 @@ function makeView(ctx, t) {
|
|
|
521
584
|
"div",
|
|
522
585
|
{ className: "lc-overview-num" },
|
|
523
586
|
h("b", null, fmt(current.total)),
|
|
524
|
-
h("span", null, data.contextWindow ? " / " + fmt(data.contextWindow) + " " + tr("overview.ofWindow", { p: windowPct ?? 0 }) : " " + t("overview.estimate"))
|
|
587
|
+
h("span", null, data.contextWindow ? " / " + fmt(data.contextWindow) + " " + tr("overview.ofWindow", { p: windowPct ?? 0 }) : " " + t("overview.estimate")),
|
|
588
|
+
// The provider-reported prompt of the last request is the best
|
|
589
|
+
// ground truth for what the model actually received; the fixed
|
|
590
|
+
// density heuristic can undercount CJK-heavy content, so show the
|
|
591
|
+
// real number alongside the estimate.
|
|
592
|
+
displayRequests.length > 0 && displayRequests[displayRequests.length - 1].prompt !== void 0 ? h(
|
|
593
|
+
"span",
|
|
594
|
+
{ className: "lc-actual" },
|
|
595
|
+
tr("overview.actual", { n: fmt(displayRequests[displayRequests.length - 1].prompt ?? 0) })
|
|
596
|
+
) : null
|
|
525
597
|
),
|
|
526
|
-
h(StackedBar, { parts: partsOf(current), height: 16, max: data.contextWindow }),
|
|
527
|
-
h(Legend, { parts: partsOf(current) }),
|
|
598
|
+
h(StackedBar, { parts: partsOf(current), height: 16, max: data.contextWindow, hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
599
|
+
h(Legend, { parts: partsOf(current), hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
528
600
|
data.toolList && data.toolList.length > 0 ? h(
|
|
529
601
|
"div",
|
|
530
602
|
{ className: "lc-tools" },
|
|
@@ -565,7 +637,12 @@ function makeView(ctx, t) {
|
|
|
565
637
|
"div",
|
|
566
638
|
null,
|
|
567
639
|
h(TrendChart, {
|
|
568
|
-
|
|
640
|
+
// Remount per session: switching sessions re-anchors the chart
|
|
641
|
+
// at the newest bars instead of inheriting stale scroll state.
|
|
642
|
+
key: sessionId,
|
|
643
|
+
// The host caps the log at 160 requests; render them ALL so
|
|
644
|
+
// earlier turns/steps stay reachable via horizontal scroll.
|
|
645
|
+
requests: displayRequests,
|
|
569
646
|
events,
|
|
570
647
|
selectedSeq: pinnedReq ? pinnedReq.seq : null,
|
|
571
648
|
hoveredSeq,
|
|
@@ -620,10 +697,13 @@ const STYLES = [
|
|
|
620
697
|
".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); }",
|
|
621
698
|
".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
699
|
".lc-stacked > div { height: 100%; }",
|
|
700
|
+
".lc-stacked-seg-on { filter: brightness(1.18); }",
|
|
623
701
|
".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
|
|
624
702
|
".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }",
|
|
625
703
|
".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
|
|
626
704
|
".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
|
|
705
|
+
".lc-chip-on { font-weight: 600; }",
|
|
706
|
+
".lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
|
|
627
707
|
".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
|
|
628
708
|
".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
709
|
".lc-chartrow { display: flex; gap: 6px; align-items: stretch; }",
|
|
@@ -632,10 +712,14 @@ const STYLES = [
|
|
|
632
712
|
".lc-axis-top { top: 13px; }",
|
|
633
713
|
".lc-axis-mid { top: 69px; }",
|
|
634
714
|
".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-
|
|
715
|
+
".lc-chart-scroll { position: relative; flex: 1; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: thin; }",
|
|
716
|
+
".lc-chart-scroll::-webkit-scrollbar { height: 8px; }",
|
|
717
|
+
".lc-chart-scroll::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--dsw-alias-label-secondary) 45%, transparent); border-radius: 4px; }",
|
|
718
|
+
".lc-chart-scroll::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary) 70%, transparent); }",
|
|
638
719
|
".lc-chart-scroll::-webkit-scrollbar-track { background: transparent; }",
|
|
720
|
+
".lc-chart-fade { position: absolute; top: 0; bottom: 0; width: 26px; pointer-events: none; z-index: 2; }",
|
|
721
|
+
".lc-chart-fade-l { left: 0; background: linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
722
|
+
".lc-chart-fade-r { right: 0; background: linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent); }",
|
|
639
723
|
".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
724
|
".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
|
|
641
725
|
".lc-grid-top { top: 18px; }",
|
|
@@ -649,12 +733,13 @@ const STYLES = [
|
|
|
649
733
|
".lc-bar-stack > div { width: 100%; }",
|
|
650
734
|
".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
|
|
651
735
|
".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }",
|
|
652
|
-
".lc-turn { flex: none;
|
|
736
|
+
".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
737
|
".lc-turn-on { filter: brightness(1.35); box-shadow: 0 0 0 1px rgba(255,255,255,0.4); }",
|
|
654
738
|
".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
|
|
655
739
|
".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
656
740
|
".lc-detail-head b { color: var(--dsw-alias-label-primary); }",
|
|
657
741
|
".lc-detail-head .lc-actual { color: var(--dsw-alias-state-success-primary); }",
|
|
742
|
+
".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
743
|
".lc-detail-rows { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; }",
|
|
659
744
|
".lc-detail-row { display: flex; align-items: center; gap: 8px; }",
|
|
660
745
|
".lc-detail-label { min-width: 70px; white-space: nowrap; color: var(--dsw-alias-label-secondary); }",
|
|
@@ -675,6 +760,7 @@ const STYLES = [
|
|
|
675
760
|
".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
|
|
676
761
|
".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
|
|
677
762
|
".lc-node-preview { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }",
|
|
763
|
+
".lc-node-time { color: var(--dsw-alias-label-secondary); font-size: 12px; min-width: 54px; text-align: right; }",
|
|
678
764
|
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
679
765
|
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
680
766
|
".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.0",
|
|
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,7 @@
|
|
|
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",
|
|
61
63
|
"typescript": "^7.0.2"
|
|
62
64
|
},
|
|
63
65
|
"packageManager": "pnpm@11.9.0"
|