dsh-context 0.7.2 → 0.8.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 +7 -0
- package/lib/client.js +88 -30
- package/lib/index.js +48 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,10 @@ One stacked bar per model request, finer than per-message. Toggle between **Turn
|
|
|
39
39
|
|
|
40
40
|
Above: a real session that grew to ~563k tokens across 48 turns, then compaction (✂) recycled −535.5k in one step, and the conversation continued from a fresh, small window.
|
|
41
41
|
|
|
42
|
+
In **Step** granularity, hovering any bar shows that single step's context info instantly — its turn/step, timestamp, and estimated vs. provider-reported token counts:
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
42
46
|
### ⚡ Context events — when and why the window changed
|
|
43
47
|
|
|
44
48
|
Every compaction, tool-output prune, skill or plugin context injection, and model switch — each with its token delta, turn/step attribution, and timestamp:
|
|
@@ -49,6 +53,9 @@ Every compaction, tool-output prune, skill or plugin context injection, and mode
|
|
|
49
53
|
|
|
50
54
|
The exact message list the model sees right now, newest first, with a per-message token cost.
|
|
51
55
|
|
|
56
|
+
## Releasing
|
|
57
|
+
|
|
58
|
+
Releases are cut by tagging: `git tag vX.Y.Z && gh release create vX.Y.Z`. A [GitHub Actions workflow](.github/workflows/release.yml) then builds, tests, and publishes the package to npm automatically via [npm Trusted Publishing (OIDC)](https://docs.npmjs.com/trusted-publishers) — no long-lived token needed, provenance included.
|
|
52
59
|
|
|
53
60
|
## Like it?
|
|
54
61
|
|
package/lib/client.js
CHANGED
|
@@ -19,6 +19,7 @@ var DICT_ZH = {
|
|
|
19
19
|
"overview.ofWindow": "tokens\uFF08\u7EA6 {p}%\uFF09",
|
|
20
20
|
"overview.estimate": "tokens\uFF08\u4F30\u7B97\uFF09",
|
|
21
21
|
"overview.free": "\u5269\u4F59\u7A97\u53E3",
|
|
22
|
+
"overview.splitEst": "\xB7 \u5206\u7C7B\u6309\u4F30\u7B97\u6BD4\u4F8B\u5206\u914D",
|
|
22
23
|
"stats.title": "\u4E0A\u4E0B\u6587\u7EDF\u8BA1",
|
|
23
24
|
"stats.hint": "\u7EDF\u8BA1\u5F53\u524D\u4FDD\u7559\u7684\u5386\u53F2\u7A97\u53E3\uFF08\u4E0E\u8D8B\u52BF\u56FE\u4E00\u81F4\uFF09",
|
|
24
25
|
"stats.turns": "\u8F6E\u6B21",
|
|
@@ -88,6 +89,7 @@ var DICT_EN = {
|
|
|
88
89
|
"overview.ofWindow": "tokens (~{p}%)",
|
|
89
90
|
"overview.estimate": "tokens (estimated)",
|
|
90
91
|
"overview.free": "Free window",
|
|
92
|
+
"overview.splitEst": "\xB7 category split is estimated",
|
|
91
93
|
"stats.title": "Context stats",
|
|
92
94
|
"stats.hint": "Over the retained history window (same as the History chart)",
|
|
93
95
|
"stats.turns": "Turns",
|
|
@@ -196,7 +198,14 @@ var STYLES = [
|
|
|
196
198
|
".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
|
|
197
199
|
".lc-grid-top { top: 18px; }",
|
|
198
200
|
".lc-grid-mid { top: 74px; }",
|
|
199
|
-
".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; }",
|
|
201
|
+
".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; transition: opacity 120ms ease; }",
|
|
202
|
+
// Turn-aware dimming: while a turn is focused, bars OUTSIDE the active
|
|
203
|
+
// turn fade to 35% and the whole current turn stays fully opaque.
|
|
204
|
+
".lc-chart-dim .lc-bar { opacity: 0.35; }",
|
|
205
|
+
".lc-chart-dim .lc-bar-in-turn { opacity: 1; }",
|
|
206
|
+
".lc-chart-dim .lc-turn { opacity: 0.35; }",
|
|
207
|
+
".lc-chart-dim .lc-turn-on { opacity: 1; }",
|
|
208
|
+
".lc-chart-tip { position: absolute; top: 0; 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; }",
|
|
200
209
|
".lc-bar:hover { background: var(--dsw-alias-bg-layer-2); }",
|
|
201
210
|
".lc-bar-selected { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
202
211
|
".lc-bar-hovered { outline: 1px dashed var(--dsw-alias-brand-primary); outline-offset: 1px; }",
|
|
@@ -205,7 +214,7 @@ var STYLES = [
|
|
|
205
214
|
".lc-bar-stack > div { width: 100%; }",
|
|
206
215
|
".lc-bar-marker { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--dsw-alias-state-warn-primary); }",
|
|
207
216
|
".lc-turns { display: flex; gap: 2px; width: max-content; min-width: 100%; margin-top: 4px; }",
|
|
208
|
-
".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; }",
|
|
217
|
+
".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, opacity 120ms; }",
|
|
209
218
|
".lc-turn-on { filter: brightness(1.35); box-shadow: 0 0 0 1px rgba(255,255,255,0.4); }",
|
|
210
219
|
".lc-detail { margin-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 12px; }",
|
|
211
220
|
".lc-detail-head { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 8px; color: var(--dsw-alias-label-secondary); }",
|
|
@@ -269,6 +278,14 @@ function partsOf(breakdown) {
|
|
|
269
278
|
return { key: c.key, color: c.color, value: breakdown[c.key] || 0 };
|
|
270
279
|
});
|
|
271
280
|
}
|
|
281
|
+
function anchoredParts(parts, target) {
|
|
282
|
+
if (target === null || target <= 0) return parts;
|
|
283
|
+
let total = 0;
|
|
284
|
+
for (const p of parts) total += p.value;
|
|
285
|
+
if (total <= 0 || total === target) return parts;
|
|
286
|
+
const scale = target / total;
|
|
287
|
+
return parts.map((p) => ({ ...p, value: Math.round(p.value * scale) }));
|
|
288
|
+
}
|
|
272
289
|
|
|
273
290
|
// src/client/format.ts
|
|
274
291
|
function fmt(n) {
|
|
@@ -433,7 +450,7 @@ function makeRequestDetail(kit, StackedBar) {
|
|
|
433
450
|
{ className: "lc-bar-track" },
|
|
434
451
|
h("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })
|
|
435
452
|
),
|
|
436
|
-
h("span", { className: "lc-detail-num" }, fmt3(v)),
|
|
453
|
+
h("span", { className: "lc-detail-num" }, "\u2248" + fmt3(v)),
|
|
437
454
|
h("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : "")
|
|
438
455
|
);
|
|
439
456
|
})
|
|
@@ -524,7 +541,7 @@ function makeStackedBar(kit) {
|
|
|
524
541
|
const pct = scale > 0 ? p.value / scale * 100 : 0;
|
|
525
542
|
if (p.key === props.hoverKey && p.value > 0) {
|
|
526
543
|
tip = {
|
|
527
|
-
text: catLabel(p.key) + " " + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
|
|
544
|
+
text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%)",
|
|
528
545
|
leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
|
|
529
546
|
};
|
|
530
547
|
break;
|
|
@@ -593,7 +610,7 @@ function makeLegend(kit) {
|
|
|
593
610
|
}
|
|
594
611
|
},
|
|
595
612
|
h("i", { style: { background: p.color } }),
|
|
596
|
-
catLabel(p.key) + " " + fmt3(p.value),
|
|
613
|
+
catLabel(p.key) + " \u2248" + fmt3(p.value),
|
|
597
614
|
total > 0 ? h("em", null, Math.round(p.value / total * 100) + "%") : null
|
|
598
615
|
);
|
|
599
616
|
})
|
|
@@ -639,8 +656,13 @@ function makeTrendChart(kit) {
|
|
|
639
656
|
return function TrendChart(props) {
|
|
640
657
|
const requests = props.requests;
|
|
641
658
|
const markers = props.markers;
|
|
659
|
+
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
660
|
+
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
642
661
|
let maxTotal = 1;
|
|
643
|
-
for (const req of requests)
|
|
662
|
+
for (const req of requests) {
|
|
663
|
+
const bt = barTotalOf(req);
|
|
664
|
+
if (bt > maxTotal) maxTotal = bt;
|
|
665
|
+
}
|
|
644
666
|
const groups = [];
|
|
645
667
|
for (const req of requests) {
|
|
646
668
|
let grp = groups.length > 0 ? groups[groups.length - 1] : null;
|
|
@@ -679,6 +701,12 @@ function makeTrendChart(kit) {
|
|
|
679
701
|
}
|
|
680
702
|
updateEdges(el);
|
|
681
703
|
});
|
|
704
|
+
const tipOf = (req) => {
|
|
705
|
+
const head = 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 });
|
|
706
|
+
return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + tr("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + tr("tip.actual", { n: fmt3(req.prompt) }) : "");
|
|
707
|
+
};
|
|
708
|
+
const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
|
|
709
|
+
const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
|
|
682
710
|
return h(
|
|
683
711
|
"div",
|
|
684
712
|
{ className: "lc-chartrow" },
|
|
@@ -692,7 +720,9 @@ function makeTrendChart(kit) {
|
|
|
692
720
|
h(
|
|
693
721
|
"div",
|
|
694
722
|
{
|
|
695
|
-
|
|
723
|
+
// Turn-aware dim scope: while a turn is focused (bar or strip hover),
|
|
724
|
+
// bars and strip blocks OUTSIDE the active turn fade to 35%.
|
|
725
|
+
className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
|
|
696
726
|
ref: scrollRef,
|
|
697
727
|
onScroll: (e) => {
|
|
698
728
|
updateEdges(e.currentTarget);
|
|
@@ -705,9 +735,12 @@ function makeTrendChart(kit) {
|
|
|
705
735
|
// clears the preview (a pinned selection, if any, takes over again).
|
|
706
736
|
h(
|
|
707
737
|
"div",
|
|
708
|
-
{
|
|
709
|
-
|
|
710
|
-
|
|
738
|
+
{
|
|
739
|
+
className: "lc-chart",
|
|
740
|
+
onMouseLeave: () => {
|
|
741
|
+
props.onHover(null);
|
|
742
|
+
}
|
|
743
|
+
},
|
|
711
744
|
h("div", { className: "lc-grid lc-grid-top" }),
|
|
712
745
|
h("div", { className: "lc-grid lc-grid-mid" }),
|
|
713
746
|
requests.map((req, i) => {
|
|
@@ -716,7 +749,6 @@ function makeTrendChart(kit) {
|
|
|
716
749
|
const selected = props.selectedSeq === req.seq;
|
|
717
750
|
const hovered = props.hoveredSeq === req.seq;
|
|
718
751
|
const inTurn = props.activeTurn !== null && (req.turn ?? 0) === props.activeTurn;
|
|
719
|
-
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 " + fmtTime2(req.time) + "\n" + tr("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? tr("tip.actual", { n: fmt3(req.prompt) }) : "") + "\n" + CATS.map((c) => catLabel(c.key) + " " + fmt3(req[c.key] || 0)).join(" / ");
|
|
720
752
|
return h(
|
|
721
753
|
"div",
|
|
722
754
|
{
|
|
@@ -725,7 +757,6 @@ function makeTrendChart(kit) {
|
|
|
725
757
|
// keep the same fixed width as step bars.
|
|
726
758
|
className: "lc-bar" + (selected ? " lc-bar-selected" : "") + (hovered ? " lc-bar-hovered" : "") + (inTurn ? " lc-bar-in-turn" : ""),
|
|
727
759
|
style: { width: BAR_W + "px" },
|
|
728
|
-
title: tip,
|
|
729
760
|
onClick: () => {
|
|
730
761
|
props.onSelect(selected ? null : req.seq);
|
|
731
762
|
},
|
|
@@ -743,7 +774,7 @@ function makeTrendChart(kit) {
|
|
|
743
774
|
"div",
|
|
744
775
|
{ className: "lc-bar-stack" },
|
|
745
776
|
CATS.map((c) => {
|
|
746
|
-
const v = req[c.key] || 0;
|
|
777
|
+
const v = (req[c.key] || 0) * anchorOf(req);
|
|
747
778
|
if (!v) return null;
|
|
748
779
|
return h("div", { key: c.key, style: { height: Math.max(1, Math.round(v / maxTotal * CHART_H)) + "px", background: c.color } });
|
|
749
780
|
})
|
|
@@ -751,6 +782,12 @@ function makeTrendChart(kit) {
|
|
|
751
782
|
);
|
|
752
783
|
})
|
|
753
784
|
),
|
|
785
|
+
// Instant hover tooltip, glued to its bar's column (it lives in the
|
|
786
|
+
// scrolling content, so it follows the bar while the chart scrolls).
|
|
787
|
+
hoveredReq !== null ? h("div", {
|
|
788
|
+
className: "lc-chart-tip",
|
|
789
|
+
style: { left: hoveredIdx * (BAR_W + BAR_GAP) + BAR_W / 2 + "px" }
|
|
790
|
+
}, tipOf(hoveredReq)) : null,
|
|
754
791
|
// Turn strip: one COLOR BLOCK per turn, spanning exactly its bars'
|
|
755
792
|
// columns, so the partition reads at a glance and lines up with the
|
|
756
793
|
// steps above. Hovering a block highlights that turn's bars in the
|
|
@@ -785,6 +822,7 @@ function makeTrendChart(kit) {
|
|
|
785
822
|
}
|
|
786
823
|
|
|
787
824
|
// src/client/components/contextView.ts
|
|
825
|
+
var viewScroll = /* @__PURE__ */ new Map();
|
|
788
826
|
function makeContextView(ctx, kit) {
|
|
789
827
|
const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
|
|
790
828
|
const StackedBar = makeStackedBar(kit);
|
|
@@ -809,6 +847,26 @@ function makeContextView(ctx, kit) {
|
|
|
809
847
|
React.useEffect(() => {
|
|
810
848
|
dataRef.current = data;
|
|
811
849
|
}, [data]);
|
|
850
|
+
const rootRef = React.useRef(null);
|
|
851
|
+
const scrollerRef = React.useRef(null);
|
|
852
|
+
const restoredRef = React.useRef(null);
|
|
853
|
+
React.useLayoutEffect(() => {
|
|
854
|
+
if (typeof sessionId !== "string" || sessionId === "" || data === null) return;
|
|
855
|
+
if (restoredRef.current === sessionId) return;
|
|
856
|
+
restoredRef.current = sessionId;
|
|
857
|
+
const scroller = rootRef.current !== null ? rootRef.current.closest("[data-conversation-scroll]") : null;
|
|
858
|
+
if (scroller === null) return;
|
|
859
|
+
scrollerRef.current = scroller;
|
|
860
|
+
scroller.scrollTop = viewScroll.get(sessionId) ?? 0;
|
|
861
|
+
}, [sessionId, data]);
|
|
862
|
+
React.useLayoutEffect(() => {
|
|
863
|
+
return () => {
|
|
864
|
+
if (typeof sessionId !== "string" || sessionId === "") return;
|
|
865
|
+
const scroller = scrollerRef.current;
|
|
866
|
+
if (scroller === null) return;
|
|
867
|
+
viewScroll.set(sessionId, scroller.scrollTop);
|
|
868
|
+
};
|
|
869
|
+
}, [sessionId]);
|
|
812
870
|
React.useEffect(() => {
|
|
813
871
|
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
814
872
|
let alive = true;
|
|
@@ -850,10 +908,10 @@ function makeContextView(ctx, kit) {
|
|
|
850
908
|
}, []);
|
|
851
909
|
void tick;
|
|
852
910
|
if (error) {
|
|
853
|
-
return h("div", { className: "lc-root" }, h("div", { className: "lc-empty" }, t("error") + error));
|
|
911
|
+
return h("div", { className: "lc-root", ref: rootRef }, h("div", { className: "lc-empty" }, t("error") + error));
|
|
854
912
|
}
|
|
855
913
|
if (!data) {
|
|
856
|
-
return h("div", { className: "lc-root" }, h("div", { className: "lc-empty" }, t("loading")));
|
|
914
|
+
return h("div", { className: "lc-root", ref: rootRef }, h("div", { className: "lc-empty" }, t("loading")));
|
|
857
915
|
}
|
|
858
916
|
const current = data.current;
|
|
859
917
|
const requests = data.requests || [];
|
|
@@ -880,10 +938,18 @@ function makeContextView(ctx, kit) {
|
|
|
880
938
|
break;
|
|
881
939
|
}
|
|
882
940
|
}
|
|
883
|
-
const
|
|
941
|
+
const occ = data.occupancy;
|
|
942
|
+
const projected = occ !== void 0 && typeof occ.projectedTokens === "number" ? occ.projectedTokens : void 0;
|
|
943
|
+
const lastReq = displayRequests.length > 0 ? displayRequests[displayRequests.length - 1] : null;
|
|
944
|
+
const derived = lastReq !== null && typeof lastReq.prompt === "number" ? lastReq.prompt + (current.total - lastReq.total) : void 0;
|
|
945
|
+
const occupancyTokens = projected ?? derived ?? null;
|
|
946
|
+
const occupancyWindow = occ !== void 0 && typeof occ.contextWindow === "number" ? occ.contextWindow : data.contextWindow;
|
|
947
|
+
const headline = occupancyTokens ?? current.total;
|
|
948
|
+
const headlinePct = occupancyWindow ? Math.min(100, Math.round(headline / occupancyWindow * 100)) : null;
|
|
949
|
+
const parts = anchoredParts(partsOf(current), occupancyTokens !== null && headline > 0 ? headline : null);
|
|
884
950
|
return h(
|
|
885
951
|
"div",
|
|
886
|
-
{ className: "lc-root" },
|
|
952
|
+
{ className: "lc-root", ref: rootRef },
|
|
887
953
|
// ---- session context stats (over the retained window) ----
|
|
888
954
|
h(StatsBoard, { requests, events }),
|
|
889
955
|
// ---- overview ----
|
|
@@ -903,20 +969,12 @@ function makeContextView(ctx, kit) {
|
|
|
903
969
|
h(
|
|
904
970
|
"div",
|
|
905
971
|
{ className: "lc-overview-num" },
|
|
906
|
-
h("b", null, fmt3(
|
|
907
|
-
h("span", null,
|
|
908
|
-
|
|
909
|
-
// ground truth for what the model actually received; the fixed
|
|
910
|
-
// density heuristic can undercount CJK-heavy content, so show the
|
|
911
|
-
// real number alongside the estimate.
|
|
912
|
-
displayRequests.length > 0 && displayRequests[displayRequests.length - 1].prompt !== void 0 ? h(
|
|
913
|
-
"span",
|
|
914
|
-
{ className: "lc-actual" },
|
|
915
|
-
tr("overview.actual", { n: fmt3(displayRequests[displayRequests.length - 1].prompt ?? 0) })
|
|
916
|
-
) : null
|
|
972
|
+
h("b", null, fmt3(headline)),
|
|
973
|
+
h("span", null, occupancyWindow ? " / " + fmt3(occupancyWindow) + " " + tr("overview.ofWindow", { p: headlinePct ?? 0 }) : " " + t("overview.estimate")),
|
|
974
|
+
occupancyTokens !== null ? h("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null
|
|
917
975
|
),
|
|
918
|
-
h(StackedBar, { parts
|
|
919
|
-
h(Legend, { parts
|
|
976
|
+
h(StackedBar, { parts, height: 16, max: occupancyWindow, hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
977
|
+
h(Legend, { parts, hoverKey: hoverCat, onHoverKey: setHoverCat }),
|
|
920
978
|
data.toolList && data.toolList.length > 0 ? h(
|
|
921
979
|
"div",
|
|
922
980
|
{ className: "lc-tools" },
|
package/lib/index.js
CHANGED
|
@@ -94,6 +94,9 @@ function createFold() {
|
|
|
94
94
|
provider: void 0,
|
|
95
95
|
lastModel: void 0,
|
|
96
96
|
contextWindow: void 0,
|
|
97
|
+
pressureTokens: void 0,
|
|
98
|
+
sampledSurfaceTokens: void 0,
|
|
99
|
+
occupancyWindow: void 0,
|
|
97
100
|
requests: [],
|
|
98
101
|
// one entry per answered model call
|
|
99
102
|
events: [],
|
|
@@ -147,8 +150,22 @@ function applySurface(st, ev, type, data, message) {
|
|
|
147
150
|
const utext = firstText(message?.content);
|
|
148
151
|
if (utext !== "") node.text = utext;
|
|
149
152
|
}
|
|
153
|
+
const shadowedSeqs = st.pendingShadowedSeqs;
|
|
154
|
+
st.pendingShadowedSeqs = void 0;
|
|
150
155
|
const op = ev.surfaceOp;
|
|
151
156
|
if (op !== null && typeof op === "object" && op.op === "replace") {
|
|
157
|
+
if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
|
|
158
|
+
const shadowed = new Set(shadowedSeqs);
|
|
159
|
+
const kept = [];
|
|
160
|
+
for (const n of st.surface) {
|
|
161
|
+
if (shadowed.has(n.seq)) st.sums[n.cat] -= n.tokens;
|
|
162
|
+
else kept.push(n);
|
|
163
|
+
}
|
|
164
|
+
st.surface = kept;
|
|
165
|
+
st.sums[cat] += node.tokens;
|
|
166
|
+
st.surface.push(node);
|
|
167
|
+
return node;
|
|
168
|
+
}
|
|
152
169
|
let si = -1;
|
|
153
170
|
let ei = -1;
|
|
154
171
|
for (let i = 0; i < st.surface.length; i++) {
|
|
@@ -169,6 +186,16 @@ function applySurface(st, ev, type, data, message) {
|
|
|
169
186
|
st.sums[cat] += node.tokens;
|
|
170
187
|
return node;
|
|
171
188
|
}
|
|
189
|
+
function pressureOf(usage) {
|
|
190
|
+
if (usage === void 0 || typeof usage.inputTokens !== "number") return void 0;
|
|
191
|
+
return usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
|
|
192
|
+
}
|
|
193
|
+
function sampleUsage(st, usage) {
|
|
194
|
+
const pressureTokens = pressureOf(usage);
|
|
195
|
+
if (pressureTokens === void 0) return;
|
|
196
|
+
st.pressureTokens = pressureTokens;
|
|
197
|
+
st.sampledSurfaceTokens = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
|
|
198
|
+
}
|
|
172
199
|
function foldInto(st, events) {
|
|
173
200
|
for (let e = st.n; e < events.length; e++) {
|
|
174
201
|
const ev = events[e];
|
|
@@ -196,6 +223,7 @@ function foldInto(st, events) {
|
|
|
196
223
|
if (data && typeof data.contextWindow === "number") st.contextWindow = data.contextWindow;
|
|
197
224
|
if (data && typeof data.model === "string") st.model = data.model;
|
|
198
225
|
if (data && typeof data.provider === "string") st.provider = data.provider;
|
|
226
|
+
st.occupancyWindow = data && typeof data.contextWindow === "number" ? data.contextWindow : void 0;
|
|
199
227
|
break;
|
|
200
228
|
case "tool/call":
|
|
201
229
|
if (data && data.callId !== void 0 && typeof data.name === "string") st.callNames[String(data.callId)] = data.name;
|
|
@@ -227,8 +255,14 @@ function foldInto(st, events) {
|
|
|
227
255
|
applySurface(st, ev, ev.type, data, toolMsg);
|
|
228
256
|
break;
|
|
229
257
|
}
|
|
258
|
+
case "assistant/chunk": {
|
|
259
|
+
const chunk = data?.chunk;
|
|
260
|
+
if (chunk !== void 0 && chunk.type === "usage") sampleUsage(st, chunk.usage);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
230
263
|
case "assistant/message": {
|
|
231
264
|
const usage = data?.usage;
|
|
265
|
+
sampleUsage(st, usage);
|
|
232
266
|
const total = st.systemTokens + st.toolsTokens + st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
|
|
233
267
|
const record = {
|
|
234
268
|
turn: data && typeof data.turn === "number" ? data.turn : void 0,
|
|
@@ -253,6 +287,9 @@ function foldInto(st, events) {
|
|
|
253
287
|
break;
|
|
254
288
|
}
|
|
255
289
|
case "compaction/summary":
|
|
290
|
+
if (data && Array.isArray(data.shadowedSeqs)) {
|
|
291
|
+
st.pendingShadowedSeqs = data.shadowedSeqs.filter((s) => typeof s === "number");
|
|
292
|
+
}
|
|
256
293
|
st.events.push({
|
|
257
294
|
seq: ev.seq,
|
|
258
295
|
time: ev.time,
|
|
@@ -262,6 +299,9 @@ function foldInto(st, events) {
|
|
|
262
299
|
});
|
|
263
300
|
break;
|
|
264
301
|
case "compaction/prune":
|
|
302
|
+
if (data && Array.isArray(data.shadowedSeqs)) {
|
|
303
|
+
st.pendingShadowedSeqs = data.shadowedSeqs.filter((s) => typeof s === "number");
|
|
304
|
+
}
|
|
265
305
|
st.events.push({
|
|
266
306
|
seq: ev.seq,
|
|
267
307
|
time: ev.time,
|
|
@@ -284,6 +324,7 @@ function foldInto(st, events) {
|
|
|
284
324
|
// src/host/snapshot.ts
|
|
285
325
|
function buildResult(st) {
|
|
286
326
|
const surfaceTotal = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
|
|
327
|
+
const projectedTokens = st.pressureTokens !== void 0 && st.sampledSurfaceTokens !== void 0 ? Math.max(0, st.pressureTokens + surfaceTotal - st.sampledSurfaceTokens) : void 0;
|
|
287
328
|
const result = {
|
|
288
329
|
ok: true,
|
|
289
330
|
model: st.model,
|
|
@@ -298,6 +339,13 @@ function buildResult(st) {
|
|
|
298
339
|
tool: st.sums.tool,
|
|
299
340
|
total: surfaceTotal + st.systemTokens + st.toolsTokens
|
|
300
341
|
},
|
|
342
|
+
occupancy: {
|
|
343
|
+
...st.pressureTokens === void 0 ? {} : { pressureTokens: st.pressureTokens },
|
|
344
|
+
surfaceTokens: surfaceTotal,
|
|
345
|
+
...st.sampledSurfaceTokens === void 0 ? {} : { sampledSurfaceTokens: st.sampledSurfaceTokens },
|
|
346
|
+
...projectedTokens === void 0 ? {} : { projectedTokens },
|
|
347
|
+
...st.occupancyWindow === void 0 ? {} : { contextWindow: st.occupancyWindow }
|
|
348
|
+
},
|
|
301
349
|
toolList: st.toolList,
|
|
302
350
|
requests: st.requests,
|
|
303
351
|
events: st.events,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Context insight panel for DeepSeek Harness: see 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": {
|