dsh-context 0.29.0 → 0.30.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 +1 -0
- package/cordis.patch.yml +3 -7
- package/lib/client.js +158 -199
- package/lib/index.d.ts +15 -54
- package/lib/index.js +14 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-context)
|
|
6
6
|
[](https://github.com/bowenliang123/dsh-context)
|
|
7
|
+
[](https://dshfind.com/en/plugins/bowenliang123/dsh-context?ref=badge)
|
|
7
8
|
|
|
8
9
|
**The best [DeepSeek Harness plugin](https://www.deepseek.com/harness/) for Agent's context insights and management.**
|
|
9
10
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# appends it to `dsh.profile.bundles`; this patch is then applied as that
|
|
5
|
-
# bundle's layer. The row below loads the package's main entry as the host
|
|
6
|
-
# half (a plain Cordis plugin) and, because the package declares `dsh.client`,
|
|
7
|
-
# the web app also loads its `./client` bundle as the browser half.
|
|
1
|
+
# Bundle patch layer: the row loads the package's main entry as the host half
|
|
2
|
+
# (a plain Cordis plugin); the `dsh.client` declaration makes the web app load
|
|
3
|
+
# its ./client bundle as the browser half.
|
|
8
4
|
- insert:
|
|
9
5
|
- id: dsh-context
|
|
10
6
|
name: dsh-context
|
package/lib/client.js
CHANGED
|
@@ -132,6 +132,12 @@ window.__ModuleLoader__.load({
|
|
|
132
132
|
"block.content": "内容",
|
|
133
133
|
"block.result": "结果",
|
|
134
134
|
"block.summary": "摘要",
|
|
135
|
+
"block.line": "{n} 行",
|
|
136
|
+
"block.lines": "{n} 行",
|
|
137
|
+
"call.ok": "正常",
|
|
138
|
+
"call.fail": "失败",
|
|
139
|
+
"call.exit": "exit {n}",
|
|
140
|
+
"node.failed": "工具执行失败",
|
|
135
141
|
"attach.images": "图片附件",
|
|
136
142
|
"attach.other": "其他内容",
|
|
137
143
|
"attach.image": "图片",
|
|
@@ -272,6 +278,12 @@ window.__ModuleLoader__.load({
|
|
|
272
278
|
"block.content": "Content",
|
|
273
279
|
"block.result": "Result",
|
|
274
280
|
"block.summary": "Summary",
|
|
281
|
+
"block.line": "1 line",
|
|
282
|
+
"block.lines": "{n} lines",
|
|
283
|
+
"call.ok": "OK",
|
|
284
|
+
"call.fail": "Failed",
|
|
285
|
+
"call.exit": "exit {n}",
|
|
286
|
+
"node.failed": "Tool execution failed",
|
|
275
287
|
"attach.images": "Images",
|
|
276
288
|
"attach.other": "Other content",
|
|
277
289
|
"attach.image": "Image",
|
|
@@ -314,7 +326,6 @@ window.__ModuleLoader__.load({
|
|
|
314
326
|
function setPendingConsume(sessionId, guard) {
|
|
315
327
|
pendingConsume.set(sessionId, guard);
|
|
316
328
|
}
|
|
317
|
-
/** Take (and clear) the guard recorded by the open path, if any. */
|
|
318
329
|
function takePendingConsume(sessionId) {
|
|
319
330
|
const guard = pendingConsume.get(sessionId);
|
|
320
331
|
if (guard !== void 0) pendingConsume.delete(sessionId);
|
|
@@ -514,7 +525,6 @@ window.__ModuleLoader__.load({
|
|
|
514
525
|
function numOf(value) {
|
|
515
526
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
516
527
|
}
|
|
517
|
-
/** The usable (non-null object) entries of a delivered list; [] when the field is not a list. */
|
|
518
528
|
function objectsOf(value) {
|
|
519
529
|
if (!Array.isArray(value)) return [];
|
|
520
530
|
return value.filter((v) => v !== null && typeof v === "object");
|
|
@@ -644,10 +654,6 @@ window.__ModuleLoader__.load({
|
|
|
644
654
|
for (let i = headers.headers.length - 1; i >= 0; i--) if (headers.headers[i].seq < seq) return headers.headers[i];
|
|
645
655
|
return null;
|
|
646
656
|
}
|
|
647
|
-
/**
|
|
648
|
-
* Reconstruct the assembled surface for one step (`seq` = the request
|
|
649
|
-
* record's seq) or the live surface (`seq` = null).
|
|
650
|
-
*/
|
|
651
657
|
function assemble(data, headers, seq) {
|
|
652
658
|
const live = seq === null;
|
|
653
659
|
let nodes;
|
|
@@ -679,7 +685,6 @@ window.__ModuleLoader__.load({
|
|
|
679
685
|
const ReactDOM = require("react-dom");
|
|
680
686
|
//#endregion
|
|
681
687
|
//#region src/client/components/nodes.tsx
|
|
682
|
-
/** One-line preview of a surface node (shared by the node list and the Context browser's element rows). */
|
|
683
688
|
function makeNodeText(kit) {
|
|
684
689
|
const { t } = kit;
|
|
685
690
|
return function nodeText(n) {
|
|
@@ -714,10 +719,7 @@ window.__ModuleLoader__.load({
|
|
|
714
719
|
}
|
|
715
720
|
//#endregion
|
|
716
721
|
//#region src/client/format.ts
|
|
717
|
-
/**
|
|
718
|
-
* Number/time formatting for the UI. `fmt` uses the same k/M suffix style
|
|
719
|
-
* everywhere (bars, details, stats); `fmtTime` renders a local HH:MM:SS.
|
|
720
|
-
*/
|
|
722
|
+
/** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
|
|
721
723
|
function fmt(n) {
|
|
722
724
|
if (n === void 0 || n === null || isNaN(n)) return "—";
|
|
723
725
|
const sign = n < 0 ? "-" : "";
|
|
@@ -767,7 +769,6 @@ window.__ModuleLoader__.load({
|
|
|
767
769
|
const MIN_PIXELS = 147456;
|
|
768
770
|
const floorDiv = (a, b) => Math.floor(a / b);
|
|
769
771
|
const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
|
|
770
|
-
/** Token count of one patch grid (rows×cols) under the v4 layout rule. */
|
|
771
772
|
function gridTokens(rows, cols) {
|
|
772
773
|
let n = rows * (cols + 1) + 2;
|
|
773
774
|
if (rows % 2 === 1) n += cols + 1;
|
|
@@ -849,10 +850,8 @@ window.__ModuleLoader__.load({
|
|
|
849
850
|
return safeResize(h, w, paddedHeight, paddedWidth);
|
|
850
851
|
}
|
|
851
852
|
/**
|
|
852
|
-
* Estimate the tokens one image consumes in a DeepSeek vision request
|
|
853
|
-
*
|
|
854
|
-
* or when the official iteration fails to converge — callers fall back to
|
|
855
|
-
* the generic structural price.
|
|
853
|
+
* Estimate the tokens one image consumes in a DeepSeek vision request from its pixel dimensions. Returns null for non-positive/non-finite
|
|
854
|
+
* dimensions or when the official iteration fails to converge — callers fall back to the generic structural price.
|
|
856
855
|
*/
|
|
857
856
|
function estimateImageTokens(width, height) {
|
|
858
857
|
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
@@ -871,11 +870,9 @@ window.__ModuleLoader__.load({
|
|
|
871
870
|
//#endregion
|
|
872
871
|
//#region src/client/components/images.tsx
|
|
873
872
|
/**
|
|
874
|
-
* Narrow an unknown content block to a durable image
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
* else returns null. Lenient on the ref's optional facts (name/bytes/dims) —
|
|
878
|
-
* `resolveImage` reads only `attachmentId`.
|
|
873
|
+
* Narrow an unknown content block to a durable image ref: accepts both raw message blocks (`{ type: 'image', attachment }`) and the
|
|
874
|
+
* snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything else null. Lenient on the optional facts — resolveImage reads
|
|
875
|
+
* only attachmentId.
|
|
879
876
|
*/
|
|
880
877
|
function imageRefOf(block) {
|
|
881
878
|
if (block === null || typeof block !== "object") return null;
|
|
@@ -901,12 +898,9 @@ window.__ModuleLoader__.load({
|
|
|
901
898
|
};
|
|
902
899
|
}
|
|
903
900
|
/**
|
|
904
|
-
* Document-level original-image preview — the chat history's ImageLightbox
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* filtered ancestor cannot trap the fixed backdrop, blurred mask layer,
|
|
908
|
-
* contain-fit image, circular close control, Escape/mask close, and focus
|
|
909
|
-
* restored to the opener on unmount.
|
|
901
|
+
* Document-level original-image preview — the chat history's ImageLightbox recipe (dsh ui-attachment, which the browser module table does
|
|
902
|
+
* not seed) ported onto the plugin's lc-* classes: body portal (a transformed/filtered ancestor cannot trap the fixed backdrop), blurred
|
|
903
|
+
* mask, contain-fit image, circular close, Escape/mask close, focus restored to the opener.
|
|
910
904
|
*/
|
|
911
905
|
function AttachmentLightbox(props) {
|
|
912
906
|
const { src, alt, labels, onClose } = props;
|
|
@@ -946,13 +940,9 @@ window.__ModuleLoader__.load({
|
|
|
946
940
|
}, /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 16 }))), document.body);
|
|
947
941
|
}
|
|
948
942
|
/**
|
|
949
|
-
* One attachment card
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
* normalization reduced the image; Sent, the normalized raster the model
|
|
953
|
-
* receives, with its stored byte size; and the estimated provider-billed
|
|
954
|
-
* tokens). Clicking opens the chat-style lightbox preview (load failures
|
|
955
|
-
* retry on click instead). Unknown facts leave no row behind.
|
|
943
|
+
* One attachment card, the WHOLE card the click target: 64px cover tile + metadata column — Raw (the pre-normalization raster dsh records
|
|
944
|
+
* when normalization reduced the image), Sent (the normalized raster the model receives, with byte size), estimated provider-billed tokens.
|
|
945
|
+
* Click opens the chat-style lightbox; load failures retry on click; unknown facts leave no row.
|
|
956
946
|
*/
|
|
957
947
|
function makeImageCard(kit) {
|
|
958
948
|
const { t, fmt } = kit;
|
|
@@ -1066,7 +1056,6 @@ window.__ModuleLoader__.load({
|
|
|
1066
1056
|
}
|
|
1067
1057
|
//#endregion
|
|
1068
1058
|
//#region src/client/components/browser.tsx
|
|
1069
|
-
/** Flatten the `anyOf` / `oneOf` alternation into one displayable string. */
|
|
1070
1059
|
function unionTypesOf(p) {
|
|
1071
1060
|
const branches = [];
|
|
1072
1061
|
if (Array.isArray(p.anyOf)) branches.push(...p.anyOf);
|
|
@@ -1076,12 +1065,6 @@ window.__ModuleLoader__.load({
|
|
|
1076
1065
|
for (const b of branches) if (b !== null && typeof b === "object") parts.push(typeOf(b));
|
|
1077
1066
|
return parts.length > 0 ? parts.join(" | ") : null;
|
|
1078
1067
|
}
|
|
1079
|
-
/**
|
|
1080
|
-
* Derive a short, human-readable type label from a JSON Schema fragment.
|
|
1081
|
-
* Arrays show their element type (`array<number>`); unions fold into
|
|
1082
|
-
* `a | b`; enums collapse into `(enum)`. Returns `unknown` when the schema
|
|
1083
|
-
* carries no usable signal — the row falls back to its description then.
|
|
1084
|
-
*/
|
|
1085
1068
|
function typeOf(p) {
|
|
1086
1069
|
const u = unionTypesOf(p);
|
|
1087
1070
|
if (u !== null) return u;
|
|
@@ -1103,9 +1086,8 @@ window.__ModuleLoader__.load({
|
|
|
1103
1086
|
return "unknown";
|
|
1104
1087
|
}
|
|
1105
1088
|
/**
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
* the schema as the bare JSON Schema (when `type === 'object'`).
|
|
1089
|
+
* Tool schemas nest parameters under `parameters`, `input_schema`, or `inputSchema` (producer-dependent), or bare when `type === 'object'`
|
|
1090
|
+
* — `{type:'object', properties}` at the root is itself the parameter object.
|
|
1109
1091
|
*/
|
|
1110
1092
|
function paramsOf(schema) {
|
|
1111
1093
|
if (schema === null || typeof schema !== "object") return null;
|
|
@@ -1116,33 +1098,19 @@ window.__ModuleLoader__.load({
|
|
|
1116
1098
|
if (s.type === "object" && s.properties !== void 0 && typeof s.properties === "object") return s;
|
|
1117
1099
|
return null;
|
|
1118
1100
|
}
|
|
1119
|
-
/**
|
|
1120
|
-
* One row of the parsed parameter table — name (mono-styled, with a
|
|
1121
|
-
* required chip), short type label, and the description on its own line
|
|
1122
|
-
* (so a long blurb never breaks the name/type rhythm).
|
|
1123
|
-
*/
|
|
1124
1101
|
function ParamRow(props) {
|
|
1125
1102
|
const typeLabel = typeOf(props.schema);
|
|
1126
1103
|
const desc = props.schema.description;
|
|
1127
1104
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-param-row" }, /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-name" }, props.name), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-type" }, typeLabel), /* @__PURE__ */ React.createElement("span", { className: props.required ? "lc-ts-param-req" : "lc-ts-param-req-off" }, props.required ? "✓" : "·"), typeof desc === "string" && desc !== "" ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-desc" }, desc) : null);
|
|
1128
1105
|
}
|
|
1129
1106
|
/**
|
|
1130
|
-
* Section — the ONE detail chrome of the
|
|
1131
|
-
*
|
|
1132
|
-
* extras on the right (a count badge and/or the Raw/MD switch), with the
|
|
1133
|
-
* body below. Every expanded element renders as a stack of these — prose,
|
|
1134
|
-
* tool calls, parameter rows, image grids, and raw JSON all share the same
|
|
1135
|
-
* frame, so the reader scans one repeating anatomy instead of a different
|
|
1136
|
-
* layout per content kind.
|
|
1107
|
+
* Section — the ONE detail chrome of the browser: every expanded element is a stack of these (labeled head + body), so the reader scans one
|
|
1108
|
+
* repeating anatomy per content kind.
|
|
1137
1109
|
*/
|
|
1138
1110
|
function Section(props) {
|
|
1139
|
-
|
|
1111
|
+
const right = props.actions !== void 0 || props.meta !== void 0;
|
|
1112
|
+
return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", { className: props.labelClass }, props.label), right ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-right" }, props.meta ?? null, props.actions ?? null) : null, props.count !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, props.count) : null), props.children);
|
|
1140
1113
|
}
|
|
1141
|
-
/**
|
|
1142
|
-
* TextSection — a prose Section (system prompt, message/injection/summary
|
|
1143
|
-
* body, thinking, answer, tool description): the head's right edge carries
|
|
1144
|
-
* the Raw/MD switch and the body follows the per-card mode.
|
|
1145
|
-
*/
|
|
1146
1114
|
function TextSection(props) {
|
|
1147
1115
|
const { rich } = props;
|
|
1148
1116
|
const [mode, setMode] = rich.useRichMode();
|
|
@@ -1151,21 +1119,19 @@ window.__ModuleLoader__.load({
|
|
|
1151
1119
|
actions: /* @__PURE__ */ React.createElement(rich.RichSwitch, {
|
|
1152
1120
|
mode,
|
|
1153
1121
|
onPick: setMode
|
|
1154
|
-
})
|
|
1122
|
+
}),
|
|
1123
|
+
meta: props.meta
|
|
1155
1124
|
}, /* @__PURE__ */ React.createElement(rich.RichText, {
|
|
1156
1125
|
text: props.text,
|
|
1157
1126
|
mode
|
|
1158
1127
|
}));
|
|
1159
1128
|
}
|
|
1160
|
-
/** RawSection — a dimmed text Section without a view switch (structured payloads: tool results, raw JSON). */
|
|
1161
1129
|
function RawSection(props) {
|
|
1162
1130
|
return /* @__PURE__ */ React.createElement(Section, { label: props.label }, /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.text));
|
|
1163
1131
|
}
|
|
1164
1132
|
/**
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
* Owns its own open/closed state for the JSON so two expanded tools stay
|
|
1168
|
-
* independent.
|
|
1133
|
+
* Full tool-row body: description, parsed parameter table (when the schema carries one), raw JSON behind a per-row toggle — the JSON open
|
|
1134
|
+
* state is per-row so two expanded tools stay independent.
|
|
1169
1135
|
*/
|
|
1170
1136
|
function ToolSchema(props) {
|
|
1171
1137
|
const { rich } = props;
|
|
@@ -1210,14 +1176,9 @@ window.__ModuleLoader__.load({
|
|
|
1210
1176
|
}, (jsonOpen ? "▾ " : "▸ ") + (jsonOpen ? props.labels.hide : props.labels.show)), jsonOpen ? /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, schemaJson) : null) : null);
|
|
1211
1177
|
}
|
|
1212
1178
|
/**
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
* conversation snapshot's assistant blocks (`kind`: text / reasoning /
|
|
1217
|
-
* tool-call / image, argsRaw). Consecutive images group into one grid
|
|
1218
|
-
* section; `richable` bodies (messages, injections) carry the Raw/MD
|
|
1219
|
-
* switch while tool-result payloads stay raw (structured data, not prose)
|
|
1220
|
-
* and dim; nested tool-result blocks flatten into the same flow.
|
|
1179
|
+
* Both block vocabularies normalize here — raw durable blocks (`type`: text/reasoning/tool-call/tool-result/image) and snapshot assistant
|
|
1180
|
+
* blocks (`kind`: text/reasoning/tool-call/image, argsRaw). Consecutive images group into one grid; tool-result payloads carry the Raw/MD
|
|
1181
|
+
* switch + line count; nested tool-result blocks flatten into the same flow.
|
|
1221
1182
|
*/
|
|
1222
1183
|
function BlocksBody(props) {
|
|
1223
1184
|
const { rich, img, labels } = props;
|
|
@@ -1248,15 +1209,12 @@ window.__ModuleLoader__.load({
|
|
|
1248
1209
|
const blockKind = blk !== null ? typeof blk.type === "string" ? blk.type : typeof blk.kind === "string" ? blk.kind : "" : "";
|
|
1249
1210
|
if ((blockKind === "text" || blockKind === "reasoning") && typeof blk?.text === "string") {
|
|
1250
1211
|
const label = blockKind === "reasoning" ? labels.thinking : props.textLabel;
|
|
1251
|
-
out.push(
|
|
1212
|
+
out.push(/* @__PURE__ */ React.createElement(TextSection, {
|
|
1252
1213
|
key: out.length,
|
|
1253
1214
|
label,
|
|
1254
1215
|
text: blk.text,
|
|
1255
|
-
rich
|
|
1256
|
-
|
|
1257
|
-
key: out.length,
|
|
1258
|
-
label,
|
|
1259
|
-
text: blk.text
|
|
1216
|
+
rich,
|
|
1217
|
+
meta: props.textLabel === labels.result ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-meta" }, labels.lines(blk.text.split("\n").length)) : void 0
|
|
1260
1218
|
}));
|
|
1261
1219
|
continue;
|
|
1262
1220
|
}
|
|
@@ -1289,11 +1247,6 @@ window.__ModuleLoader__.load({
|
|
|
1289
1247
|
flushImages();
|
|
1290
1248
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, out);
|
|
1291
1249
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Parse a call's raw argument payload into an object; null when the payload
|
|
1294
|
-
* is missing, malformed, or not a JSON object (callers fall back to the raw
|
|
1295
|
-
* text).
|
|
1296
|
-
*/
|
|
1297
1250
|
function parseArgs(raw) {
|
|
1298
1251
|
if (typeof raw !== "string" || raw === "") return null;
|
|
1299
1252
|
try {
|
|
@@ -1304,11 +1257,8 @@ window.__ModuleLoader__.load({
|
|
|
1304
1257
|
}
|
|
1305
1258
|
}
|
|
1306
1259
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
* read / write) name their target via `file_path`/`path`/`filePath`. Null
|
|
1310
|
-
* when the arguments hold neither — the row then falls back to the generic
|
|
1311
|
-
* label.
|
|
1260
|
+
* A call's preview: bash-style `description`, or the target path (`file_path`/`path`/`filePath`) for edit/read/write tools; null when the
|
|
1261
|
+
* arguments hold neither.
|
|
1312
1262
|
*/
|
|
1313
1263
|
function summaryIn(args) {
|
|
1314
1264
|
if (args === null) return null;
|
|
@@ -1323,18 +1273,24 @@ window.__ModuleLoader__.load({
|
|
|
1323
1273
|
}
|
|
1324
1274
|
return null;
|
|
1325
1275
|
}
|
|
1326
|
-
/**
|
|
1327
|
-
* The summary of a tool result's own call — the collapsed row's preview.
|
|
1328
|
-
* Null when the call summarizes to nothing.
|
|
1329
|
-
*/
|
|
1330
1276
|
function callSummaryOf(conv) {
|
|
1331
1277
|
return summaryIn(parseArgs(conv?.call?.argsRaw));
|
|
1332
1278
|
}
|
|
1333
1279
|
/**
|
|
1334
|
-
* The
|
|
1335
|
-
*
|
|
1336
|
-
* block summarizes.
|
|
1280
|
+
* The dsh `[exit code: N]` marker across the payload's direct text blocks — surfaced only on the FAILED run-state pill, the one place the
|
|
1281
|
+
* number matters.
|
|
1337
1282
|
*/
|
|
1283
|
+
function exitCodeOf(conv) {
|
|
1284
|
+
if (conv === void 0 || !Array.isArray(conv.content)) return null;
|
|
1285
|
+
for (const b of conv.content) {
|
|
1286
|
+
if (b === null || typeof b !== "object") continue;
|
|
1287
|
+
const blk = b;
|
|
1288
|
+
if (typeof blk.text !== "string") continue;
|
|
1289
|
+
const m = blk.text.match(/\[exit code:\s*(\d+)\]/);
|
|
1290
|
+
if (m !== null) return Number(m[1]);
|
|
1291
|
+
}
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1338
1294
|
function blockSummaryOf(conv) {
|
|
1339
1295
|
if (conv === void 0 || !Array.isArray(conv.blocks)) return null;
|
|
1340
1296
|
for (const b of conv.blocks) {
|
|
@@ -1345,37 +1301,23 @@ window.__ModuleLoader__.load({
|
|
|
1345
1301
|
}
|
|
1346
1302
|
return null;
|
|
1347
1303
|
}
|
|
1348
|
-
/**
|
|
1349
|
-
* One tool call as a Section, mirroring the tool-definition parameter
|
|
1350
|
-
* card: the head names the call target (mono, with the parsed argument
|
|
1351
|
-
* count), the body lists the arguments as name/value rows. Arguments that
|
|
1352
|
-
* are not a parseable JSON object fall back to the raw payload inside the
|
|
1353
|
-
* same card. Shared by assistant tool-call blocks (`→`) and the call half
|
|
1354
|
-
* of a tool result (`←`).
|
|
1355
|
-
*/
|
|
1356
1304
|
function ToolCallCard(props) {
|
|
1357
1305
|
const args = React.useMemo(() => parseArgs(props.argsRaw), [props.argsRaw]);
|
|
1358
1306
|
return /* @__PURE__ */ React.createElement(Section, {
|
|
1359
1307
|
label: (props.arrow ?? "→") + " " + props.name,
|
|
1360
1308
|
labelClass: "lc-ts-call-name",
|
|
1361
|
-
|
|
1309
|
+
meta: props.status
|
|
1362
1310
|
}, args !== null ? Object.keys(args).map((k) => /* @__PURE__ */ React.createElement(CallArgRow, {
|
|
1363
1311
|
key: k,
|
|
1364
1312
|
name: k,
|
|
1365
1313
|
value: args[k]
|
|
1366
1314
|
})) : typeof props.argsRaw === "string" && props.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.argsRaw) : null);
|
|
1367
1315
|
}
|
|
1368
|
-
/** One parsed call argument: the name on the left, the value on the right. */
|
|
1369
1316
|
function CallArgRow(props) {
|
|
1370
1317
|
const v = props.value;
|
|
1371
1318
|
const text = typeof v === "string" ? v : v === void 0 ? "" : JSON.stringify(v);
|
|
1372
1319
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-arg-row" }, /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-name" }, props.name), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-arg-val" }, text));
|
|
1373
1320
|
}
|
|
1374
|
-
/**
|
|
1375
|
-
* The actual content of one surface element, joined from the conversation
|
|
1376
|
-
* snapshot — rendered as a uniform Section stack (see BlocksBody); the
|
|
1377
|
-
* element row's open body wraps the stack in `lc-br-content`.
|
|
1378
|
-
*/
|
|
1379
1321
|
function NodeContent(props) {
|
|
1380
1322
|
const { node, conv, rich, img, labels } = props;
|
|
1381
1323
|
if (conv === void 0) {
|
|
@@ -1394,18 +1336,22 @@ window.__ModuleLoader__.load({
|
|
|
1394
1336
|
img,
|
|
1395
1337
|
labels
|
|
1396
1338
|
});
|
|
1397
|
-
if (conv.kind === "tool-result")
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1339
|
+
if (conv.kind === "tool-result") {
|
|
1340
|
+
const err = node.err === true || conv.isError === true;
|
|
1341
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, conv.call != null ? /* @__PURE__ */ React.createElement(ToolCallCard, {
|
|
1342
|
+
arrow: "←",
|
|
1343
|
+
name: conv.call.name,
|
|
1344
|
+
argsRaw: conv.call.argsRaw,
|
|
1345
|
+
status: labels.callState(err, err ? exitCodeOf(conv) : null)
|
|
1346
|
+
}) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(BlocksBody, {
|
|
1347
|
+
blocks: conv.content,
|
|
1348
|
+
richable: false,
|
|
1349
|
+
textLabel: labels.result,
|
|
1350
|
+
rich,
|
|
1351
|
+
img,
|
|
1352
|
+
labels
|
|
1353
|
+
}) : null);
|
|
1354
|
+
}
|
|
1409
1355
|
if (conv.kind === "compaction") return typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(TextSection, {
|
|
1410
1356
|
label: labels.summary,
|
|
1411
1357
|
text: conv.summary,
|
|
@@ -1421,19 +1367,16 @@ window.__ModuleLoader__.load({
|
|
|
1421
1367
|
});
|
|
1422
1368
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint);
|
|
1423
1369
|
}
|
|
1424
|
-
/** Group an assembled surface's nodes by category (for per-category counts). */
|
|
1425
1370
|
function byCatOf(asm) {
|
|
1426
1371
|
const m = {};
|
|
1427
1372
|
for (const n of asm.nodes) (m[n.cat] ??= []).push(n);
|
|
1428
1373
|
return m;
|
|
1429
1374
|
}
|
|
1430
|
-
/** Count one category's elements in an assembled surface (header cats included). */
|
|
1431
1375
|
function countOf(asm, c) {
|
|
1432
1376
|
if (c === "system") return asm.header !== null && asm.header.system !== void 0 ? 1 : 0;
|
|
1433
1377
|
if (c === "tools") return asm.header !== null ? asm.header.tools.length : 0;
|
|
1434
1378
|
return byCatOf(asm)[c]?.length ?? 0;
|
|
1435
1379
|
}
|
|
1436
|
-
/** The last request of `turn` in a seq-ordered timeline, or null. */
|
|
1437
1380
|
function lastOfTurn(requests, turn) {
|
|
1438
1381
|
for (let i = requests.length - 1; i >= 0; i--) if ((requests[i].turn ?? 0) === turn) return requests[i];
|
|
1439
1382
|
return null;
|
|
@@ -1534,9 +1477,11 @@ window.__ModuleLoader__.load({
|
|
|
1534
1477
|
const toggleElem = (key) => {
|
|
1535
1478
|
setOpenElem(openElem === key ? null : key);
|
|
1536
1479
|
};
|
|
1537
|
-
/**
|
|
1538
|
-
*
|
|
1539
|
-
|
|
1480
|
+
/**
|
|
1481
|
+
* Expandable element row; `err` rows carry the red run-state dot right after the chevron (the chat's failed-tool marker) so a failed
|
|
1482
|
+
* result scans while collapsed.
|
|
1483
|
+
*/
|
|
1484
|
+
const elemRow = (key, tag, preview, tokens, time, body, err = false) => {
|
|
1540
1485
|
const open = openElem === key;
|
|
1541
1486
|
return /* @__PURE__ */ React.createElement("div", {
|
|
1542
1487
|
key,
|
|
@@ -1547,7 +1492,10 @@ window.__ModuleLoader__.load({
|
|
|
1547
1492
|
onClick: () => {
|
|
1548
1493
|
toggleElem(key);
|
|
1549
1494
|
}
|
|
1550
|
-
}, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "▸"),
|
|
1495
|
+
}, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "▸"), err ? /* @__PURE__ */ React.createElement("span", {
|
|
1496
|
+
className: "lc-br-err-dot",
|
|
1497
|
+
title: t("node.failed")
|
|
1498
|
+
}) : null, tag !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, tag) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-preview" }, preview), time !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-time" }, fmtTime(time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "≈" + fmt(tokens))), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, body) : null);
|
|
1551
1499
|
};
|
|
1552
1500
|
const catBody = (c) => {
|
|
1553
1501
|
if (c === "system") {
|
|
@@ -1579,17 +1527,18 @@ window.__ModuleLoader__.load({
|
|
|
1579
1527
|
});
|
|
1580
1528
|
}
|
|
1581
1529
|
return (byCat[c] ?? []).slice().reverse().map((n) => {
|
|
1530
|
+
const conv = bySeq.get(n.seq);
|
|
1531
|
+
const rowErr = n.cat === "tool" && (n.err === true || conv !== void 0 && conv.isError === true);
|
|
1582
1532
|
let tag = null;
|
|
1583
1533
|
let preview = nodeText(n);
|
|
1584
1534
|
if (n.cat === "tool") {
|
|
1585
|
-
tag = n.skill ? t("node.skillTag", { name: n.skill }) :
|
|
1586
|
-
preview = callSummaryOf(
|
|
1535
|
+
tag = n.skill ? t("node.skillTag", { name: n.skill }) : n.tool ?? "?";
|
|
1536
|
+
preview = callSummaryOf(conv) ?? t("node.toolResult");
|
|
1587
1537
|
} else if (n.cat === "assistant" && Array.isArray(n.calls) && n.calls.length > 0) {
|
|
1588
1538
|
tag = n.calls.join(" › ");
|
|
1589
|
-
preview = (n.text !== void 0 && n.text !== "" ? n.text : null) ?? blockSummaryOf(
|
|
1590
|
-
} else if (n.cat === "assistant" && (n.text === void 0 || n.text === "")) preview = blockSummaryOf(
|
|
1539
|
+
preview = (n.text !== void 0 && n.text !== "" ? n.text : null) ?? blockSummaryOf(conv) ?? t("node.empty");
|
|
1540
|
+
} else if (n.cat === "assistant" && (n.text === void 0 || n.text === "")) preview = blockSummaryOf(conv) ?? preview;
|
|
1591
1541
|
else if (n.cat === "user") {
|
|
1592
|
-
const conv = bySeq.get(n.seq);
|
|
1593
1542
|
const imgCount = conv !== void 0 && Array.isArray(conv.content) ? conv.content.filter((b) => imageRefOf(b) !== null).length : 0;
|
|
1594
1543
|
if (imgCount > 0 && openElem !== `n${n.seq}`) tag = t("attach.image") + (imgCount > 1 ? " ×" + String(imgCount) : "");
|
|
1595
1544
|
} else if (n.cat === "inject" && !n.skill) {
|
|
@@ -1598,7 +1547,7 @@ window.__ModuleLoader__.load({
|
|
|
1598
1547
|
}
|
|
1599
1548
|
return elemRow(`n${n.seq}`, tag, preview, n.tokens, n.time, /* @__PURE__ */ React.createElement(NodeContent, {
|
|
1600
1549
|
node: n,
|
|
1601
|
-
conv
|
|
1550
|
+
conv,
|
|
1602
1551
|
rich,
|
|
1603
1552
|
img: {
|
|
1604
1553
|
Card: ImageCard,
|
|
@@ -1611,10 +1560,12 @@ window.__ModuleLoader__.load({
|
|
|
1611
1560
|
result: t("block.result"),
|
|
1612
1561
|
summary: t("block.summary"),
|
|
1613
1562
|
images: t("attach.images"),
|
|
1614
|
-
other: t("attach.other")
|
|
1563
|
+
other: t("attach.other"),
|
|
1564
|
+
lines: (n) => t(n === 1 ? "block.line" : "block.lines", { n }),
|
|
1565
|
+
callState: (err, exit) => /* @__PURE__ */ React.createElement("span", { className: "lc-ts-call-state " + (err ? "lc-ts-call-err" : "lc-ts-call-ok") }, /* @__PURE__ */ React.createElement("i", null), err ? t("call.fail") + (exit !== null ? " · " + t("call.exit", { n: exit }) : "") : t("call.ok"))
|
|
1615
1566
|
},
|
|
1616
|
-
hint:
|
|
1617
|
-
}));
|
|
1567
|
+
hint: conv === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
|
|
1568
|
+
}), rowErr);
|
|
1618
1569
|
});
|
|
1619
1570
|
};
|
|
1620
1571
|
return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("browser.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-br-hint" }, t("browser.deltaHint")), /* @__PURE__ */ React.createElement("select", {
|
|
@@ -1669,12 +1620,9 @@ window.__ModuleLoader__.load({
|
|
|
1669
1620
|
//#endregion
|
|
1670
1621
|
//#region src/client/components/stackedBar.tsx
|
|
1671
1622
|
/**
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1675
|
-
* reached). DSH does not publish the configured ratio to plugins or clients,
|
|
1676
|
-
* so the UI mirrors the default here; deployments that tune `thresholdRatio`
|
|
1677
|
-
* / `modelPolicies` can adjust it if they want the reserve band to match.
|
|
1623
|
+
* Mirror of dsh-compaction-basic's default `thresholdRatio` (0.8): it compacts at step boundaries once `floor(contextWindow × ratio)` is
|
|
1624
|
+
* reached; DSH does not publish the configured ratio to plugins/clients, so the reserve band mirrors the default — deployments tuning
|
|
1625
|
+
* `thresholdRatio`/`modelPolicies` should adjust it to match.
|
|
1678
1626
|
*/
|
|
1679
1627
|
const AUTO_COMPACT_RATIO = .8;
|
|
1680
1628
|
function makeStackedBar(kit) {
|
|
@@ -1945,7 +1893,6 @@ window.__ModuleLoader__.load({
|
|
|
1945
1893
|
}
|
|
1946
1894
|
//#endregion
|
|
1947
1895
|
//#region src/client/components/settingsCard.tsx
|
|
1948
|
-
/** One preference row: label + selector pill opening the framework Menu. */
|
|
1949
1896
|
function PrefRow(props) {
|
|
1950
1897
|
const [open, setOpen] = React.useState(false);
|
|
1951
1898
|
const active = props.options.find((o) => o.id === props.value)?.label ?? props.value;
|
|
@@ -2025,7 +1972,6 @@ window.__ModuleLoader__.load({
|
|
|
2025
1972
|
}
|
|
2026
1973
|
//#endregion
|
|
2027
1974
|
//#region src/client/settings.ts
|
|
2028
|
-
/** Read the known preferences out of a raw section; unknown/absent fields drop out. */
|
|
2029
1975
|
function prefsOf(value) {
|
|
2030
1976
|
if (value === null || typeof value !== "object") return {};
|
|
2031
1977
|
const v = value;
|
|
@@ -2093,7 +2039,6 @@ window.__ModuleLoader__.load({
|
|
|
2093
2039
|
model: "⇄",
|
|
2094
2040
|
mode: "⇄"
|
|
2095
2041
|
};
|
|
2096
|
-
/** Build the event text helpers bound to the translate function. */
|
|
2097
2042
|
function makeEventText(t) {
|
|
2098
2043
|
function eventLabel(ev) {
|
|
2099
2044
|
if (ev.kind === "compaction") return t("ev.compaction", { n: ev.count || 0 });
|
|
@@ -2110,11 +2055,8 @@ window.__ModuleLoader__.load({
|
|
|
2110
2055
|
return label;
|
|
2111
2056
|
}
|
|
2112
2057
|
/**
|
|
2113
|
-
* Where this event sits in the
|
|
2114
|
-
*
|
|
2115
|
-
* "Step 2→3", cross-turn "Turn 50 · Step 8 → Turn 51 · Step 1". Injections
|
|
2116
|
-
* and model switches belong to one request and keep the single point.
|
|
2117
|
-
* Events with no following request (in flight) stay unlabeled.
|
|
2058
|
+
* Where this event sits in the timeline: boundary events (compaction/prune) label the GAP they sit in — same-turn 'Turn 2 · Step 3→4',
|
|
2059
|
+
* cross-turn 'Turn 50 · Step 8 → Turn 51 · Step 1'; other kinds keep their single point; no turn/step (in flight) → null.
|
|
2118
2060
|
*/
|
|
2119
2061
|
function eventAt(ev) {
|
|
2120
2062
|
if (ev.kind === "compaction" || ev.kind === "prune") {
|
|
@@ -2183,20 +2125,14 @@ window.__ModuleLoader__.load({
|
|
|
2183
2125
|
};
|
|
2184
2126
|
}
|
|
2185
2127
|
const PLUGIN_REPO = "https://github.com/bowenliang123/dsh-context";
|
|
2186
|
-
/** Short `owner/repo` form of the GitHub URL, for display only. */
|
|
2187
2128
|
const PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
|
|
2188
2129
|
//#endregion
|
|
2189
2130
|
//#region src/client/latestVersion.ts
|
|
2190
2131
|
/**
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2193
|
-
* card mount and cached with a 1-hour TTL, so the registry is hit at most
|
|
2194
|
-
* once per hour per page. Every failure mode (offline, CSP-blocked,
|
|
2195
|
-
* non-200, malformed body) narrows to null = no upgrade hint, so the card
|
|
2196
|
-
* silently keeps its static version.
|
|
2132
|
+
* One live check for the Plugin-info card: lazy with 1h TTL; every failure
|
|
2133
|
+
* mode narrows to null so the card silently keeps its static version.
|
|
2197
2134
|
*/
|
|
2198
2135
|
const REGISTRY_URL = "https://registry.npmjs.org/dsh-context/latest";
|
|
2199
|
-
/** How long a registry result stays fresh before the next check refetches. */
|
|
2200
2136
|
const TTL_MS = 36e5;
|
|
2201
2137
|
let cached = null;
|
|
2202
2138
|
function fetchLatestVersion() {
|
|
@@ -2231,7 +2167,7 @@ window.__ModuleLoader__.load({
|
|
|
2231
2167
|
return function PluginInfo() {
|
|
2232
2168
|
const [latest, setLatest] = React.useState(null);
|
|
2233
2169
|
React.useEffect(() => {
|
|
2234
|
-
if ("0.
|
|
2170
|
+
if ("0.30.0".includes("-dev")) return;
|
|
2235
2171
|
let on = true;
|
|
2236
2172
|
fetchLatestVersion().then((v) => {
|
|
2237
2173
|
if (on && v) setLatest(v);
|
|
@@ -2240,8 +2176,8 @@ window.__ModuleLoader__.load({
|
|
|
2240
2176
|
on = false;
|
|
2241
2177
|
};
|
|
2242
2178
|
}, []);
|
|
2243
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
2244
|
-
const nameValue = ["dsh-context (v0.
|
|
2179
|
+
const update = latest !== null && isNewerVersion(latest, "0.30.0") ? latest : null;
|
|
2180
|
+
const nameValue = ["dsh-context (v0.30.0)"];
|
|
2245
2181
|
if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
|
|
2246
2182
|
key: "update",
|
|
2247
2183
|
className: "lc-pi-update"
|
|
@@ -2366,7 +2302,6 @@ window.__ModuleLoader__.load({
|
|
|
2366
2302
|
}
|
|
2367
2303
|
return any ? total : null;
|
|
2368
2304
|
}
|
|
2369
|
-
/** Short money format: two decimals at and above one unit, two significant digits below. */
|
|
2370
2305
|
function formatCost(amount, currency) {
|
|
2371
2306
|
return (currency === "cny" ? "¥" : "$") + (amount >= 1 ? amount.toFixed(2) : amount.toPrecision(2));
|
|
2372
2307
|
}
|
|
@@ -2391,15 +2326,10 @@ window.__ModuleLoader__.load({
|
|
|
2391
2326
|
//#endregion
|
|
2392
2327
|
//#region src/client/components/statsBoard.tsx
|
|
2393
2328
|
/**
|
|
2394
|
-
* Cache-hit share of billed prompt-side input — same buckets as the harness
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2397
|
-
*
|
|
2398
|
-
* round). Null when no input was billed. A tiny epsilon keeps a double stored
|
|
2399
|
-
* a hair below a cent boundary (e.g. 80.00 as 79.9999999999…) from losing its
|
|
2400
|
-
* last digit — with integer token counts no genuine value ever sits that close
|
|
2401
|
-
* to a boundary, so the epsilon can only absorb float noise. Bucket reads go
|
|
2402
|
-
* through numOf so a malformed payload degrades to a dash instead of NaN text.
|
|
2329
|
+
* Cache-hit share of billed prompt-side input — same three disjoint buckets as the harness chat line (`cacheReadTokens` over uncached +
|
|
2330
|
+
* reads + writes), but deliberately TRUNCATES to two decimals (cut, not round), unlike that line's integer rounding; null when nothing was
|
|
2331
|
+
* billed. The 1e-9 epsilon absorbs only float noise (integer token counts never sit that close to a boundary); `numOf` keeps malformed
|
|
2332
|
+
* payloads at a dash.
|
|
2403
2333
|
*/
|
|
2404
2334
|
function cacheHitPercent(usage) {
|
|
2405
2335
|
const uncached = numOf(usage.uncachedInputTokens);
|
|
@@ -2446,12 +2376,8 @@ window.__ModuleLoader__.load({
|
|
|
2446
2376
|
//#endregion
|
|
2447
2377
|
//#region src/client/components/trendChart.tsx
|
|
2448
2378
|
/**
|
|
2449
|
-
* Collapse
|
|
2450
|
-
*
|
|
2451
|
-
* finished), tagged with the number of steps the turn spans so the bar can
|
|
2452
|
-
* keep the turn's column width and the detail can label it. Requests of one
|
|
2453
|
-
* turn are consecutive in the log, so a run of equal turns is replaced by
|
|
2454
|
-
* its final record.
|
|
2379
|
+
* Collapse per-step requests into one bar per turn — each turn is represented by its LAST step's record, tagged `stepCount` for the bar's
|
|
2380
|
+
* column width; the log keeps one turn's requests consecutive, so a run of equal turns collapses to its final record.
|
|
2455
2381
|
*/
|
|
2456
2382
|
function aggregateByTurn(requests) {
|
|
2457
2383
|
const out = [];
|
|
@@ -2475,10 +2401,8 @@ window.__ModuleLoader__.load({
|
|
|
2475
2401
|
return out;
|
|
2476
2402
|
}
|
|
2477
2403
|
/**
|
|
2478
|
-
* Attach each boundary event (compaction/prune) to the first request
|
|
2479
|
-
*
|
|
2480
|
-
* the bar and the range chip in the detail header. Shared by TrendChart
|
|
2481
|
-
* and the detail panel so both show the SAME event for a request.
|
|
2404
|
+
* Attach each boundary event (compaction/prune) to the first request logged after it — one entry per index, for the ✂ marker and the detail
|
|
2405
|
+
* chip; shared with the detail panel so both show the SAME event.
|
|
2482
2406
|
*/
|
|
2483
2407
|
function attachMarkers(requests, events) {
|
|
2484
2408
|
const markers = new Array(requests.length);
|
|
@@ -2500,14 +2424,8 @@ window.__ModuleLoader__.load({
|
|
|
2500
2424
|
const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
|
|
2501
2425
|
const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
|
|
2502
2426
|
/**
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2505
|
-
* change, stacked like the cumulative bars), `total` becomes the summed
|
|
2506
|
-
* magnitude, and `net` keeps the signed change for the tooltip. The first
|
|
2507
|
-
* request (no previous) diff from empty is zero, so the diff mode's scale
|
|
2508
|
-
* is driven purely by inter-request changes rather than the initial
|
|
2509
|
-
* cumulative baseline. Provider prompt/output are dropped: they are
|
|
2510
|
-
* per-request values, not diffs.
|
|
2427
|
+
* Diff mode: each category becomes |current − previous|, `total` the summed magnitude, `net` the signed change for the tooltip; the first
|
|
2428
|
+
* request diffs from zero so the scale is change-driven, and per-request provider prompt/output are dropped (they are not diffs).
|
|
2511
2429
|
*/
|
|
2512
2430
|
const diffOf = (req, prev) => {
|
|
2513
2431
|
const out = { ...req };
|
|
@@ -2577,6 +2495,17 @@ window.__ModuleLoader__.load({
|
|
|
2577
2495
|
grp.count++;
|
|
2578
2496
|
grp.span += req.stepCount ?? 1;
|
|
2579
2497
|
}
|
|
2498
|
+
const turnOffsets = [];
|
|
2499
|
+
const turnWidths = [];
|
|
2500
|
+
{
|
|
2501
|
+
let x = 0;
|
|
2502
|
+
for (const grp of groups) {
|
|
2503
|
+
const w = grp.agg ? BAR_W : grp.span * 16 - BAR_GAP;
|
|
2504
|
+
turnOffsets.push(x);
|
|
2505
|
+
turnWidths.push(w);
|
|
2506
|
+
x += w + BAR_GAP;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2580
2509
|
const scrollRef = React.useRef(null);
|
|
2581
2510
|
const scrolledOnce = React.useRef(false);
|
|
2582
2511
|
const lastGranRef = React.useRef(props.granularity);
|
|
@@ -2599,6 +2528,35 @@ window.__ModuleLoader__.load({
|
|
|
2599
2528
|
right
|
|
2600
2529
|
});
|
|
2601
2530
|
};
|
|
2531
|
+
/**
|
|
2532
|
+
* Keep each turn label centered within its block's VISIBLE slice so it never scrolls out while any part of the block is on screen
|
|
2533
|
+
* (narrower-than-label blocks stay put); reads (offsetWidth) batch before writes (transform) to avoid layout thrash — out-of-view
|
|
2534
|
+
* blocks need no measurement.
|
|
2535
|
+
*/
|
|
2536
|
+
const updateTurnLabels = (el) => {
|
|
2537
|
+
const labels = el.querySelectorAll(".lc-turn-label");
|
|
2538
|
+
const n = Math.min(labels.length, turnOffsets.length);
|
|
2539
|
+
const sl = el.scrollLeft;
|
|
2540
|
+
const vr = sl + el.clientWidth;
|
|
2541
|
+
const writes = [];
|
|
2542
|
+
for (let i = 0; i < n; i++) {
|
|
2543
|
+
const off = turnOffsets[i];
|
|
2544
|
+
const w = turnWidths[i];
|
|
2545
|
+
const visL = Math.max(off, sl);
|
|
2546
|
+
const visR = Math.min(off + w, vr);
|
|
2547
|
+
let dx = 0;
|
|
2548
|
+
if (visR > visL) {
|
|
2549
|
+
const lw = labels[i].offsetWidth;
|
|
2550
|
+
if (lw < w) {
|
|
2551
|
+
const center = (visL + visR) / 2 - off;
|
|
2552
|
+
dx = Math.min(Math.max(center, lw / 2), w - lw / 2) - w / 2;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
const next = dx !== 0 ? `translateX(${dx}px)` : "";
|
|
2556
|
+
if (labels[i].style.transform !== next) writes.push([labels[i], next]);
|
|
2557
|
+
}
|
|
2558
|
+
for (const [label, next] of writes) label.style.transform = next;
|
|
2559
|
+
};
|
|
2602
2560
|
React.useLayoutEffect(() => {
|
|
2603
2561
|
const el = scrollRef.current;
|
|
2604
2562
|
if (el === null) return;
|
|
@@ -2619,6 +2577,7 @@ window.__ModuleLoader__.load({
|
|
|
2619
2577
|
el.scrollLeft = el.scrollWidth;
|
|
2620
2578
|
} else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth;
|
|
2621
2579
|
updateEdges(el);
|
|
2580
|
+
updateTurnLabels(el);
|
|
2622
2581
|
});
|
|
2623
2582
|
const tipOf = (req) => {
|
|
2624
2583
|
const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", {
|
|
@@ -2641,6 +2600,7 @@ window.__ModuleLoader__.load({
|
|
|
2641
2600
|
ref: scrollRef,
|
|
2642
2601
|
onScroll: (e) => {
|
|
2643
2602
|
updateEdges(e.currentTarget);
|
|
2603
|
+
updateTurnLabels(e.currentTarget);
|
|
2644
2604
|
}
|
|
2645
2605
|
}, edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null, /* @__PURE__ */ React.createElement("div", {
|
|
2646
2606
|
className: "lc-chart",
|
|
@@ -2667,12 +2627,11 @@ window.__ModuleLoader__.load({
|
|
|
2667
2627
|
}
|
|
2668
2628
|
}, groups.map((grp, gi) => {
|
|
2669
2629
|
const on = props.activeTurn === grp.turn;
|
|
2670
|
-
const blockW = grp.agg ? BAR_W : grp.span * 16 - BAR_GAP;
|
|
2671
2630
|
return /* @__PURE__ */ React.createElement("span", {
|
|
2672
2631
|
key: `turn-${gi}`,
|
|
2673
2632
|
className: "lc-turn" + (on ? " lc-turn-on" : ""),
|
|
2674
2633
|
style: {
|
|
2675
|
-
width: `${
|
|
2634
|
+
width: `${turnWidths[gi]}px`,
|
|
2676
2635
|
background: TURN_FILLS[gi % TURN_FILLS.length]
|
|
2677
2636
|
},
|
|
2678
2637
|
title: `T${grp.turn}`,
|
|
@@ -2682,7 +2641,7 @@ window.__ModuleLoader__.load({
|
|
|
2682
2641
|
onClick: () => {
|
|
2683
2642
|
props.onPickTurn(grp.turn);
|
|
2684
2643
|
}
|
|
2685
|
-
}, `T${grp.turn}`);
|
|
2644
|
+
}, /* @__PURE__ */ React.createElement("span", { className: "lc-turn-label" }, `T${grp.turn}`));
|
|
2686
2645
|
}))), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
|
|
2687
2646
|
};
|
|
2688
2647
|
}
|
|
@@ -2906,7 +2865,7 @@ window.__ModuleLoader__.load({
|
|
|
2906
2865
|
}
|
|
2907
2866
|
//#endregion
|
|
2908
2867
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles.css.mjs
|
|
2909
|
-
const css = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-stats{grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);cursor:help;border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:280px;color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:6px 10px;font-size:12px;font-weight:400;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;transition:border-color .16s,background .16s}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-select:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.lc-settings-select:disabled{opacity:.5;cursor:default}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stacked-wrap{width:100%;position:relative}.lc-stacked{background:#8080802e;border-radius:5px;width:100%;display:flex;position:relative;overflow:hidden}.lc-occupied-box{border:2px solid var(--dsw-alias-label-tertiary);box-sizing:border-box;pointer-events:none;opacity:0;box-shadow:0 0 0 1px var(--dsw-alias-bg-layer-2);border-radius:5px;transition:opacity .12s;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:3px 8px;font-size:12px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg{transition:filter .12s,opacity .12s}.lc-stacked-free{box-sizing:border-box;transition:box-shadow .12s,opacity .12s}.lc-stacked-seg-on{filter:brightness(1.18)}.lc-reserve{z-index:1;pointer-events:auto;cursor:help;background:repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-state-warn-primary) 24%, transparent) 0 5px, transparent 5px 10px);position:absolute;top:0;bottom:0}.lc-stacked-free-on{border:2px dashed var(--dsw-alias-label-secondary);border-radius:3px}.lc-stacked-dim .lc-stacked-seg{opacity:.35}.lc-stacked-dim .lc-stacked-seg-on{opacity:1}.lc-stacked-dim .lc-stacked-free{opacity:.35}.lc-stacked-dim .lc-stacked-free-on{opacity:1}.lc-legend{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px 14px;margin-top:10px;display:grid}.lc-chip{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:5px;padding:1px 6px;transition:background-color .12s;display:flex}.lc-chip-label{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}.lc-chip-nums{white-space:nowrap;flex:none;align-items:baseline;gap:6px;margin-left:auto;display:inline-flex}.lc-chip i,.lc-detail-row i,.lc-node i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow .12s}.lc-chip em{color:var(--dsw-alias-label-secondary);font-style:normal}.lc-chip-on{background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.lc-chip-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-tools{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;margin-top:10px;display:flex}.lc-tools-label,.lc-tools-more{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;padding:0;font-family:inherit;font-size:12px}.lc-tools-label:hover,.lc-tools-more:hover{text-decoration:underline}.lc-tool-chip{background:var(--dsw-alias-bg-layer-2);font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:0;border-radius:4px;padding:1px 7px;font-size:12px}.lc-tool-chip:hover{text-decoration:underline}.lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-mid{top:69px}.lc-axis-bot{top:125px}.lc-chart-scroll{scrollbar-width:thin;flex:1;min-width:0;padding-bottom:8px;position:relative;overflow:auto hidden}.lc-chart-fade{pointer-events:none;z-index:2;width:26px;position:absolute;top:0;bottom:0}.lc-chart-fade-l{background:linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent);left:0}.lc-chart-fade-r{background:linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent);right:0}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l1);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-mid{top:74px}.lc-bar{cursor:pointer;border-radius:2px;flex:none;align-items:flex-end;width:14px;height:100%;transition:opacity .12s,background-color .12s;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;padding:3px 8px;font-size:12px;position:absolute;top:0;transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline:1px dashed var(--dsw-alias-brand-primary);outline-offset:1px}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:#80808024}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-turns{gap:2px;width:max-content;min-width:100%;margin-top:4px;display:flex}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:3px;flex:none;height:14px;font-size:10px;font-weight:600;line-height:14px;transition:filter .12s,opacity .12s;overflow:hidden}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-detail{border-top:1px solid var(--dsw-alias-border-l1);margin-top:12px;padding-top:12px}.lc-detail-head{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary)}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-bg-layer-2);border-radius:6px;padding:1px 7px;font-size:11px}.lc-detail-head .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-detail-tag{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:0 6px;font-size:11px}.lc-detail-rows{grid-template-columns:1fr 1fr;gap:4px 24px;margin-top:10px;display:grid}.lc-detail-row{align-items:center;gap:8px;display:flex}.lc-detail-label{white-space:nowrap;min-width:70px;color:var(--dsw-alias-label-secondary)}.lc-bar-track{background:#8080802e;border-radius:3px;flex:1;height:5px;display:block;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-detail-num{text-align:right;width:44px}.lc-detail-pct{text-align:right;width:34px;color:var(--dsw-alias-label-secondary)}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card:first-child{flex:7 1 0;min-width:360px}.lc-head>.lc-card:last-child{flex:3 1 0;min-width:220px}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-events,.lc-nodes{flex-direction:column;gap:2px;max-height:320px;display:flex;overflow-y:auto}.lc-event{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-event-icon{text-align:center;width:18px;color:var(--dsw-alias-state-warn-primary)}.lc-event-icon.lc-event-inject{color:#a855f7}.lc-event-icon.lc-event-model{color:var(--dsw-alias-brand-primary)}.lc-event-icon.lc-event-mode{color:var(--dsw-alias-label-secondary)}.lc-kind{white-space:nowrap;border-radius:4px;flex:none;padding:1px 6px;font-size:10px;font-weight:600}.lc-kind-inject{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent);color:var(--dsw-alias-state-success-primary)}.lc-kind-compaction{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent);color:var(--dsw-alias-state-error-primary)}.lc-kind-prune{color:#8b5cf6;background:#8b5cf626}.lc-kind-model{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent);color:var(--dsw-alias-brand-primary)}.lc-kind-mode{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 15%, transparent);color:var(--dsw-alias-label-secondary)}.lc-event-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.lc-event-at{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;font-size:11px}.lc-event-tokens{color:var(--dsw-alias-state-success-primary);white-space:nowrap;font-weight:600}.lc-event-tokens.lc-up{color:var(--dsw-alias-state-warn-primary)}.lc-event-time{color:var(--dsw-alias-label-secondary);font-size:12px}.lc-node{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-node-preview{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);flex:1;overflow:hidden}.lc-node-time{color:var(--dsw-alias-label-secondary);text-align:right;min-width:54px;font-size:12px}.lc-node-tokens{color:var(--dsw-alias-label-secondary)}.lc-nodes-more{color:var(--dsw-alias-label-secondary);padding:3px 0}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.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;cursor:pointer;border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}.lc-modal-backdrop{z-index:200;background:#00000073;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.lc-br-hint{color:var(--dsw-alias-label-secondary);white-space:nowrap;margin-left:auto;font-size:11px;font-weight:400}.lc-br-pick{font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;max-width:240px;padding:3px 6px;font-size:12px}.lc-br-meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-br-meta b{color:var(--dsw-alias-label-primary)}.lc-br-meta .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-br-note{color:var(--dsw-alias-label-secondary);margin-top:8px;font-size:12px}.lc-br-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;overflow:hidden}.lc-br-cat-empty{opacity:.55}.lc-br-cat-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;transition:background-color .12s;display:flex}.lc-br-cat-row:hover,.lc-br-cat-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-cat-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-br-cat-row i{border-radius:2px;flex:none;width:8px;height:8px;transition:box-shadow .12s;display:inline-block}.lc-br-cat-label{font-weight:600}.lc-br-cat-count{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}.lc-br-count-grp{flex:1;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-chev{width:12px;color:var(--dsw-alias-label-secondary);flex:none;transition:transform .12s}.lc-br-chev-on{transform:rotate(90deg)}.lc-br-tokens{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-delta,.lc-br-tdelta{white-space:nowrap;border-radius:4px;flex:none;padding:0 5px;font-size:11px;font-weight:600}.lc-br-delta-up,.lc-br-tdelta-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-br-delta-down,.lc-br-tdelta-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-br-tokens-grp{flex:none;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-pct{text-align:right;width:36px;color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-body{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:2px;padding:4px 6px;display:flex}.lc-br-elem{border-radius:6px}.lc-br-elem-on{background:var(--dsw-alias-interactive-bg-active)}.lc-br-elem-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;transition:background-color .12s;display:flex}.lc-br-elem-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-tag{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);text-overflow:ellipsis;white-space:nowrap;border-radius:4px;flex:none;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.lc-br-preview{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-br-time{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.lc-br-content{flex-direction:column;gap:6px;padding:2px 6px 8px 26px;display:flex}.lc-br-dim{color:var(--dsw-alias-label-secondary)}.lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;overflow:hidden}.lc-ts-card-head{color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:6px 10px;font-size:11px;font-weight:600;display:flex}.lc-ts-card-head b{color:var(--dsw-alias-label-primary)}.lc-ts-call-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.lc-ts-card-count{margin-left:auto}.lc-ts-desc-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:320px;margin:0;padding:8px 10px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:140px 90px 56px 1fr;align-items:baseline;gap:2px 10px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-param-row:first-of-type{border-top:0}.lc-ts-param-name{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.lc-ts-param-type{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.lc-ts-param-req{color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600}.lc-ts-param-req-off{color:var(--dsw-alias-label-secondary);font-size:11px}.lc-ts-param-desc{color:var(--dsw-alias-label-secondary);grid-column:1/-1;line-height:1.5}.lc-ts-params-empty{color:var(--dsw-alias-label-secondary);padding:8px 10px;font-size:12px}.lc-ts-arg-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,96px) 1fr;align-items:start;column-gap:12px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-arg-row:first-of-type{border-top:0}.lc-ts-arg-row .lc-ts-param-name{text-overflow:unset;white-space:normal;overflow-wrap:anywhere;overflow:visible}.lc-ts-arg-val{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-json{flex-direction:column;gap:4px;display:flex}.lc-ts-json-toggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-self:flex-start;padding:0;font-size:11px}.lc-ts-json-toggle:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.lc-rich-seg{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-rich-seg-btn:hover{color:var(--dsw-alias-label-primary)}.lc-rich-seg-on,.lc-rich-seg-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-ts-desc-md{color:var(--dsw-alias-label-primary);word-break:break-word;scrollbar-width:thin;max-height:320px;padding:4px 10px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-desc-md>div{font-size:13px;line-height:1.55}.lc-ts-desc-md div :is(h1,h2,h3,h4,h5,h6){margin:10px 0 4px;font-size:13px;font-weight:600}.lc-ts-desc-md div p,.lc-ts-desc-md div pre{margin:6px 0}.lc-ts-desc-md div :not(pre)>code{box-decoration-break:clone;padding:1px 5px;line-height:1.3;font-size:1em!important}.lc-ts-desc-md div table code{font-size:inherit}.lc-att-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:8px 10px;display:grid}.lc-att-item{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);width:100%;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border-radius:8px;align-items:center;gap:8px;padding:6px;display:flex}.lc-att-item:hover{border-color:var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}.lc-att-thumb{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex:none;justify-content:center;align-items:center;width:64px;height:64px;padding:0;display:flex;overflow:hidden}.lc-att-thumb img{object-fit:cover;width:100%;height:100%;display:block}.lc-att-ph,.lc-att-err{color:var(--dsw-alias-label-secondary);text-align:center;padding:2px;font-size:10px}.lc-att-meta{flex-direction:column;gap:2px;min-width:0;display:flex}.lc-att-name{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;word-break:break-word;font-size:12px;line-height:1.3}.lc-att-row{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:11px}.lc-att-row-label{min-width:3.2em;font-weight:400;display:inline-block}.lc-att-lightbox{z-index:1000;place-items:center;padding:40px;display:grid;position:fixed;inset:0}.lc-att-lightbox-mask{background:var(--dsw-alias-bg-mask-1,#00000073);backdrop-filter:var(--dsw-mask-blur,blur(2px));position:absolute;inset:0}.lc-att-lightbox-img{object-fit:contain;background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));max-width:min(100%,1600px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);border-radius:12px;position:relative}.lc-att-lightbox-close{z-index:1;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,var(--dsw-alias-border-l1));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));width:36px;height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;position:fixed;top:20px;right:20px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
|
|
2868
|
+
const css = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-stats{grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);cursor:help;border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:280px;color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:6px 10px;font-size:12px;font-weight:400;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;transition:border-color .16s,background .16s}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-select:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.lc-settings-select:disabled{opacity:.5;cursor:default}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stacked-wrap{width:100%;position:relative}.lc-stacked{background:#8080802e;border-radius:5px;width:100%;display:flex;position:relative;overflow:hidden}.lc-occupied-box{border:2px solid var(--dsw-alias-label-tertiary);box-sizing:border-box;pointer-events:none;opacity:0;box-shadow:0 0 0 1px var(--dsw-alias-bg-layer-2);border-radius:5px;transition:opacity .12s;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:3px 8px;font-size:12px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg{transition:filter .12s,opacity .12s}.lc-stacked-free{box-sizing:border-box;transition:box-shadow .12s,opacity .12s}.lc-stacked-seg-on{filter:brightness(1.18)}.lc-reserve{z-index:1;pointer-events:auto;cursor:help;background:repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-state-warn-primary) 24%, transparent) 0 5px, transparent 5px 10px);position:absolute;top:0;bottom:0}.lc-stacked-free-on{border:2px dashed var(--dsw-alias-label-secondary);border-radius:3px}.lc-stacked-dim .lc-stacked-seg{opacity:.35}.lc-stacked-dim .lc-stacked-seg-on{opacity:1}.lc-stacked-dim .lc-stacked-free{opacity:.35}.lc-stacked-dim .lc-stacked-free-on{opacity:1}.lc-legend{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px 14px;margin-top:10px;display:grid}.lc-chip{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:5px;padding:1px 6px;transition:background-color .12s;display:flex}.lc-chip-label{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}.lc-chip-nums{white-space:nowrap;flex:none;align-items:baseline;gap:6px;margin-left:auto;display:inline-flex}.lc-chip i,.lc-detail-row i,.lc-node i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow .12s}.lc-chip em{color:var(--dsw-alias-label-secondary);font-style:normal}.lc-chip-on{background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.lc-chip-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-tools{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;margin-top:10px;display:flex}.lc-tools-label,.lc-tools-more{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;padding:0;font-family:inherit;font-size:12px}.lc-tools-label:hover,.lc-tools-more:hover{text-decoration:underline}.lc-tool-chip{background:var(--dsw-alias-bg-layer-2);font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:0;border-radius:4px;padding:1px 7px;font-size:12px}.lc-tool-chip:hover{text-decoration:underline}.lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-mid{top:69px}.lc-axis-bot{top:125px}.lc-chart-scroll{scrollbar-width:thin;flex:1;min-width:0;padding-bottom:8px;position:relative;overflow:auto hidden}.lc-chart-fade{pointer-events:none;z-index:2;width:26px;position:absolute;top:0;bottom:0}.lc-chart-fade-l{background:linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent);left:0}.lc-chart-fade-r{background:linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent);right:0}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l1);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-mid{top:74px}.lc-bar{cursor:pointer;border-radius:2px;flex:none;align-items:flex-end;width:14px;height:100%;transition:opacity .12s,background-color .12s;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;padding:3px 8px;font-size:12px;position:absolute;top:0;transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline:1px dashed var(--dsw-alias-brand-primary);outline-offset:1px}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:#80808024}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-turns{gap:2px;width:max-content;min-width:100%;margin-top:4px;display:flex}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:3px;flex:none;height:14px;font-size:10px;font-weight:600;line-height:14px;transition:filter .12s,opacity .12s;overflow:hidden}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{text-overflow:ellipsis;max-width:100%;display:inline-block;overflow:hidden}.lc-detail{border-top:1px solid var(--dsw-alias-border-l1);margin-top:12px;padding-top:12px}.lc-detail-head{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary)}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-bg-layer-2);border-radius:6px;padding:1px 7px;font-size:11px}.lc-detail-head .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-detail-tag{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:0 6px;font-size:11px}.lc-detail-rows{grid-template-columns:1fr 1fr;gap:4px 24px;margin-top:10px;display:grid}.lc-detail-row{align-items:center;gap:8px;display:flex}.lc-detail-label{white-space:nowrap;min-width:70px;color:var(--dsw-alias-label-secondary)}.lc-bar-track{background:#8080802e;border-radius:3px;flex:1;height:5px;display:block;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-detail-num{text-align:right;width:44px}.lc-detail-pct{text-align:right;width:34px;color:var(--dsw-alias-label-secondary)}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card:first-child{flex:7 1 0;min-width:360px}.lc-head>.lc-card:last-child{flex:3 1 0;min-width:220px}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-events,.lc-nodes{flex-direction:column;gap:2px;max-height:320px;display:flex;overflow-y:auto}.lc-event{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-event-icon{text-align:center;width:18px;color:var(--dsw-alias-state-warn-primary)}.lc-event-icon.lc-event-inject{color:#a855f7}.lc-event-icon.lc-event-model{color:var(--dsw-alias-brand-primary)}.lc-event-icon.lc-event-mode{color:var(--dsw-alias-label-secondary)}.lc-kind{white-space:nowrap;border-radius:4px;flex:none;padding:1px 6px;font-size:10px;font-weight:600}.lc-kind-inject{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent);color:var(--dsw-alias-state-success-primary)}.lc-kind-compaction{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent);color:var(--dsw-alias-state-error-primary)}.lc-kind-prune{color:#8b5cf6;background:#8b5cf626}.lc-kind-model{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent);color:var(--dsw-alias-brand-primary)}.lc-kind-mode{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 15%, transparent);color:var(--dsw-alias-label-secondary)}.lc-event-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.lc-event-at{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;font-size:11px}.lc-event-tokens{color:var(--dsw-alias-state-success-primary);white-space:nowrap;font-weight:600}.lc-event-tokens.lc-up{color:var(--dsw-alias-state-warn-primary)}.lc-event-time{color:var(--dsw-alias-label-secondary);font-size:12px}.lc-node{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-node-preview{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);flex:1;overflow:hidden}.lc-node-time{color:var(--dsw-alias-label-secondary);text-align:right;min-width:54px;font-size:12px}.lc-node-tokens{color:var(--dsw-alias-label-secondary)}.lc-nodes-more{color:var(--dsw-alias-label-secondary);padding:3px 0}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.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;cursor:pointer;border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}.lc-modal-backdrop{z-index:200;background:#00000073;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.lc-br-hint{color:var(--dsw-alias-label-secondary);white-space:nowrap;margin-left:auto;font-size:11px;font-weight:400}.lc-br-pick{font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;max-width:240px;padding:3px 6px;font-size:12px}.lc-br-meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-br-meta b{color:var(--dsw-alias-label-primary)}.lc-br-meta .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-br-note{color:var(--dsw-alias-label-secondary);margin-top:8px;font-size:12px}.lc-br-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;overflow:hidden}.lc-br-cat-empty{opacity:.55}.lc-br-cat-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;transition:background-color .12s;display:flex}.lc-br-cat-row:hover,.lc-br-cat-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-cat-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-br-cat-row i{border-radius:2px;flex:none;width:8px;height:8px;transition:box-shadow .12s;display:inline-block}.lc-br-cat-label{font-weight:600}.lc-br-cat-count{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}.lc-br-count-grp{flex:1;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-chev{width:12px;color:var(--dsw-alias-label-secondary);flex:none;transition:transform .12s}.lc-br-chev-on{transform:rotate(90deg)}.lc-br-tokens{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-delta,.lc-br-tdelta{white-space:nowrap;border-radius:4px;flex:none;padding:0 5px;font-size:11px;font-weight:600}.lc-br-delta-up,.lc-br-tdelta-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-br-delta-down,.lc-br-tdelta-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-br-tokens-grp{flex:none;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-pct{text-align:right;width:36px;color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-body{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:2px;padding:4px 6px;display:flex}.lc-br-elem{border-radius:6px}.lc-br-elem-on{background:var(--dsw-alias-interactive-bg-active)}.lc-br-elem-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;transition:background-color .12s;display:flex}.lc-br-elem-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-err-dot{background:var(--dsw-alias-state-error-primary);width:6px;height:6px;box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);border-radius:50%;flex:none}.lc-br-tag{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);text-overflow:ellipsis;white-space:nowrap;border-radius:4px;flex:none;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.lc-br-preview{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-br-time{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.lc-br-content{flex-direction:column;gap:6px;padding:2px 6px 8px 26px;display:flex}.lc-br-dim{color:var(--dsw-alias-label-secondary)}.lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;min-width:0;overflow:hidden}.lc-ts-card-head{color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:6px 10px;font-size:11px;font-weight:600;display:flex}.lc-ts-card-head b{color:var(--dsw-alias-label-primary)}.lc-ts-call-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.lc-ts-card-count{margin-left:auto}.lc-ts-card-right{align-items:center;gap:8px;margin-left:auto;display:inline-flex}.lc-ts-card-meta{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11px;font-weight:400}.lc-ts-call-state{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:4px;padding:2px 7px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;font-weight:600;line-height:1;display:inline-flex}.lc-ts-call-state i{background:currentColor;border-radius:50%;flex:none;width:6px;height:6px}.lc-ts-call-ok{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)}.lc-ts-call-err{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.lc-ts-desc-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:320px;margin:0;padding:8px 10px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:140px 90px 56px 1fr;align-items:baseline;gap:2px 10px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-param-row:first-of-type{border-top:0}.lc-ts-param-name{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.lc-ts-param-type{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.lc-ts-param-req{color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600}.lc-ts-param-req-off{color:var(--dsw-alias-label-secondary);font-size:11px}.lc-ts-param-desc{color:var(--dsw-alias-label-secondary);grid-column:1/-1;line-height:1.5}.lc-ts-params-empty{color:var(--dsw-alias-label-secondary);padding:8px 10px;font-size:12px}.lc-ts-arg-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,96px) 1fr;align-items:start;column-gap:12px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-arg-row:first-of-type{border-top:0}.lc-ts-arg-row .lc-ts-param-name{text-overflow:unset;white-space:normal;overflow-wrap:anywhere;overflow:visible}.lc-ts-arg-val{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-json{flex-direction:column;gap:4px;display:flex}.lc-ts-json-toggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-self:flex-start;padding:0;font-size:11px}.lc-ts-json-toggle:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.lc-rich-seg{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-rich-seg-btn:hover{color:var(--dsw-alias-label-primary)}.lc-rich-seg-on,.lc-rich-seg-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-ts-desc-md{color:var(--dsw-alias-label-primary);word-break:break-word;overflow-wrap:anywhere;white-space:pre-line;scrollbar-width:thin;min-width:0;max-height:320px;padding:4px 10px;font-size:12px;line-height:1.55;overflow:hidden auto}.lc-ts-desc-md>div{font-size:13px;line-height:1.55}.lc-ts-desc-md div :is(h1,h2,h3,h4,h5,h6){margin:10px 0 4px;font-size:13px;font-weight:600}.lc-ts-desc-md div p{margin:6px 0}.lc-ts-desc-md div pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;margin:6px 0}.lc-ts-desc-md div :not(pre)>code{box-decoration-break:clone;padding:1px 5px;line-height:1.3;font-size:1em!important}.lc-ts-desc-md div table code{font-size:inherit}.lc-att-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:8px 10px;display:grid}.lc-att-item{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);width:100%;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border-radius:8px;align-items:center;gap:8px;padding:6px;display:flex}.lc-att-item:hover{border-color:var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}.lc-att-thumb{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex:none;justify-content:center;align-items:center;width:64px;height:64px;padding:0;display:flex;overflow:hidden}.lc-att-thumb img{object-fit:cover;width:100%;height:100%;display:block}.lc-att-ph,.lc-att-err{color:var(--dsw-alias-label-secondary);text-align:center;padding:2px;font-size:10px}.lc-att-meta{flex-direction:column;gap:2px;min-width:0;display:flex}.lc-att-name{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;word-break:break-word;font-size:12px;line-height:1.3}.lc-att-row{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:11px}.lc-att-row-label{min-width:3.2em;font-weight:400;display:inline-block}.lc-att-lightbox{z-index:1000;place-items:center;padding:40px;display:grid;position:fixed;inset:0}.lc-att-lightbox-mask{background:var(--dsw-alias-bg-mask-1,#00000073);backdrop-filter:var(--dsw-mask-blur,blur(2px));position:absolute;inset:0}.lc-att-lightbox-img{object-fit:contain;background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));max-width:min(100%,1600px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);border-radius:12px;position:relative}.lc-att-lightbox-close{z-index:1;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,var(--dsw-alias-border-l1));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));width:36px;height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;position:fixed;top:20px;right:20px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
|
|
2910
2869
|
const tagId = "dsh-context/styles.css";
|
|
2911
2870
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2912
2871
|
const tag = document.createElement("style");
|
package/lib/index.d.ts
CHANGED
|
@@ -2,29 +2,24 @@ import { ZodType, z } from "zod";
|
|
|
2
2
|
import { Session, SessionEvent } from "@deepseek-ai/dsh-session";
|
|
3
3
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
4
|
//#region src/host/config.d.ts
|
|
5
|
-
/** dsh-context host config. All fields optional; defaults below. */
|
|
6
5
|
interface Config {
|
|
7
6
|
/** Cap on kept per-step request records (the hard step backstop). */
|
|
8
7
|
maxRequestSteps?: number;
|
|
9
8
|
/** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
|
|
10
9
|
maxKeptTurns?: number;
|
|
11
|
-
/** Newest context-event records kept. */
|
|
12
10
|
maxEvents?: number;
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* surface far below it in healthy sessions, so the browser effectively
|
|
18
|
-
* lists EVERY live node; the bound stays as a backstop for pathological
|
|
19
|
-
* sessions (every projection push ships the whole value, ~150B per node).
|
|
12
|
+
* Served surface nodes (newest carry the signal; live inject nodes are pinned — they land first and are few). Deliberately generous:
|
|
13
|
+
* auto-compaction keeps healthy surfaces far below it, so the browser effectively lists every live node; the bound is a
|
|
14
|
+
* pathological-session backstop (each push ships the whole value, ~150B/node).
|
|
20
15
|
*/
|
|
21
16
|
maxNodes?: number;
|
|
22
17
|
/** Removed (shadowed) surface nodes kept for per-step reconstruction. */
|
|
23
18
|
maxArchiveNodes?: number;
|
|
24
19
|
}
|
|
25
20
|
/**
|
|
26
|
-
* The cordis `Config` validator: strict on keys, defaults on the schema fields
|
|
27
|
-
*
|
|
21
|
+
* The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
|
|
22
|
+
* block — defaults win).
|
|
28
23
|
*/
|
|
29
24
|
declare const Config: z.ZodPreprocess<z.ZodObject<{
|
|
30
25
|
maxRequestSteps: z.ZodDefault<z.ZodNumber>;
|
|
@@ -36,19 +31,10 @@ declare const Config: z.ZodPreprocess<z.ZodObject<{
|
|
|
36
31
|
//#endregion
|
|
37
32
|
//#region src/shared/types.d.ts
|
|
38
33
|
/**
|
|
39
|
-
* Shared wire contract — the snapshot model exchanged between the Host and
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `view()` payload of the `contextTimeline` session projection, registered on
|
|
44
|
-
* the harness's `ctx.sessionProjections` registry. The registry drives
|
|
45
|
-
* `apply(state, event)` over every committed session event, persists the state
|
|
46
|
-
* through `ctx.sessionProjectionCache`, and pushes the finished value to the
|
|
47
|
-
* browser as a `session/projection` frame (with a tail-page baseline), where
|
|
48
|
-
* the Client reads it through the framework-standard `useProjection` seat.
|
|
49
|
-
*
|
|
50
|
-
* TYPE-ONLY host-side module: both halves import these as `import type`, so
|
|
51
|
-
* nothing from here ever reaches the runtime bundles.
|
|
34
|
+
* Shared wire contract — the snapshot model exchanged between the Host and Client halves. Delivered as the `view()` payload of the
|
|
35
|
+
* `contextTimeline`/`contextHeaders` session projections (registered on `ctx.sessionProjections`; the registry pushes finished views as
|
|
36
|
+
* `session/projection` frames — see host/timeline.ts). TYPE-ONLY host-side module: both halves import these as `import type`, so nothing
|
|
37
|
+
* from here ever reaches the runtime bundles.
|
|
52
38
|
*/
|
|
53
39
|
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
54
40
|
interface SessionProjectionMap {
|
|
@@ -70,7 +56,6 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
|
70
56
|
contextHeaders: ContextHeaders;
|
|
71
57
|
}
|
|
72
58
|
}
|
|
73
|
-
/** The five priced context categories (plus system/tools handled separately). */
|
|
74
59
|
type Category = 'user' | 'inject' | 'assistant' | 'tool';
|
|
75
60
|
interface Snapshot {
|
|
76
61
|
ok: boolean;
|
|
@@ -93,15 +78,10 @@ interface Snapshot {
|
|
|
93
78
|
* instead. Kept optional for wire compatibility with older clients.
|
|
94
79
|
*/
|
|
95
80
|
occupancy?: {
|
|
96
|
-
/** Provider-reported prompt size of the most recent request (input + cache). */
|
|
97
81
|
pressureTokens?: number;
|
|
98
|
-
/** Heuristic total over the current model-visible surface. */
|
|
99
82
|
surfaceTokens: number;
|
|
100
|
-
/** `surfaceTokens` at the newest usage sample. */
|
|
101
83
|
sampledSurfaceTokens?: number;
|
|
102
|
-
/** pressureTokens + surface movement since the sample (clamped ≥ 0). */
|
|
103
84
|
projectedTokens?: number;
|
|
104
|
-
/** Newest recorded route capacity (last-wins). */
|
|
105
85
|
contextWindow?: number;
|
|
106
86
|
};
|
|
107
87
|
toolList: {
|
|
@@ -131,10 +111,9 @@ interface Snapshot {
|
|
|
131
111
|
*/
|
|
132
112
|
cost?: SessionCostUsage;
|
|
133
113
|
/**
|
|
134
|
-
* The served live surface: the newest `maxNodes` tail PLUS every live
|
|
135
|
-
*
|
|
136
|
-
* they are pinned
|
|
137
|
-
* browser could list none). Seq-ordered, oldest first.
|
|
114
|
+
* The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
|
|
115
|
+
* few,
|
|
116
|
+
* so they are pinned). Seq-ordered, oldest first.
|
|
138
117
|
*/
|
|
139
118
|
nodes: SurfaceNode[];
|
|
140
119
|
/** Live nodes not served (the overflow beyond `maxNodes`, minus pinned injects — see `nodes`). */
|
|
@@ -159,11 +138,8 @@ interface Snapshot {
|
|
|
159
138
|
archiveFloor?: number;
|
|
160
139
|
}
|
|
161
140
|
/**
|
|
162
|
-
* The `contextTimeline` projection's whole value — the same snapshot the
|
|
163
|
-
*
|
|
164
|
-
* pipeline. `ok` is always `true` here (a delivered projection is by
|
|
165
|
-
* definition available); it is kept for wire compatibility with the
|
|
166
|
-
* snapshot shape.
|
|
141
|
+
* The `contextTimeline` projection's whole value — the same snapshot the Client has always rendered. `ok` is always `true` here (a
|
|
142
|
+
* delivered projection is by definition available); kept for wire compatibility with the snapshot shape.
|
|
167
143
|
*/
|
|
168
144
|
type ContextTimeline = Snapshot;
|
|
169
145
|
/**
|
|
@@ -172,20 +148,14 @@ type ContextTimeline = Snapshot;
|
|
|
172
148
|
* session log, immune to the request/event retention bounds).
|
|
173
149
|
*/
|
|
174
150
|
interface CostBucketTotals {
|
|
175
|
-
/** Billed prompt tokens that missed the provider cache. */
|
|
176
151
|
uncached: number;
|
|
177
|
-
/** Billed prompt tokens served from the provider cache. */
|
|
178
152
|
cacheRead: number;
|
|
179
|
-
/** Billed prompt tokens written into the provider cache. */
|
|
180
153
|
cacheWrite: number;
|
|
181
|
-
/** Billed output tokens (reasoning included). */
|
|
182
154
|
output: number;
|
|
183
155
|
}
|
|
184
156
|
/** One model family's totals split by DeepSeek's pricing period (Beijing Time). */
|
|
185
157
|
interface CostFamilyUsage {
|
|
186
|
-
/** Peak windows: 09:00-12:00 and 14:00-18:00 Beijing Time, weekdays only. */
|
|
187
158
|
peak?: CostBucketTotals;
|
|
188
|
-
/** All other hours plus all of Saturday/Sunday (half the peak rate). */
|
|
189
159
|
off?: CostBucketTotals;
|
|
190
160
|
}
|
|
191
161
|
/**
|
|
@@ -202,7 +172,6 @@ interface SessionCostUsage {
|
|
|
202
172
|
/** One model-visible message on the surface, with its heuristic token price. */
|
|
203
173
|
interface SurfaceNode {
|
|
204
174
|
seq: number;
|
|
205
|
-
/** Event timestamp (ms epoch); the Client shows it when present. */
|
|
206
175
|
time?: number;
|
|
207
176
|
cat: Category;
|
|
208
177
|
tokens: number;
|
|
@@ -280,10 +249,7 @@ interface HeaderTool {
|
|
|
280
249
|
schema?: unknown;
|
|
281
250
|
}
|
|
282
251
|
/**
|
|
283
|
-
* One request-header epoch: the full system prompt and tool schemas in force
|
|
284
|
-
* from this event's seq until the next epoch. Headers change rarely (the loop
|
|
285
|
-
* only logs `request/header` on change), so this unit's pushes are rare and
|
|
286
|
-
* carrying full content is cheap.
|
|
252
|
+
* One request-header epoch: the full system prompt and tool schemas in force from this event's seq until the next epoch.
|
|
287
253
|
*/
|
|
288
254
|
interface HeaderRecord {
|
|
289
255
|
seq: number;
|
|
@@ -307,11 +273,9 @@ interface ContextHeaders {
|
|
|
307
273
|
* (not only when the raw step bound is), so the bounded state stays at the
|
|
308
274
|
* newest ~`maxKeptTurns` turns deterministically as a live log grows.
|
|
309
275
|
*/
|
|
310
|
-
/** The projection unit's persisted state (plain JSON, bounded see above). */
|
|
311
276
|
interface TimelineState {
|
|
312
277
|
/** Model-visible surface, newest last. */
|
|
313
278
|
surface: SurfaceNode[];
|
|
314
|
-
/** Live per-category token sums over the surface. */
|
|
315
279
|
sums: Record<Category, number>;
|
|
316
280
|
systemTokens: number;
|
|
317
281
|
toolsTokens: number;
|
|
@@ -351,7 +315,6 @@ interface TimelineState {
|
|
|
351
315
|
* Absent until a v4-flash / v4-pro request reports usage.
|
|
352
316
|
*/
|
|
353
317
|
cost?: SessionCostUsage;
|
|
354
|
-
/** Newest `gone` among archive entries dropped by the retention bounds. */
|
|
355
318
|
archiveFloor?: number;
|
|
356
319
|
/**
|
|
357
320
|
* Tool callId → name, armed by `tool/call` and DELETED when its
|
|
@@ -608,14 +571,12 @@ declare class SessionProjectionRegistry extends Service {
|
|
|
608
571
|
}
|
|
609
572
|
//#endregion
|
|
610
573
|
//#region src/host/headers.d.ts
|
|
611
|
-
/** The unit's persisted state (plain JSON, bounded). */
|
|
612
574
|
interface HeadersState {
|
|
613
575
|
headers: HeaderRecord[];
|
|
614
576
|
}
|
|
615
577
|
//#endregion
|
|
616
578
|
//#region src/host/index.d.ts
|
|
617
579
|
declare const name = "dsh-context";
|
|
618
|
-
/** Required services: the session-projection registry that drives the unit. */
|
|
619
580
|
declare const inject: string[];
|
|
620
581
|
declare function apply(ctx: Context, config: Config): void;
|
|
621
582
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,6 @@ import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
|
17
17
|
* only tune the fold's retention / presentation slice, so changing them never
|
|
18
18
|
* requires a projection `stateVersion` bump.
|
|
19
19
|
*/
|
|
20
|
-
/** Defaults — the exact bounds the fold used before they became configurable. */
|
|
21
20
|
const DEFAULT_BOUNDS = {
|
|
22
21
|
maxRequestSteps: 1500,
|
|
23
22
|
maxKeptTurns: 300,
|
|
@@ -26,8 +25,8 @@ const DEFAULT_BOUNDS = {
|
|
|
26
25
|
maxArchiveNodes: 400
|
|
27
26
|
};
|
|
28
27
|
/**
|
|
29
|
-
* The cordis `Config` validator: strict on keys, defaults on the schema fields
|
|
30
|
-
*
|
|
28
|
+
* The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
|
|
29
|
+
* block — defaults win).
|
|
31
30
|
*/
|
|
32
31
|
const Config = z.preprocess((v) => v ?? {}, z.object({
|
|
33
32
|
maxRequestSteps: z.number().int().min(1).default(DEFAULT_BOUNDS.maxRequestSteps),
|
|
@@ -36,7 +35,6 @@ const Config = z.preprocess((v) => v ?? {}, z.object({
|
|
|
36
35
|
maxNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxNodes),
|
|
37
36
|
maxArchiveNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxArchiveNodes)
|
|
38
37
|
}).strict());
|
|
39
|
-
/** Validate (and default) the entry config into concrete fold bounds. */
|
|
40
38
|
function resolveBounds(config) {
|
|
41
39
|
return Config.parse(config ?? {});
|
|
42
40
|
}
|
|
@@ -69,7 +67,6 @@ const MAX_WH_RATIO = 8;
|
|
|
69
67
|
const MIN_PIXELS = 147456;
|
|
70
68
|
const floorDiv = (a, b) => Math.floor(a / b);
|
|
71
69
|
const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
|
|
72
|
-
/** Token count of one patch grid (rows×cols) under the v4 layout rule. */
|
|
73
70
|
function gridTokens(rows, cols) {
|
|
74
71
|
let n = rows * (cols + 1) + 2;
|
|
75
72
|
if (rows % 2 === 1) n += cols + 1;
|
|
@@ -151,10 +148,8 @@ function calcResizeInner(width, height) {
|
|
|
151
148
|
return safeResize(h, w, paddedHeight, paddedWidth);
|
|
152
149
|
}
|
|
153
150
|
/**
|
|
154
|
-
* Estimate the tokens one image consumes in a DeepSeek vision request
|
|
155
|
-
*
|
|
156
|
-
* or when the official iteration fails to converge — callers fall back to
|
|
157
|
-
* the generic structural price.
|
|
151
|
+
* Estimate the tokens one image consumes in a DeepSeek vision request from its pixel dimensions. Returns null for non-positive/non-finite
|
|
152
|
+
* dimensions or when the official iteration fails to converge — callers fall back to the generic structural price.
|
|
158
153
|
*/
|
|
159
154
|
function estimateImageTokens(width, height) {
|
|
160
155
|
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
@@ -188,7 +183,6 @@ function estimateImageTokens(width, height) {
|
|
|
188
183
|
const CHARS_PER_TOKEN = 4;
|
|
189
184
|
const BLOCK_OVERHEAD = 4;
|
|
190
185
|
const ROLE_OVERHEAD = 4;
|
|
191
|
-
/** Whole-array tool-schema price (the header's tools total). */
|
|
192
186
|
function estimateToolsTotal(tools) {
|
|
193
187
|
return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
|
|
194
188
|
}
|
|
@@ -234,9 +228,8 @@ function estimateToolSchema(tool) {
|
|
|
234
228
|
return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
|
|
235
229
|
}
|
|
236
230
|
/**
|
|
237
|
-
* Count image blocks in a message payload, recursing into nested content
|
|
238
|
-
*
|
|
239
|
-
* whole-session image-file cell.
|
|
231
|
+
* Count image blocks in a message payload, recursing into nested content (tool-result blocks carry their inner blocks) — seeds each node's
|
|
232
|
+
* `imgs`, which the stats board's image cell sums over the LIVE surface (compacted/pruned messages stop counting).
|
|
240
233
|
*/
|
|
241
234
|
function imageCountOf(blocks) {
|
|
242
235
|
let count = 0;
|
|
@@ -312,12 +305,10 @@ const contextHeadersSchema = z.object({ headers: z.array(z.object({
|
|
|
312
305
|
tools: z.array(headerToolSchema)
|
|
313
306
|
}).strict()) }).strict();
|
|
314
307
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* dsh 0.1.1-rc.1+ `stateSchema`/`wire` contract.
|
|
308
|
+
* State and wire are the same shape (the view only shallow-copies each record), so one schema validates both under the dsh 0.1.1-rc.1+
|
|
309
|
+
* `stateSchema`/`wire` contract.
|
|
318
310
|
*/
|
|
319
311
|
const contextHeadersStateSchema = contextHeadersSchema;
|
|
320
|
-
/** Fold one `request/header` payload into an epoch record (display-priced). */
|
|
321
312
|
function recordOf(event) {
|
|
322
313
|
if (event.type !== "request/header") return null;
|
|
323
314
|
const rawHeader = event.data.header;
|
|
@@ -342,14 +333,9 @@ function recordOf(event) {
|
|
|
342
333
|
return record;
|
|
343
334
|
}
|
|
344
335
|
/**
|
|
345
|
-
* The context-headers projection unit
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* Dual-contract definition (see compat.ts): `schema`/`view` for
|
|
350
|
-
* dsh <= 0.1.0-rc.8, `stateSchema`/`wire` for dsh >= 0.1.1-rc.1. Without
|
|
351
|
-
* `wire`, the 0.1.1-rc.1+ registry treats the unit as host-only and the
|
|
352
|
-
* Context browser's system/tools sections would degrade.
|
|
336
|
+
* The context-headers projection unit; registered alongside the timeline unit (host/index.ts); clients read it through
|
|
337
|
+
* `useProjection('contextHeaders')` and degrade to tokens-only header sections when the key is absent. Dual-contract definition (see
|
|
338
|
+
* compat.ts).
|
|
353
339
|
*/
|
|
354
340
|
function createContextHeadersDefinition() {
|
|
355
341
|
const view = (state) => ({ headers: state.headers.map((h) => ({
|
|
@@ -394,7 +380,6 @@ function installSettings(ctx) {
|
|
|
394
380
|
}
|
|
395
381
|
//#endregion
|
|
396
382
|
//#region src/host/fold.ts
|
|
397
|
-
/** Keep only the trailing `maxTurns` turn-runs of a request timeline. */
|
|
398
383
|
function trimToLastTurns(requests, maxTurns) {
|
|
399
384
|
let runs = 0;
|
|
400
385
|
let start = requests.length;
|
|
@@ -410,7 +395,6 @@ function trimToLastTurns(requests, maxTurns) {
|
|
|
410
395
|
}
|
|
411
396
|
return requests.slice(start);
|
|
412
397
|
}
|
|
413
|
-
/** Distinct turn runs in a request timeline (consecutive equal-turn runs). */
|
|
414
398
|
function countTurnRuns(requests) {
|
|
415
399
|
let runs = 0;
|
|
416
400
|
let prevTurn;
|
|
@@ -420,7 +404,6 @@ function countTurnRuns(requests) {
|
|
|
420
404
|
}
|
|
421
405
|
return runs;
|
|
422
406
|
}
|
|
423
|
-
/** Retain the newest tail of the two unbounded lists (bounded persisted state). */
|
|
424
407
|
function trimState(st, bounds) {
|
|
425
408
|
if (countTurnRuns(st.requests) > bounds.maxKeptTurns) st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
|
|
426
409
|
if (st.requests.length > bounds.maxRequestSteps) st.requests = st.requests.slice(-bounds.maxRequestSteps);
|
|
@@ -808,10 +791,9 @@ function applyTimeline(state, event, bounds) {
|
|
|
808
791
|
return state;
|
|
809
792
|
}
|
|
810
793
|
/**
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
* objects are never mutated).
|
|
794
|
+
* Serve the projection's wire view: bound the surface nodes to the newest tail and attach each event to the request around it; stamp
|
|
795
|
+
* COPIES
|
|
796
|
+
* — the persisted state objects are never mutated.
|
|
815
797
|
*/
|
|
816
798
|
function buildTimelineView(state, bounds) {
|
|
817
799
|
const surfaceTotal = state.sums.user + state.sums.inject + state.sums.assistant + state.sums.tool;
|
|
@@ -1078,7 +1060,6 @@ function createContextTimelineDefinition(config) {
|
|
|
1078
1060
|
//#endregion
|
|
1079
1061
|
//#region src/host/index.ts
|
|
1080
1062
|
const name = "dsh-context";
|
|
1081
|
-
/** Required services: the session-projection registry that drives the unit. */
|
|
1082
1063
|
const inject = ["sessionProjections"];
|
|
1083
1064
|
function apply(ctx, config) {
|
|
1084
1065
|
ctx.sessionProjections.register(createContextTimelineDefinition(config));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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": {
|