dsh-context 0.21.0 → 0.22.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 +8 -0
- package/lib/client.js +300 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,14 @@ Six collapsible category sections (system prompt, tool schemas, user messages, i
|
|
|
86
86
|
- **Linked with the history chart** — hover any bar in the History card and the browser previews that step instantly; leave the chart and it returns to your own pick. Keep a category open while scrubbing to compare one category across steps.
|
|
87
87
|
- **Honest about coverage** — steps before a compaction are reconstructed from the removed-message archive, and the card says so when a step's makeup is only approximate. Elements older than the loaded chat window page older history in automatically when you expand them, and live injections (AGENTS.md, session-start context, …) are always listed — never a token sum without its items.
|
|
88
88
|
|
|
89
|
+
### 🖼 Multimodal — image attachments in full view (DeepSeek Harness 0.1.1+)
|
|
90
|
+
|
|
91
|
+
Fully adapted to DeepSeek Harness 0.1.1's multimodal pipeline and the vision capability of **DeepSeek-V4-Flash-Vision-Exp**. A user message carrying images expands into a card layout — prose in the text card (with the usual raw/Markdown toggle), each image attachment as a thumbnail card with its name, normalized dimensions (plus the pre-normalization size when 0.1.1's image pipeline downscaled it) and stored size, and anything unrecognized as raw content:
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
Images load through the harness's own session-authorized loader — the same one the chat history uses — and degrade to metadata-only cards when it is unavailable. Image blocks in assistant messages and tool results (e.g. `read_image` output) now render too, instead of being silently dropped.
|
|
96
|
+
|
|
89
97
|
## Like it?
|
|
90
98
|
|
|
91
99
|
If `dsh-context` helped you understand what your agent is carrying around, a ⭐ on [GitHub](https://github.com/bowenliang123/dsh-context) is much appreciated — and issues/PRs are welcome!
|
package/lib/client.js
CHANGED
|
@@ -67,6 +67,7 @@ var DICT_ZH = {
|
|
|
67
67
|
"nodes.empty": "\u5F53\u524D\u6CA1\u6709\u6A21\u578B\u53EF\u89C1\u7684\u6D88\u606F",
|
|
68
68
|
"loading": "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u2026",
|
|
69
69
|
"error": "\u4E0A\u4E0B\u6587\u6570\u636E\u8BFB\u53D6\u5931\u8D25\uFF1A",
|
|
70
|
+
"error.retry": "\u91CD\u8BD5",
|
|
70
71
|
"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",
|
|
71
72
|
"tip.step": "\u7B2C {t} \u8F6E \xB7 \u7B2C{s}\u6B65",
|
|
72
73
|
"tip.turn": "\u7B2C {t} \u8F6E \xB7 \u5171 {n} \u6B65",
|
|
@@ -115,7 +116,22 @@ var DICT_ZH = {
|
|
|
115
116
|
"rich.raw": "\u539F\u6587",
|
|
116
117
|
"rich.md": "Markdown",
|
|
117
118
|
"rich.toMd": "\u6309 Markdown \u6E32\u67D3\u67E5\u770B",
|
|
118
|
-
"rich.toRaw": "\u67E5\u770B\u539F\u59CB\u6587\u672C"
|
|
119
|
+
"rich.toRaw": "\u67E5\u770B\u539F\u59CB\u6587\u672C",
|
|
120
|
+
// Assistant-message block titles inside the detail card: the reasoning
|
|
121
|
+
// trace and the response each carry a small label (Reasoning / Response,
|
|
122
|
+
// mirroring the harness's own block + locale vocabulary).
|
|
123
|
+
"block.thinking": "\u601D\u8003",
|
|
124
|
+
"block.answer": "\u56DE\u7B54",
|
|
125
|
+
// Attachment cards in a message detail: image refs render as thumbnail
|
|
126
|
+
// cards (name + normalized dims + stored size), anything unrecognized
|
|
127
|
+
// falls into the generic raw-JSON card.
|
|
128
|
+
"attach.images": "\u56FE\u7247\u9644\u4EF6",
|
|
129
|
+
"attach.other": "\u5176\u4ED6\u5185\u5BB9",
|
|
130
|
+
"attach.image": "\u56FE\u7247",
|
|
131
|
+
"attach.open": "\u67E5\u770B\u539F\u56FE",
|
|
132
|
+
"attach.loading": "\u2026",
|
|
133
|
+
"attach.loadFailed": "\u52A0\u8F7D\u5931\u8D25 \xB7 \u70B9\u51FB\u91CD\u8BD5",
|
|
134
|
+
"attach.orig": "\u539F\u56FE {d}"
|
|
119
135
|
};
|
|
120
136
|
var DICT_EN = {
|
|
121
137
|
"tab": "Context",
|
|
@@ -177,6 +193,7 @@ var DICT_EN = {
|
|
|
177
193
|
"nodes.empty": "No model-visible messages right now",
|
|
178
194
|
"loading": "Reading the session log\u2026",
|
|
179
195
|
"error": "Failed to read context data: ",
|
|
196
|
+
"error.retry": "Retry",
|
|
180
197
|
"footer": "Estimate: same fixed-density heuristic as dsh\u2019s built-in tokenMeter (~4 chars \u2248 1 token); \u201Cactual\u201D is provider-reported usage.",
|
|
181
198
|
"tip.step": "Turn {t} \xB7 Step {s}",
|
|
182
199
|
"tip.turn": "Turn {t} \xB7 {n} steps",
|
|
@@ -225,7 +242,22 @@ var DICT_EN = {
|
|
|
225
242
|
"rich.raw": "Raw",
|
|
226
243
|
"rich.md": "Markdown",
|
|
227
244
|
"rich.toMd": "View as Markdown",
|
|
228
|
-
"rich.toRaw": "View Raw Text"
|
|
245
|
+
"rich.toRaw": "View Raw Text",
|
|
246
|
+
// Assistant-message block titles inside the detail card: the reasoning
|
|
247
|
+
// trace and the response each carry a small label (Reasoning / Response,
|
|
248
|
+
// mirroring the harness's own block + locale vocabulary).
|
|
249
|
+
"block.thinking": "Reasoning",
|
|
250
|
+
"block.answer": "Response",
|
|
251
|
+
// Attachment cards in a message detail: image refs render as thumbnail
|
|
252
|
+
// cards (name + normalized dims + stored size), anything unrecognized
|
|
253
|
+
// falls into the generic raw-JSON card.
|
|
254
|
+
"attach.images": "Images",
|
|
255
|
+
"attach.other": "Other content",
|
|
256
|
+
"attach.image": "Image",
|
|
257
|
+
"attach.open": "Open full image",
|
|
258
|
+
"attach.loading": "\u2026",
|
|
259
|
+
"attach.loadFailed": "Load failed \xB7 click to retry",
|
|
260
|
+
"attach.orig": "original {d}"
|
|
229
261
|
};
|
|
230
262
|
|
|
231
263
|
// src/client/modalStore.ts
|
|
@@ -338,12 +370,60 @@ function asRecord(value) {
|
|
|
338
370
|
if (value === null || value === void 0 || typeof value !== "object") return null;
|
|
339
371
|
return value;
|
|
340
372
|
}
|
|
341
|
-
|
|
373
|
+
function numOf(value) {
|
|
374
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
375
|
+
}
|
|
376
|
+
function objectsOf(value) {
|
|
377
|
+
if (!Array.isArray(value)) return [];
|
|
378
|
+
return value.filter((v) => v !== null && typeof v === "object");
|
|
379
|
+
}
|
|
380
|
+
function timelineOf(value) {
|
|
381
|
+
const data = asRecord(value);
|
|
382
|
+
if (data === null) return null;
|
|
383
|
+
const current = data.current;
|
|
384
|
+
const numericBreakdown = current !== null && typeof current === "object" && ["system", "tools", "user", "inject", "assistant", "tool", "total"].every((k) => typeof current[k] === "number");
|
|
385
|
+
if (numericBreakdown && Array.isArray(data.requests) && Array.isArray(data.events) && Array.isArray(data.nodes) && Array.isArray(data.archive) && Array.isArray(data.toolList)) {
|
|
386
|
+
return data;
|
|
387
|
+
}
|
|
388
|
+
const safeCurrent = current !== null && typeof current === "object" ? current : {};
|
|
389
|
+
const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
|
|
390
|
+
return {
|
|
391
|
+
ok: true,
|
|
392
|
+
...typeof data.model === "string" ? { model: data.model } : {},
|
|
393
|
+
...typeof data.provider === "string" ? { provider: data.provider } : {},
|
|
394
|
+
...typeof data.contextWindow === "number" ? { contextWindow: data.contextWindow } : {},
|
|
395
|
+
current: {
|
|
396
|
+
system: numOf(safeCurrent.system),
|
|
397
|
+
tools: numOf(safeCurrent.tools),
|
|
398
|
+
user: numOf(safeCurrent.user),
|
|
399
|
+
inject: numOf(safeCurrent.inject),
|
|
400
|
+
assistant: numOf(safeCurrent.assistant),
|
|
401
|
+
tool: numOf(safeCurrent.tool),
|
|
402
|
+
total: numOf(safeCurrent.total)
|
|
403
|
+
},
|
|
404
|
+
toolList: objectsOf(data.toolList),
|
|
405
|
+
requests: objectsOf(data.requests),
|
|
406
|
+
events: objectsOf(data.events),
|
|
407
|
+
nodes: objectsOf(data.nodes),
|
|
408
|
+
droppedNodes: numOf(data.droppedNodes),
|
|
409
|
+
archive: objectsOf(data.archive),
|
|
410
|
+
...cost !== void 0 ? { cost } : {},
|
|
411
|
+
...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
|
|
412
|
+
...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
342
415
|
var contextPressureOf = (value) => asRecord(value);
|
|
343
416
|
var tokenUsageOf = (value) => asRecord(value);
|
|
344
417
|
function headersOf(value) {
|
|
345
418
|
const headers = asRecord(value);
|
|
346
|
-
|
|
419
|
+
if (headers === null || !Array.isArray(headers.headers)) return null;
|
|
420
|
+
for (const h2 of headers.headers) {
|
|
421
|
+
if (h2 === null || typeof h2 !== "object") return null;
|
|
422
|
+
const entry = h2;
|
|
423
|
+
if (!Array.isArray(entry.tools)) return null;
|
|
424
|
+
if (entry.system !== void 0 && typeof entry.system !== "string") return null;
|
|
425
|
+
}
|
|
426
|
+
return headers;
|
|
347
427
|
}
|
|
348
428
|
|
|
349
429
|
// src/client/assemble.ts
|
|
@@ -417,6 +497,96 @@ function makeNodeList(kit) {
|
|
|
417
497
|
};
|
|
418
498
|
}
|
|
419
499
|
|
|
500
|
+
// src/client/format.ts
|
|
501
|
+
function fmt(n) {
|
|
502
|
+
if (n === void 0 || n === null || isNaN(n)) return "\u2014";
|
|
503
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
504
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
|
505
|
+
return String(Math.round(n));
|
|
506
|
+
}
|
|
507
|
+
function fmtBytes(n) {
|
|
508
|
+
if (n === void 0 || n === null || isNaN(n) || n < 0) return "\u2014";
|
|
509
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + " MB";
|
|
510
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + " kB";
|
|
511
|
+
return String(Math.round(n)) + " B";
|
|
512
|
+
}
|
|
513
|
+
function fmtTime(t) {
|
|
514
|
+
const d = new Date(t);
|
|
515
|
+
if (isNaN(d.getTime())) return "\u2014";
|
|
516
|
+
return d.toLocaleTimeString("en-GB", { hour12: false });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/client/components/images.tsx
|
|
520
|
+
function imageRefOf(block) {
|
|
521
|
+
if (block === null || typeof block !== "object") return null;
|
|
522
|
+
const b = block;
|
|
523
|
+
if (b.type !== "image" && b.kind !== "image") return null;
|
|
524
|
+
const a = b.attachment;
|
|
525
|
+
if (a === null || typeof a !== "object") return null;
|
|
526
|
+
const r = a;
|
|
527
|
+
if (typeof r.attachmentId !== "string" || r.attachmentId === "") return null;
|
|
528
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
|
|
529
|
+
const orig = r.originalDimensions !== null && typeof r.originalDimensions === "object" ? r.originalDimensions : void 0;
|
|
530
|
+
const origDims = orig !== void 0 && num(orig.width) !== void 0 && num(orig.height) !== void 0 ? { width: num(orig.width), height: num(orig.height) } : void 0;
|
|
531
|
+
return {
|
|
532
|
+
attachmentId: r.attachmentId,
|
|
533
|
+
...typeof r.name === "string" && r.name !== "" ? { name: r.name } : {},
|
|
534
|
+
...num(r.bytes) !== void 0 ? { bytes: num(r.bytes) } : {},
|
|
535
|
+
...num(r.width) !== void 0 ? { width: num(r.width) } : {},
|
|
536
|
+
...num(r.height) !== void 0 ? { height: num(r.height) } : {},
|
|
537
|
+
...origDims !== void 0 ? { originalDimensions: origDims } : {}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function makeImageCard(kit) {
|
|
541
|
+
const { t } = kit;
|
|
542
|
+
return function ImageCard(props) {
|
|
543
|
+
const { attachment, load } = props;
|
|
544
|
+
const [src, setSrc] = React.useState(null);
|
|
545
|
+
const [error, setError] = React.useState(false);
|
|
546
|
+
const [attempt, setAttempt] = React.useState(0);
|
|
547
|
+
React.useEffect(() => {
|
|
548
|
+
if (load === void 0) return;
|
|
549
|
+
let live = true;
|
|
550
|
+
setError(false);
|
|
551
|
+
setSrc(null);
|
|
552
|
+
void load(attachment).then((url) => {
|
|
553
|
+
if (live) setSrc(url);
|
|
554
|
+
}).catch(() => {
|
|
555
|
+
if (live) setError(true);
|
|
556
|
+
});
|
|
557
|
+
return () => {
|
|
558
|
+
live = false;
|
|
559
|
+
};
|
|
560
|
+
}, [attachment, load, attempt]);
|
|
561
|
+
const name = attachment.name ?? t("attach.image");
|
|
562
|
+
const dims = attachment.width !== void 0 && attachment.height !== void 0 ? attachment.width + "\xD7" + attachment.height : null;
|
|
563
|
+
const orig = attachment.originalDimensions;
|
|
564
|
+
const facts = [];
|
|
565
|
+
if (dims !== null) facts.push(dims);
|
|
566
|
+
if (orig !== void 0) facts.push(t("attach.orig", { d: orig.width + "\xD7" + orig.height }));
|
|
567
|
+
if (attachment.bytes !== void 0) facts.push(fmtBytes(attachment.bytes));
|
|
568
|
+
const open = () => {
|
|
569
|
+
if (src === null) return;
|
|
570
|
+
try {
|
|
571
|
+
window.open(src, "_blank", "noopener");
|
|
572
|
+
} catch {
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-att-item" }, /* @__PURE__ */ React.createElement(
|
|
576
|
+
"button",
|
|
577
|
+
{
|
|
578
|
+
type: "button",
|
|
579
|
+
className: "lc-att-thumb",
|
|
580
|
+
title: error ? t("attach.loadFailed") : t("attach.open"),
|
|
581
|
+
onClick: error ? () => {
|
|
582
|
+
setAttempt((a) => a + 1);
|
|
583
|
+
} : open
|
|
584
|
+
},
|
|
585
|
+
src !== null ? /* @__PURE__ */ React.createElement("img", { src, alt: name }) : /* @__PURE__ */ React.createElement("span", { className: error ? "lc-att-err" : "lc-att-ph" }, error ? "\u26A0" : load === void 0 ? "\u{1F5BC}" : t("attach.loading"))
|
|
586
|
+
), /* @__PURE__ */ React.createElement("div", { className: "lc-att-meta" }, /* @__PURE__ */ React.createElement("span", { className: "lc-att-name", title: name }, name), facts.length > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-att-dims" }, facts.join(" \xB7 ")) : null));
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
420
590
|
// src/client/components/richText.tsx
|
|
421
591
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
422
592
|
function makeRichText(kit) {
|
|
@@ -551,7 +721,7 @@ function ToolSchema(props) {
|
|
|
551
721
|
), jsonOpen ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, schemaJson) : null) : null);
|
|
552
722
|
}
|
|
553
723
|
function RawBlocks(props) {
|
|
554
|
-
const { rich } = props;
|
|
724
|
+
const { rich, img } = props;
|
|
555
725
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, props.blocks.map((b, i) => {
|
|
556
726
|
const blk = b;
|
|
557
727
|
if (blk !== null && typeof blk === "object" && (blk.type === "text" || blk.type === "reasoning") && typeof blk.text === "string") {
|
|
@@ -560,14 +730,16 @@ function RawBlocks(props) {
|
|
|
560
730
|
}
|
|
561
731
|
return /* @__PURE__ */ React.createElement(rich.RichText, { key: i, text: blk.text, mode: props.mode });
|
|
562
732
|
}
|
|
733
|
+
const image = imageRefOf(b);
|
|
734
|
+
if (image !== null) return /* @__PURE__ */ React.createElement(img.Card, { key: i, attachment: image, load: img.load });
|
|
563
735
|
if (blk !== null && typeof blk === "object" && blk.type === "tool-result" && Array.isArray(blk.content)) {
|
|
564
|
-
return /* @__PURE__ */ React.createElement(RawBlocks, { key: i, blocks: blk.content, mode: props.mode, rich });
|
|
736
|
+
return /* @__PURE__ */ React.createElement(RawBlocks, { key: i, blocks: blk.content, mode: props.mode, rich, img });
|
|
565
737
|
}
|
|
566
738
|
return /* @__PURE__ */ React.createElement("pre", { key: i, className: "lc-br-pre lc-br-dim" }, JSON.stringify(b, null, 2));
|
|
567
739
|
}));
|
|
568
740
|
}
|
|
569
741
|
function NodeContent(props) {
|
|
570
|
-
const { node, conv, rich } = props;
|
|
742
|
+
const { node, conv, rich, img } = props;
|
|
571
743
|
const richable = node.cat === "user" || node.cat === "assistant" || node.cat === "inject";
|
|
572
744
|
const wrap = (render) => richable ? /* @__PURE__ */ React.createElement(rich.RichCard, { render }) : /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, render("raw"));
|
|
573
745
|
if (conv === void 0) {
|
|
@@ -581,25 +753,52 @@ function NodeContent(props) {
|
|
|
581
753
|
return wrap((mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, blocks.map((b, i) => {
|
|
582
754
|
const blk = b;
|
|
583
755
|
if (blk.kind === "text" && typeof blk.text === "string") {
|
|
584
|
-
return /* @__PURE__ */ React.createElement(
|
|
756
|
+
return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-blk" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-blk-label" }, props.labels.answer), /* @__PURE__ */ React.createElement(rich.RichText, { text: blk.text, mode }));
|
|
585
757
|
}
|
|
586
758
|
if (blk.kind === "reasoning" && typeof blk.text === "string") {
|
|
587
|
-
return /* @__PURE__ */ React.createElement("
|
|
759
|
+
return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-blk" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-blk-label" }, props.labels.thinking), /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, blk.text));
|
|
588
760
|
}
|
|
589
761
|
if (blk.kind === "tool-call") {
|
|
590
762
|
return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2192 " + String(blk.name ?? "?")), typeof blk.argsRaw === "string" && blk.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, blk.argsRaw) : null);
|
|
591
763
|
}
|
|
764
|
+
const image = imageRefOf(b);
|
|
765
|
+
if (image !== null) return /* @__PURE__ */ React.createElement(img.Card, { key: i, attachment: image, load: img.load });
|
|
592
766
|
return null;
|
|
593
767
|
})));
|
|
594
768
|
}
|
|
595
769
|
if (conv.kind === "tool-result") {
|
|
596
|
-
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, conv.call != null ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2190 " + conv.call.name), conv.call.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, conv.call.argsRaw) : null) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(RawBlocks, { blocks: conv.content, mode: "raw", rich }) : null);
|
|
770
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, conv.call != null ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2190 " + conv.call.name), conv.call.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, conv.call.argsRaw) : null) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(RawBlocks, { blocks: conv.content, mode: "raw", rich, img }) : null);
|
|
597
771
|
}
|
|
598
772
|
if (conv.kind === "compaction") {
|
|
599
773
|
return wrap((mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(rich.RichText, { text: conv.summary, mode }) : null));
|
|
600
774
|
}
|
|
601
775
|
if (Array.isArray(conv.content)) {
|
|
602
|
-
|
|
776
|
+
const blocks = conv.content;
|
|
777
|
+
if (node.cat !== "user") {
|
|
778
|
+
return wrap((mode) => /* @__PURE__ */ React.createElement(RawBlocks, { blocks, mode, rich, img }));
|
|
779
|
+
}
|
|
780
|
+
const texts = [];
|
|
781
|
+
const images = [];
|
|
782
|
+
const others = [];
|
|
783
|
+
for (const b of blocks) {
|
|
784
|
+
const blk = b;
|
|
785
|
+
if (blk !== null && typeof blk === "object" && blk.type === "text" && typeof blk.text === "string") {
|
|
786
|
+
texts.push(blk.text);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const image = imageRefOf(b);
|
|
790
|
+
if (image !== null) {
|
|
791
|
+
images.push(image);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
others.push(b);
|
|
795
|
+
}
|
|
796
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, texts.length > 0 ? (
|
|
797
|
+
// Un-padded RichCard: the outer lc-br-content already owns the
|
|
798
|
+
// body indent, so the text card aligns with the attachment cards
|
|
799
|
+
// instead of sitting one indent deeper.
|
|
800
|
+
/* @__PURE__ */ React.createElement(rich.RichCard, { className: "lc-br-rich-inline", render: (mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, texts.map((text, i) => /* @__PURE__ */ React.createElement(rich.RichText, { key: i, text, mode }))) })
|
|
801
|
+
) : null, images.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", null, props.labels.images), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, images.length)), /* @__PURE__ */ React.createElement("div", { className: "lc-att-grid" }, images.map((a, i) => /* @__PURE__ */ React.createElement(img.Card, { key: a.attachmentId + ":" + i, attachment: a, load: img.load })))) : null, others.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", null, props.labels.other), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, others.length)), others.map((b, i) => /* @__PURE__ */ React.createElement("pre", { key: i, className: "lc-br-pre lc-br-dim" }, JSON.stringify(b, null, 2)))) : null);
|
|
603
802
|
}
|
|
604
803
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint));
|
|
605
804
|
}
|
|
@@ -622,6 +821,7 @@ function makeContextBrowser(kit, StackedBar) {
|
|
|
622
821
|
const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
|
|
623
822
|
const nodeText = makeNodeText(kit);
|
|
624
823
|
const rich = makeRichText(kit);
|
|
824
|
+
const ImageCard = makeImageCard(kit);
|
|
625
825
|
const catColor = {};
|
|
626
826
|
for (const c of CATS) catColor[c.key] = c.color;
|
|
627
827
|
const MAX_AUTO_PAGES = 20;
|
|
@@ -766,6 +966,13 @@ function makeContextBrowser(kit, StackedBar) {
|
|
|
766
966
|
node: n,
|
|
767
967
|
conv: bySeq.get(n.seq),
|
|
768
968
|
rich,
|
|
969
|
+
img: { Card: ImageCard, load: props.loadImage },
|
|
970
|
+
labels: {
|
|
971
|
+
thinking: t("block.thinking"),
|
|
972
|
+
answer: t("block.answer"),
|
|
973
|
+
images: t("attach.images"),
|
|
974
|
+
other: t("attach.other")
|
|
975
|
+
},
|
|
769
976
|
hint: bySeq.get(n.seq) === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
|
|
770
977
|
}
|
|
771
978
|
)
|
|
@@ -982,6 +1189,34 @@ function makeCurrentComposition(kit, StackedBar, Legend) {
|
|
|
982
1189
|
};
|
|
983
1190
|
}
|
|
984
1191
|
|
|
1192
|
+
// src/client/components/errorBoundary.tsx
|
|
1193
|
+
function makeErrorBoundary(t) {
|
|
1194
|
+
return class ErrorBoundary extends React.Component {
|
|
1195
|
+
constructor(props) {
|
|
1196
|
+
super(props);
|
|
1197
|
+
this.state = { error: null };
|
|
1198
|
+
}
|
|
1199
|
+
static getDerivedStateFromError(error) {
|
|
1200
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
1201
|
+
}
|
|
1202
|
+
render() {
|
|
1203
|
+
const error = this.state.error;
|
|
1204
|
+
if (error === null) return this.props.children;
|
|
1205
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-root" }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty lc-error" }, /* @__PURE__ */ React.createElement("span", null, t("error")), /* @__PURE__ */ React.createElement("code", { className: "lc-error-msg" }, error.message), /* @__PURE__ */ React.createElement(
|
|
1206
|
+
"button",
|
|
1207
|
+
{
|
|
1208
|
+
type: "button",
|
|
1209
|
+
className: "lc-error-retry",
|
|
1210
|
+
onClick: () => {
|
|
1211
|
+
this.setState({ error: null });
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
t("error.retry")
|
|
1215
|
+
)));
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
985
1220
|
// src/client/components/contextModal.tsx
|
|
986
1221
|
function makeContextModal(ctx, kit) {
|
|
987
1222
|
const { t } = kit;
|
|
@@ -990,7 +1225,8 @@ function makeContextModal(ctx, kit) {
|
|
|
990
1225
|
const Legend = makeLegend(kit);
|
|
991
1226
|
const CurrentComposition = makeCurrentComposition(kit, StackedBar, Legend);
|
|
992
1227
|
const ContextBrowser = makeContextBrowser(kit, StackedBar);
|
|
993
|
-
|
|
1228
|
+
const ErrorBoundary = makeErrorBoundary(t);
|
|
1229
|
+
function ContextModalBody(props) {
|
|
994
1230
|
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
995
1231
|
const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
|
|
996
1232
|
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
@@ -1051,6 +1287,9 @@ function makeContextModal(ctx, kit) {
|
|
|
1051
1287
|
}
|
|
1052
1288
|
}
|
|
1053
1289
|
))));
|
|
1290
|
+
}
|
|
1291
|
+
return function ContextModal(props) {
|
|
1292
|
+
return h(ErrorBoundary, null, h(ContextModalBody, props));
|
|
1054
1293
|
};
|
|
1055
1294
|
}
|
|
1056
1295
|
|
|
@@ -1273,6 +1512,13 @@ var STYLES = [
|
|
|
1273
1512
|
".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
|
|
1274
1513
|
".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
|
|
1275
1514
|
".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
|
|
1515
|
+
// Error-boundary fallback card: the tab degrades to this instead of
|
|
1516
|
+
// white-screening; the message runs in mono on a padded chip, and Retry
|
|
1517
|
+
// re-mounts the subtree once a healthy value arrives.
|
|
1518
|
+
".lc-error { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 40px 16px; }",
|
|
1519
|
+
".lc-error-msg { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 4px 8px; max-width: 100%; overflow-wrap: anywhere; }",
|
|
1520
|
+
".lc-error-retry { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 4px 14px; border-radius: 6px; cursor: pointer; }",
|
|
1521
|
+
".lc-error-retry:hover { border-color: var(--dsw-alias-label-primary); }",
|
|
1276
1522
|
".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }",
|
|
1277
1523
|
// ---- /context modal (centered dialog; escapes the composer anchor via fixed positioning) ----
|
|
1278
1524
|
".lc-modal-backdrop { position: fixed; inset: 0; z-index: 200; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; }",
|
|
@@ -1333,6 +1579,11 @@ var STYLES = [
|
|
|
1333
1579
|
".lc-br-pre { margin: 0; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 320px; overflow-y: auto; scrollbar-width: thin; }",
|
|
1334
1580
|
".lc-br-dim { color: var(--dsw-alias-label-secondary); }",
|
|
1335
1581
|
".lc-br-call { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }",
|
|
1582
|
+
// Assistant-message block title: a slim label line above each block
|
|
1583
|
+
// (thinking / answer) so a mixed reply stays scannable — same weight and
|
|
1584
|
+
// tone as the tool-schema card heads, without an extra bordered card.
|
|
1585
|
+
".lc-br-blk { display: flex; flex-direction: column; gap: 4px; }",
|
|
1586
|
+
".lc-br-blk-label { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); }",
|
|
1336
1587
|
// ---- Tool schema body (browser card, tools category). The description
|
|
1337
1588
|
// and parameter table share one "card with a small title" chrome — a
|
|
1338
1589
|
// labeled bordered block so a reader can scan three stacked sections
|
|
@@ -1392,24 +1643,27 @@ var STYLES = [
|
|
|
1392
1643
|
".lc-br-md div p, .lc-ts-desc-md div p { margin: 6px 0; }",
|
|
1393
1644
|
".lc-br-md div pre, .lc-ts-desc-md div pre { margin: 6px 0; }",
|
|
1394
1645
|
".lc-br-md div :not(pre) > code, .lc-ts-desc-md div :not(pre) > code { font-size: 1em !important; line-height: 1.3; padding: 1px 5px; box-decoration-break: clone; }",
|
|
1395
|
-
".lc-br-md div table code, .lc-ts-desc-md div table code { font-size: inherit; }"
|
|
1646
|
+
".lc-br-md div table code, .lc-ts-desc-md div table code { font-size: inherit; }",
|
|
1647
|
+
// Un-padded RichCard variant: used when the text card is one section
|
|
1648
|
+
// among several (attachment cards) inside an already-indented body.
|
|
1649
|
+
".lc-br-rich-inline { display: flex; flex-direction: column; gap: 6px; }",
|
|
1650
|
+
// ---- Attachment cards (image refs in a message detail). The card chrome
|
|
1651
|
+
// reuses the tool-schema sections' `lc-ts-card`/`lc-ts-card-head`; only the
|
|
1652
|
+
// image grid and tile are new. Each image is a 64px cover tile (the chat
|
|
1653
|
+
// history's tile size) beside a name + dims + size column. ----
|
|
1654
|
+
".lc-ts-card .lc-br-pre { margin: 8px 10px; }",
|
|
1655
|
+
".lc-att-grid { display: flex; flex-wrap: wrap; gap: 10px; padding: 8px 10px; }",
|
|
1656
|
+
".lc-att-item { display: flex; gap: 8px; align-items: center; min-width: 0; }",
|
|
1657
|
+
".lc-att-thumb { width: 64px; height: 64px; flex: none; padding: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; overflow: hidden; background: var(--dsw-alias-bg-layer-1); cursor: pointer; font: inherit; }",
|
|
1658
|
+
".lc-att-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }",
|
|
1659
|
+
".lc-att-ph, .lc-att-err { font-size: 10px; color: var(--dsw-alias-label-secondary); text-align: center; padding: 2px; }",
|
|
1660
|
+
".lc-att-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
|
|
1661
|
+
".lc-att-name { font-size: 12px; color: var(--dsw-alias-label-primary); max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
1662
|
+
".lc-att-dims { font-size: 11px; color: var(--dsw-alias-label-secondary); }"
|
|
1396
1663
|
].join("\n");
|
|
1397
1664
|
|
|
1398
1665
|
// src/client/components/events.tsx
|
|
1399
1666
|
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1400
|
-
|
|
1401
|
-
// src/client/format.ts
|
|
1402
|
-
function fmt(n) {
|
|
1403
|
-
if (n === void 0 || n === null || isNaN(n)) return "\u2014";
|
|
1404
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
1405
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
|
1406
|
-
return String(Math.round(n));
|
|
1407
|
-
}
|
|
1408
|
-
function fmtTime(t) {
|
|
1409
|
-
return new Date(t).toLocaleTimeString("en-GB", { hour12: false });
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// src/client/components/events.tsx
|
|
1413
1667
|
var EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
|
|
1414
1668
|
function makeEventText(t) {
|
|
1415
1669
|
function eventLabel(ev) {
|
|
@@ -1472,7 +1726,7 @@ function makeEventList(kit) {
|
|
|
1472
1726
|
|
|
1473
1727
|
// src/client/meta.ts
|
|
1474
1728
|
var PLUGIN_NAME = "dsh-context";
|
|
1475
|
-
var PLUGIN_VERSION = true ? "0.
|
|
1729
|
+
var PLUGIN_VERSION = true ? "0.22.0" : "0.0.0-dev";
|
|
1476
1730
|
var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
|
|
1477
1731
|
var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
|
|
1478
1732
|
|
|
@@ -1563,7 +1817,7 @@ function estimateSessionCost(usage, currency) {
|
|
|
1563
1817
|
const b = fam[period];
|
|
1564
1818
|
if (b === void 0) continue;
|
|
1565
1819
|
const p = PRICES[currency][family][period];
|
|
1566
|
-
total += (b.cacheRead * p.hit + (b.uncached + b.cacheWrite) * p.miss + b.output * p.out) / 1e6;
|
|
1820
|
+
total += (numOf(b.cacheRead) * p.hit + (numOf(b.uncached) + numOf(b.cacheWrite)) * p.miss + numOf(b.output) * p.out) / 1e6;
|
|
1567
1821
|
any = true;
|
|
1568
1822
|
}
|
|
1569
1823
|
}
|
|
@@ -1586,9 +1840,12 @@ function formatPriceRate(amount, currency) {
|
|
|
1586
1840
|
|
|
1587
1841
|
// src/client/components/statsBoard.tsx
|
|
1588
1842
|
function cacheHitPercent(usage) {
|
|
1589
|
-
const
|
|
1843
|
+
const uncached = numOf(usage.uncachedInputTokens);
|
|
1844
|
+
const reads = numOf(usage.cacheReadTokens);
|
|
1845
|
+
const writes = numOf(usage.cacheWriteTokens);
|
|
1846
|
+
const billed = uncached + reads + writes;
|
|
1590
1847
|
if (billed === 0) return null;
|
|
1591
|
-
const hundredths = Math.trunc(
|
|
1848
|
+
const hundredths = Math.trunc(reads / billed * 1e4 + 1e-9);
|
|
1592
1849
|
return `${Math.floor(hundredths / 100)}.${String(hundredths % 100).padStart(2, "0")}`;
|
|
1593
1850
|
}
|
|
1594
1851
|
function makeStatsBoard(kit) {
|
|
@@ -1811,7 +2068,8 @@ function makeContextView(ctx, kit) {
|
|
|
1811
2068
|
const StatsBoard = makeStatsBoard(kit);
|
|
1812
2069
|
const PluginInfo = makePluginInfo(kit);
|
|
1813
2070
|
const ContextBrowser = makeContextBrowser(kit, StackedBar);
|
|
1814
|
-
|
|
2071
|
+
const ErrorBoundary = makeErrorBoundary(t);
|
|
2072
|
+
function ContextViewBody(props) {
|
|
1815
2073
|
const sessionId = props.sessionId;
|
|
1816
2074
|
const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
|
|
1817
2075
|
const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
|
|
@@ -1835,6 +2093,12 @@ function makeContextView(ctx, kit) {
|
|
|
1835
2093
|
const clearToolFocus = React.useCallback(() => {
|
|
1836
2094
|
setToolFocus(null);
|
|
1837
2095
|
}, []);
|
|
2096
|
+
const loadImage = React.useMemo(() => {
|
|
2097
|
+
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
2098
|
+
const conversation = ctx.get("conversation");
|
|
2099
|
+
if (conversation === void 0 || typeof conversation.resolveImage !== "function") return void 0;
|
|
2100
|
+
return (attachment) => conversation.resolveImage(sessionId, attachment);
|
|
2101
|
+
}, [sessionId]);
|
|
1838
2102
|
const rootRef = React.useRef(null);
|
|
1839
2103
|
const scrollerRef = React.useRef(null);
|
|
1840
2104
|
const restoredRef = React.useRef(null);
|
|
@@ -1946,7 +2210,8 @@ function makeContextView(ctx, kit) {
|
|
|
1946
2210
|
hoverKey: hoverCat,
|
|
1947
2211
|
onHoverKey: setHoverCat,
|
|
1948
2212
|
toolFocus,
|
|
1949
|
-
onToolFocusHandled: clearToolFocus
|
|
2213
|
+
onToolFocusHandled: clearToolFocus,
|
|
2214
|
+
loadImage
|
|
1950
2215
|
}
|
|
1951
2216
|
))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("events.title")), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement(
|
|
1952
2217
|
"button",
|
|
@@ -1959,6 +2224,9 @@ function makeContextView(ctx, kit) {
|
|
|
1959
2224
|
},
|
|
1960
2225
|
t("kind." + k)
|
|
1961
2226
|
)))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("nodes.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, { nodes, dropped: data.droppedNodes || 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
|
|
2227
|
+
}
|
|
2228
|
+
return function ContextView(props) {
|
|
2229
|
+
return h(ErrorBoundary, null, h(ContextViewBody, props));
|
|
1962
2230
|
};
|
|
1963
2231
|
}
|
|
1964
2232
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|